hyperframes 0.1.14 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,450 @@
1
+ <template id="nyt-chart-template">
2
+ <div data-composition-id="nyt-chart" data-width="1920" data-height="1080" data-duration="15">
3
+ <!-- Google Fonts Import -->
4
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
5
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
6
+ <link
7
+ href="https://fonts.googleapis.com/css2?family=Libre+Baskerville:wght@400;700&family=Libre+Franklin:wght@300;400;600&display=block"
8
+ rel="stylesheet"
9
+ />
10
+
11
+ <div class="chart-container">
12
+ <!-- Header Section -->
13
+ <div class="header">
14
+ <h1 class="headline">Monthly Revenue vs. Conversion Rate</h1>
15
+ <p class="subtitle">Jan–Jun 2024, in thousands</p>
16
+ <div class="key">
17
+ <div class="key-item">
18
+ <div class="key-box revenue-box"></div>
19
+ <span class="key-text">Revenue</span>
20
+ </div>
21
+ <div class="key-item">
22
+ <div class="key-line conversion-line"></div>
23
+ <span class="key-text">Conversion Rate</span>
24
+ </div>
25
+ </div>
26
+ </div>
27
+
28
+ <!-- SVG Chart Area -->
29
+ <svg class="chart-svg" viewBox="0 0 1600 700" preserveAspectRatio="xMidYMid meet">
30
+ <!-- Gridlines Group -->
31
+ <g class="gridlines"></g>
32
+
33
+ <!-- X-Axis Labels -->
34
+ <g class="x-axis-labels"></g>
35
+
36
+ <!-- Bars Group (Revenue) -->
37
+ <g class="bars-group"></g>
38
+
39
+ <!-- Bar Labels Group -->
40
+ <g class="bar-labels-group"></g>
41
+
42
+ <!-- Line Group (Conversion Rate) -->
43
+ <g class="line-group">
44
+ <path class="conversion-path" fill="none" stroke="#326FA8" stroke-width="1.5" />
45
+ <circle class="line-dot" r="5" fill="#326FA8" style="opacity: 0" />
46
+ </g>
47
+
48
+ <!-- Line Labels Group -->
49
+ <g class="line-labels-group"></g>
50
+
51
+ <!-- Baseline -->
52
+ <line x1="100" y1="600" x2="1500" y2="600" stroke="black" stroke-width="1" />
53
+ </svg>
54
+
55
+ <!-- Source Line -->
56
+ <div class="source">Source: Internal analytics</div>
57
+ </div>
58
+
59
+ <style>
60
+ [data-composition-id="nyt-chart"] {
61
+ width: 1920px;
62
+ height: 1080px;
63
+ background-color: #faf9f6;
64
+ font-family: "Libre Franklin", sans-serif;
65
+ color: #333;
66
+ display: flex;
67
+ justify-content: center;
68
+ align-items: center;
69
+ overflow: hidden;
70
+ }
71
+
72
+ [data-composition-id="nyt-chart"] .chart-container {
73
+ width: 1600px;
74
+ height: 900px;
75
+ display: flex;
76
+ flex-direction: column;
77
+ position: relative;
78
+ transform: scale(1.15);
79
+ transform-origin: center center;
80
+ }
81
+
82
+ [data-composition-id="nyt-chart"] .header {
83
+ margin-bottom: 40px;
84
+ text-align: left;
85
+ }
86
+
87
+ [data-composition-id="nyt-chart"] .headline {
88
+ font-family: "Libre Baskerville", serif;
89
+ font-size: 42px;
90
+ margin: 0;
91
+ padding: 0;
92
+ clip-path: inset(0 100% 0 0);
93
+ white-space: nowrap;
94
+ }
95
+
96
+ [data-composition-id="nyt-chart"] .subtitle {
97
+ font-size: 16px;
98
+ color: #d4d4d4;
99
+ margin: 8px 0 16px 0;
100
+ clip-path: inset(0 100% 0 0);
101
+ white-space: nowrap;
102
+ }
103
+
104
+ [data-composition-id="nyt-chart"] .key {
105
+ display: flex;
106
+ gap: 24px;
107
+ opacity: 0;
108
+ }
109
+
110
+ [data-composition-id="nyt-chart"] .key-item {
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 8px;
114
+ }
115
+
116
+ [data-composition-id="nyt-chart"] .key-box {
117
+ width: 10px;
118
+ height: 10px;
119
+ background-color: #5c5c5c;
120
+ }
121
+
122
+ [data-composition-id="nyt-chart"] .key-line {
123
+ width: 16px;
124
+ height: 1.5px;
125
+ background-color: #326fa8;
126
+ }
127
+
128
+ [data-composition-id="nyt-chart"] .key-text {
129
+ font-size: 13px;
130
+ color: #666;
131
+ }
132
+
133
+ [data-composition-id="nyt-chart"] .chart-svg {
134
+ width: 100%;
135
+ height: 600px;
136
+ overflow: visible;
137
+ }
138
+
139
+ [data-composition-id="nyt-chart"] .source {
140
+ position: absolute;
141
+ bottom: 0;
142
+ left: 0;
143
+ font-size: 11px;
144
+ color: #999;
145
+ opacity: 0;
146
+ }
147
+
148
+ [data-composition-id="nyt-chart"] .grid-line {
149
+ stroke: #e8e8e8;
150
+ stroke-width: 0.5px;
151
+ opacity: 0;
152
+ }
153
+
154
+ [data-composition-id="nyt-chart"] .axis-label {
155
+ font-size: 14px;
156
+ fill: #666;
157
+ text-anchor: middle;
158
+ }
159
+
160
+ [data-composition-id="nyt-chart"] .bar {
161
+ fill: #5c5c5c;
162
+ }
163
+
164
+ [data-composition-id="nyt-chart"] .bar-label {
165
+ font-size: 14px;
166
+ fill: #333;
167
+ text-anchor: middle;
168
+ opacity: 0;
169
+ }
170
+
171
+ [data-composition-id="nyt-chart"] .line-label {
172
+ font-size: 14px;
173
+ fill: #326fa8;
174
+ font-weight: 600;
175
+ text-anchor: middle;
176
+ opacity: 0;
177
+ }
178
+ </style>
179
+
180
+ <script>
181
+ (function initChart() {
182
+ // Wait for DOM — the runtime may not have cloned the template content yet
183
+ const svg = document.querySelector(`[data-composition-id="nyt-chart"] .chart-svg`);
184
+ if (!svg) {
185
+ setTimeout(initChart, 50);
186
+ return;
187
+ }
188
+ const tl = gsap.timeline({ paused: true });
189
+
190
+ // Data
191
+ const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"];
192
+ const revenueData = [8, 12, 15, 11, 18, 22]; // in thousands
193
+ const conversionData = [2.1, 2.8, 3.2, 2.9, 3.8, 4.2]; // in percentage
194
+
195
+ // Chart Dimensions
196
+ const chartWidth = 1400;
197
+ const chartHeight = 500;
198
+ const startX = 100;
199
+ const startY = 600;
200
+ const barWidth = 80;
201
+ const spacing = (chartWidth - barWidth * months.length) / (months.length + 1);
202
+
203
+ // Scales
204
+ const maxRevenue = 25;
205
+ const maxConversion = 5;
206
+
207
+ const getBarY = (val) => startY - (val / maxRevenue) * chartHeight;
208
+ const getLineY = (val) => startY - (val / maxConversion) * chartHeight;
209
+ const getX = (i) => startX + spacing + i * (barWidth + spacing) + barWidth / 2;
210
+
211
+ // 1. Setup Elements (svg already found above)
212
+ const gridGroup = svg.querySelector(".gridlines");
213
+ const xAxisGroup = svg.querySelector(".x-axis-labels");
214
+ const barsGroup = svg.querySelector(".bars-group");
215
+ const barLabelsGroup = svg.querySelector(".bar-labels-group");
216
+ const lineLabelsGroup = svg.querySelector(".line-labels-group");
217
+ const conversionPath = svg.querySelector(".conversion-path");
218
+ const lineDot = svg.querySelector(".line-dot");
219
+
220
+ // Create Gridlines (5 lines)
221
+ for (let i = 1; i <= 5; i++) {
222
+ const y = startY - i * (chartHeight / 5);
223
+ const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
224
+ line.setAttribute("x1", startX);
225
+ line.setAttribute("y1", y);
226
+ line.setAttribute("x2", startX + chartWidth);
227
+ line.setAttribute("y2", y);
228
+ line.setAttribute("class", "grid-line");
229
+ gridGroup.appendChild(line);
230
+ }
231
+
232
+ // Create X-Axis Labels & Bars & Bar Labels
233
+ const linePoints = [];
234
+ months.forEach((month, i) => {
235
+ const x = getX(i);
236
+
237
+ // X-Axis Label
238
+ const label = document.createElementNS("http://www.w3.org/2000/svg", "text");
239
+ label.setAttribute("x", x);
240
+ label.setAttribute("y", startY + 30);
241
+ label.setAttribute("class", "axis-label");
242
+ label.textContent = month;
243
+ xAxisGroup.appendChild(label);
244
+
245
+ // Bar
246
+ const barHeight = (revenueData[i] / maxRevenue) * chartHeight;
247
+ const bar = document.createElementNS("http://www.w3.org/2000/svg", "rect");
248
+ bar.setAttribute("x", x - barWidth / 2);
249
+ bar.setAttribute("y", startY);
250
+ bar.setAttribute("width", barWidth);
251
+ bar.setAttribute("height", 0);
252
+ bar.setAttribute("class", "bar");
253
+ barsGroup.appendChild(bar);
254
+
255
+ // Bar Label
256
+ const barLabel = document.createElementNS("http://www.w3.org/2000/svg", "text");
257
+ barLabel.setAttribute("x", x);
258
+ barLabel.setAttribute("y", getBarY(revenueData[i]) - 10);
259
+ barLabel.setAttribute("class", "bar-label");
260
+ barLabel.textContent = `$${revenueData[i]}K`;
261
+ barLabelsGroup.appendChild(barLabel);
262
+
263
+ // Line Point
264
+ linePoints.push(`${x},${getLineY(conversionData[i])}`);
265
+
266
+ // Line Label
267
+ const lineLabel = document.createElementNS("http://www.w3.org/2000/svg", "text");
268
+ lineLabel.setAttribute("x", x);
269
+ lineLabel.setAttribute("y", getLineY(conversionData[i]) - 15);
270
+ lineLabel.setAttribute("class", "line-label");
271
+ lineLabel.textContent = `${conversionData[i]}%`;
272
+ lineLabelsGroup.appendChild(lineLabel);
273
+ });
274
+
275
+ // Set Path Data
276
+ conversionPath.setAttribute("d", `M ${linePoints.join(" L ")}`);
277
+ const pathLength = conversionPath.getTotalLength();
278
+ conversionPath.style.strokeDasharray = pathLength;
279
+ conversionPath.style.strokeDashoffset = pathLength;
280
+
281
+ // --- ANIMATION TIMELINE ---
282
+
283
+ // 2. Headline & Subtitle (Draw on left-to-right)
284
+ tl.to(
285
+ `[data-composition-id="nyt-chart"] .headline`,
286
+ {
287
+ clipPath: "inset(0 0% 0 0)",
288
+ duration: 1.2,
289
+ ease: "power1.inOut",
290
+ },
291
+ 0,
292
+ );
293
+
294
+ tl.to(
295
+ `[data-composition-id="nyt-chart"] .subtitle`,
296
+ {
297
+ clipPath: "inset(0 0% 0 0)",
298
+ duration: 1.2,
299
+ ease: "power1.inOut",
300
+ },
301
+ 0.2,
302
+ );
303
+
304
+ // 3. Inline Key
305
+ tl.to(
306
+ `[data-composition-id="nyt-chart"] .key`,
307
+ {
308
+ opacity: 1,
309
+ duration: 0.4,
310
+ ease: "power2.out",
311
+ },
312
+ 1.0,
313
+ );
314
+
315
+ // 4. Gridlines (Stagger bottom to top)
316
+ tl.to(
317
+ `[data-composition-id="nyt-chart"] .grid-line`,
318
+ {
319
+ opacity: 1,
320
+ duration: 0.5,
321
+ stagger: 0.25,
322
+ ease: "power2.out",
323
+ },
324
+ 0.5,
325
+ );
326
+
327
+ // 5. Bars (Revenue)
328
+ const bars = barsGroup.querySelectorAll(".bar");
329
+ const barLabels = barLabelsGroup.querySelectorAll(".bar-label");
330
+
331
+ bars.forEach((bar, i) => {
332
+ const barHeight = (revenueData[i] / maxRevenue) * chartHeight;
333
+ const startTime = 1.5 + i * 0.5; // 0.8s duration, 0.3s overlap (0.8 - 0.3 = 0.5 step)
334
+
335
+ tl.to(
336
+ bar,
337
+ {
338
+ attr: {
339
+ height: barHeight,
340
+ y: startY - barHeight,
341
+ },
342
+ duration: 0.8,
343
+ ease: "power2.out",
344
+ },
345
+ startTime,
346
+ );
347
+
348
+ // 6. Bar Labels
349
+ tl.to(
350
+ barLabels[i],
351
+ {
352
+ opacity: 1,
353
+ duration: 0.3,
354
+ ease: "power2.out",
355
+ },
356
+ startTime + 0.8,
357
+ );
358
+ });
359
+
360
+ // 7. Line (Conversion Rate)
361
+ // Starts 0.5s after the last bar begins
362
+ const lastBarStartTime = 1.5 + (months.length - 1) * 0.5;
363
+ const lineStartTime = lastBarStartTime + 0.5;
364
+
365
+ tl.to(
366
+ conversionPath,
367
+ {
368
+ strokeDashoffset: 0,
369
+ duration: 3,
370
+ ease: "none",
371
+ },
372
+ lineStartTime,
373
+ );
374
+
375
+ // 8. Line Dot & 9. Line Labels
376
+ // We need to sync the dot and labels with the path drawing
377
+ tl.set(
378
+ lineDot,
379
+ { opacity: 1, attr: { cx: getX(0), cy: getLineY(conversionData[0]) } },
380
+ lineStartTime,
381
+ );
382
+
383
+ const lineLabels = lineLabelsGroup.querySelectorAll(".line-label");
384
+ months.forEach((_, i) => {
385
+ if (i > 0) {
386
+ const x = getX(i);
387
+ const y = getLineY(conversionData[i]);
388
+ tl.to(
389
+ lineDot,
390
+ {
391
+ attr: {
392
+ cx: x,
393
+ cy: y,
394
+ },
395
+ duration: 3 / (months.length - 1),
396
+ ease: "none",
397
+ },
398
+ lineStartTime + ((i - 1) / (months.length - 1)) * 3,
399
+ );
400
+ }
401
+
402
+ const labelTime = lineStartTime + (i / (months.length - 1)) * 3 + 0.2;
403
+
404
+ // Set initial text to 0.0%
405
+ lineLabels[i].textContent = "0.0%";
406
+
407
+ // Fade in
408
+ tl.to(
409
+ lineLabels[i],
410
+ {
411
+ opacity: 1,
412
+ duration: 0.1,
413
+ ease: "power2.out",
414
+ },
415
+ labelTime,
416
+ );
417
+
418
+ // Animate value
419
+ const dummy = { val: 0 };
420
+ tl.to(
421
+ dummy,
422
+ {
423
+ val: conversionData[i],
424
+ duration: 0.8,
425
+ ease: "power2.out",
426
+ onUpdate: () => {
427
+ lineLabels[i].textContent = dummy.val.toFixed(1) + "%";
428
+ },
429
+ },
430
+ labelTime,
431
+ );
432
+ });
433
+
434
+ // 11. Source Line
435
+ tl.to(
436
+ `[data-composition-id="nyt-chart"] .source`,
437
+ {
438
+ opacity: 1,
439
+ duration: 0.5,
440
+ ease: "power2.out",
441
+ },
442
+ ">+0.5",
443
+ );
444
+
445
+ // Register timeline
446
+ window.__timelines["nyt-chart"] = tl;
447
+ })();
448
+ </script>
449
+ </div>
450
+ </template>
@@ -0,0 +1,51 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=1920, height=1080" />
6
+ <title>NYT Graph</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
8
+ <style>
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+ html,
15
+ body {
16
+ margin: 0;
17
+ width: 1920px;
18
+ height: 1080px;
19
+ overflow: hidden;
20
+ background-color: #faf9f6;
21
+ }
22
+ </style>
23
+ </head>
24
+ <body>
25
+ <div
26
+ id="root"
27
+ data-composition-id="main"
28
+ data-start="0"
29
+ data-duration="15"
30
+ data-width="1920"
31
+ data-height="1080"
32
+ >
33
+ <div
34
+ id="nyt-chart-comp"
35
+ data-composition-id="nyt-chart"
36
+ data-composition-src="compositions/nyt-chart.html"
37
+ data-start="0"
38
+ data-duration="15"
39
+ data-track-index="0"
40
+ data-width="1920"
41
+ data-height="1080"
42
+ ></div>
43
+ </div>
44
+
45
+ <script>
46
+ window.__timelines = window.__timelines || {};
47
+ const tl = gsap.timeline({ paused: true });
48
+ window.__timelines["main"] = tl;
49
+ </script>
50
+ </body>
51
+ </html>
@@ -0,0 +1,30 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1920 1080" width="1920" height="1080">
2
+ <defs>
3
+ <filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
4
+ <feDropShadow dx="0" dy="2" stdDeviation="2" flood-opacity="0.2"/>
5
+ </filter>
6
+ <g id="cursor-arrow">
7
+ <path d="M0 0 L 16 16 L 9.5 16 L 6.5 23 L 6.5 16 L 0 16 Z" fill="white" stroke="#000" stroke-width="1.5" stroke-linejoin="round" filter="url(#shadow)"/>
8
+ </g>
9
+ </defs>
10
+
11
+ <rect width="1920" height="1080" fill="#f0f0f0" />
12
+
13
+ <g id="cursor-designer" transform="translate(600, 500)">
14
+ <use href="#cursor-arrow" x="0" y="0"/>
15
+ <rect x="16" y="20" width="90" height="24" rx="4" fill="#A259FF"/>
16
+ <text x="61" y="36" font-family="Inter, sans-serif" font-size="12" fill="white" text-anchor="middle" font-weight="bold">Designer</text>
17
+ </g>
18
+
19
+ <g id="cursor-engineer" transform="translate(900, 400)">
20
+ <use href="#cursor-arrow" x="0" y="0"/>
21
+ <rect x="16" y="20" width="90" height="24" rx="4" fill="#1ABCFE"/>
22
+ <text x="61" y="36" font-family="Inter, sans-serif" font-size="12" fill="white" text-anchor="middle" font-weight="bold">Engineer</text>
23
+ </g>
24
+
25
+ <g id="cursor-pm" transform="translate(1200, 600)">
26
+ <use href="#cursor-arrow" x="0" y="0"/>
27
+ <rect x="16" y="20" width="50" height="24" rx="4" fill="#F24E1E"/>
28
+ <text x="41" y="36" font-family="Inter, sans-serif" font-size="12" fill="white" text-anchor="middle" font-weight="bold">PM</text>
29
+ </g>
30
+ </svg>
@@ -0,0 +1,15 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1920 1080" width="1920" height="1080">
2
+ <g transform="translate(880, 420)">
3
+ <!-- Top-left: rounded square -->
4
+ <path id="logo-tl" d="M 40 0 A 40 40 0 0 0 0 40 L 0 80 A 40 40 0 0 0 40 120 A 40 40 0 0 0 80 80 L 80 40 A 40 40 0 0 0 40 0 Z" fill="#F24E1E" />
5
+
6
+ <!-- Top-right: rounded square -->
7
+ <path id="logo-tr" d="M 120 0 A 40 40 0 0 0 80 40 L 80 80 A 40 40 0 0 0 120 120 A 40 40 0 0 0 160 80 L 160 40 A 40 40 0 0 0 120 0 Z" fill="#A259FF" />
8
+
9
+ <!-- Bottom-left: half-circle -->
10
+ <path id="logo-bl" d="M 0 120 A 40 120 0 0 0 80 120 Z" fill="#1ABCFE" />
11
+
12
+ <!-- Bottom-right: half-circle -->
13
+ <path id="logo-br" d="M 80 120 A 40 120 0 0 0 160 120 Z" fill="#0ACF83" />
14
+ </g>
15
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1920 1080" width="1920" height="1080">
2
+ <rect id="pill-tl" x="870" y="410" width="80" height="120" rx="40" fill="#F24E1E" />
3
+ <rect id="pill-tr" x="970" y="410" width="80" height="120" rx="40" fill="#A259FF" />
4
+ <rect id="pill-bl" x="870" y="550" width="80" height="120" rx="40" fill="#1ABCFE" />
5
+ <rect id="pill-br" x="970" y="550" width="80" height="120" rx="40" fill="#0ACF83" />
6
+ </svg>