Skip to content

Commit a5091fd

Browse files
committed
feat(registry): add a week-in-merges leaderboard block
Authors race across the row at a speed set by their merge count, bouncing at both edges.
1 parent 0be08f3 commit a5091fd

8 files changed

Lines changed: 1102 additions & 3 deletions

File tree

‎docs/catalog/blocks/week-in-merges.mdx‎

Lines changed: 540 additions & 0 deletions
Large diffs are not rendered by default.

‎docs/docs.json‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,12 +510,12 @@
510510
},
511511
{
512512
"group": "Data & charts",
513-
"tag": "17",
513+
"tag": "18",
514514
"expanded": false,
515515
"pages": [
516516
{
517517
"group": "Data",
518-
"tag": "17",
518+
"tag": "18",
519519
"expanded": false,
520520
"pages": [
521521
"catalog/components/animated-bar-chart",
@@ -534,6 +534,7 @@
534534
"catalog/blocks/us-map-bubble",
535535
"catalog/blocks/us-map-flow",
536536
"catalog/blocks/us-map-hex",
537+
"catalog/blocks/week-in-merges",
537538
"catalog/blocks/world-map"
538539
]
539540
}

‎docs/public/catalog-index.json‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5699,6 +5699,20 @@
56995699
"href": "/catalog/blocks/vpn-youtube-spot",
57005700
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/vpn-youtube-spot.png"
57015701
},
5702+
{
5703+
"name": "week-in-merges",
5704+
"type": "block",
5705+
"title": "Week in Merges",
5706+
"description": "Each author's avatar bounces across their row at a speed set by how many pull requests they merged",
5707+
"tags": [
5708+
"data",
5709+
"chart",
5710+
"ranking",
5711+
"leaderboard"
5712+
],
5713+
"href": "/catalog/blocks/week-in-merges",
5714+
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/week-in-merges.png"
5715+
},
57025716
{
57035717
"name": "weight-wave",
57045718
"type": "block",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"html":"<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=1080, height=1350\" />\n <title>Week in Merges</title>\n <script src=\"https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js\"></script>\n <style>\n *,\n *::before,\n *::after {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n html,\n body {\n width: 1080px;\n height: 1350px;\n overflow: hidden;\n background-color: #ffffff;\n }\n </style>\n </head>\n <body>\n <div\n id=\"wim-root\"\n data-composition-id=\"week-in-merges\"\n data-width=\"1080\"\n data-height=\"1350\"\n data-start=\"0\"\n data-duration=\"16\"\n data-composition-variables='[{\"id\":\"eyebrow\",\"type\":\"string\",\"label\":\"Eyebrow\",\"default\":\"ACME / WEEK IN MERGES\"},{\"id\":\"title\",\"type\":\"string\",\"label\":\"Title\",\"default\":\"Pull requests merged this week\"},{\"id\":\"period\",\"type\":\"string\",\"label\":\"Period\",\"default\":\"1 September - 7 September 2026 · America/Los_Angeles\"},{\"id\":\"total\",\"type\":\"string\",\"label\":\"Total\",\"default\":\"12 PRs\"},{\"id\":\"footer\",\"type\":\"string\",\"label\":\"Footer\",\"default\":\"Speed proportional to merges · By PR author\"},{\"id\":\"source\",\"type\":\"string\",\"label\":\"Source line\",\"default\":\"Example org · 1 repo · Bots · 0\"},{\"id\":\"rows\",\"type\":\"string\",\"label\":\"Rows (JSON array of name, merges, avatar)\",\"default\":\"[{\\\"name\\\":\\\"Ada\\\",\\\"merges\\\":12,\\\"avatar\\\":\\\"\\\"},{\\\"name\\\":\\\"Bao\\\",\\\"merges\\\":7,\\\"avatar\\\":\\\"\\\"},{\\\"name\\\":\\\"Cam\\\",\\\"merges\\\":4,\\\"avatar\\\":\\\"\\\"},{\\\"name\\\":\\\"Dee\\\",\\\"merges\\\":2,\\\"avatar\\\":\\\"\\\"},{\\\"name\\\":\\\"Eli\\\",\\\"merges\\\":1,\\\"avatar\\\":\\\"\\\"}]\"}]'\n >\n <div id=\"wim-stage\" class=\"clip\" data-start=\"0\" data-duration=\"16\" data-track-index=\"0\">\n <p id=\"wim-eyebrow\"></p>\n <h1 id=\"wim-title\"></h1>\n <div id=\"wim-sub\">\n <p id=\"wim-period\"></p>\n <p id=\"wim-total\"></p>\n </div>\n <div id=\"wim-rows\"></div>\n <div id=\"wim-rule\"></div>\n <p id=\"wim-footer\"></p>\n <p id=\"wim-source\"></p>\n </div>\n\n <style>\n #wim-root {\n position: relative;\n width: 1080px;\n height: 1350px;\n overflow: hidden;\n background: #ffffff;\n color: #141414;\n font-family: Inter, sans-serif;\n }\n\n #wim-stage {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n padding: 64px 72px 52px;\n }\n\n #wim-eyebrow {\n font-size: 13px;\n font-weight: 600;\n letter-spacing: 0.16em;\n text-transform: uppercase;\n color: rgba(20, 20, 20, 0.55);\n }\n\n #wim-title {\n margin-top: 16px;\n font-size: 52px;\n font-weight: 600;\n letter-spacing: -0.035em;\n line-height: 1.05;\n }\n\n #wim-sub {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n gap: 24px;\n margin-top: 16px;\n }\n\n #wim-period {\n font-size: 20px;\n line-height: 1.3;\n color: rgba(20, 20, 20, 0.55);\n }\n\n #wim-total {\n flex: none;\n font-size: 22px;\n font-weight: 600;\n white-space: nowrap;\n }\n\n #wim-rows {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-height: 0;\n margin-top: 28px;\n }\n\n .wim-row {\n flex: 1;\n display: flex;\n align-items: center;\n gap: 28px;\n min-height: 0;\n }\n\n .wim-meta {\n width: 280px;\n flex: none;\n min-width: 0;\n }\n\n .wim-name,\n .wim-count {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .wim-name {\n font-size: 36px;\n font-weight: 600;\n letter-spacing: -0.02em;\n line-height: 1.1;\n }\n\n .wim-count {\n margin-top: 4px;\n font-size: 16px;\n line-height: 1.2;\n color: rgba(20, 20, 20, 0.55);\n }\n\n .wim-lane {\n position: relative;\n flex: 1;\n align-self: stretch;\n min-width: 0;\n }\n\n .wim-avatar {\n position: absolute;\n left: 0;\n top: 50%;\n width: var(--wim-avatar, 88px);\n height: var(--wim-avatar, 88px);\n margin-top: calc(var(--wim-avatar, 88px) / -2);\n border-radius: 50%;\n overflow: hidden;\n }\n\n .wim-avatar-lead {\n background: rgba(20, 20, 20, 0.72);\n }\n\n .wim-avatar-rest {\n background: rgba(20, 20, 20, 0.45);\n }\n\n .wim-avatar img {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n\n .wim-initials {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n color: #ffffff;\n font-weight: 600;\n letter-spacing: -0.03em;\n }\n\n #wim-rule {\n height: 1px;\n margin: 12px 0 18px;\n background: rgba(20, 20, 20, 0.14);\n }\n\n #wim-footer,\n #wim-source {\n font-size: 16px;\n line-height: 1.4;\n color: rgba(20, 20, 20, 0.55);\n }\n\n #wim-source {\n margin-top: 4px;\n }\n </style>\n\n <script>\n (function () {\n \"use strict\";\n\n var FLOOR_SPEED = 10;\n var LEADER_SPEED = 300;\n\n function readVars() {\n var out = {};\n var root = document.getElementById(\"wim-root\");\n try {\n JSON.parse(root.getAttribute(\"data-composition-variables\")).forEach(function (d) {\n out[d.id] = d.default;\n });\n } catch (err) {\n // Declaration unreadable; the literal fallbacks below still render.\n }\n var hf = window.__hyperframes;\n var overrides =\n hf && typeof hf.getVariables === \"function\" ? hf.getVariables() : window.__hfVariables;\n if (overrides && typeof overrides === \"object\") {\n for (var key in overrides) {\n if (overrides[key] !== undefined) out[key] = overrides[key];\n }\n }\n return out;\n }\n\n function num(value, fallback, lo, hi) {\n var n = typeof value === \"number\" ? value : Number.parseFloat(value);\n if (!Number.isFinite(n)) n = fallback;\n return Math.min(hi, Math.max(lo, n));\n }\n\n function str(value, fallback) {\n return typeof value === \"string\" && value.length > 0 ? value : fallback;\n }\n\n var FALLBACK_ROWS = [\n { name: \"Ada\", merges: 12, avatar: \"\" },\n { name: \"Bao\", merges: 7, avatar: \"\" },\n { name: \"Cam\", merges: 4, avatar: \"\" },\n { name: \"Dee\", merges: 2, avatar: \"\" },\n { name: \"Eli\", merges: 1, avatar: \"\" },\n ];\n\n function parseRows(text) {\n var raw;\n try {\n raw = JSON.parse(String(text));\n } catch (err) {\n return null;\n }\n if (!Array.isArray(raw)) return null;\n var out = [];\n for (var i = 0; i < raw.length; i++) {\n var row = raw[i];\n if (!row || typeof row.name !== \"string\" || row.name.trim().length === 0) continue;\n var merges = Math.round(num(row.merges, 0, 0, 100000));\n var avatar = typeof row.avatar === \"string\" ? row.avatar : \"\";\n out.push({ name: row.name.trim(), merges: merges, avatar: avatar });\n }\n return out.length > 0 ? out : null;\n }\n\n function mergeLabel(merges) {\n return merges + (merges === 1 ? \" merge\" : \" merges\");\n }\n\n function initials(name) {\n var parts = name.trim().split(/\\s+/);\n if (parts.length >= 2) {\n return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase();\n }\n return (parts[0] || \"?\").charAt(0).toUpperCase();\n }\n\n // Linear between a floor and the leader pace, so 1 merge still travels\n // and a higher count is never slower.\n function speedFor(merges, minM, maxM) {\n if (!(maxM > minM)) return LEADER_SPEED;\n var u = (merges - minM) / (maxM - minM);\n return FLOOR_SPEED + (LEADER_SPEED - FLOOR_SPEED) * u;\n }\n\n function bounceX(distance, span) {\n var cycle = span * 2;\n if (!(cycle > 0)) return 0;\n var phase = distance % cycle;\n if (phase < 0) phase += cycle;\n return phase <= span ? phase : cycle - phase;\n }\n\n var root = document.getElementById(\"wim-root\");\n var V = readVars();\n var DURATION = num(root.getAttribute(\"data-duration\"), 16, 0.1, 600);\n var rows = parseRows(V.rows) || FALLBACK_ROWS;\n var minM = rows[0].merges;\n var maxM = rows[0].merges;\n for (var i = 1; i < rows.length; i++) {\n if (rows[i].merges < minM) minM = rows[i].merges;\n if (rows[i].merges > maxM) maxM = rows[i].merges;\n }\n\n document.getElementById(\"wim-eyebrow\").textContent = str(V.eyebrow, \"ACME / WEEK IN MERGES\");\n document.getElementById(\"wim-title\").textContent = str(\n V.title,\n \"Pull requests merged this week\",\n );\n document.getElementById(\"wim-period\").textContent = str(V.period, \"\");\n document.getElementById(\"wim-total\").textContent = str(V.total, \"\");\n document.getElementById(\"wim-footer\").textContent = str(\n V.footer,\n \"Speed proportional to merges · By PR author\",\n );\n document.getElementById(\"wim-source\").textContent = str(V.source, \"\");\n\n var mount = document.getElementById(\"wim-rows\");\n var avatars = [];\n for (var r = 0; r < rows.length; r++) {\n var row = rows[r];\n var rowEl = document.createElement(\"div\");\n rowEl.className = \"wim-row\";\n rowEl.id = \"wim-row-\" + r;\n\n var meta = document.createElement(\"div\");\n meta.className = \"wim-meta\";\n var nameEl = document.createElement(\"div\");\n nameEl.className = \"wim-name\";\n nameEl.id = \"wim-name-\" + r;\n nameEl.textContent = row.name;\n var countEl = document.createElement(\"div\");\n countEl.className = \"wim-count\";\n countEl.id = \"wim-count-\" + r;\n countEl.textContent = mergeLabel(row.merges);\n meta.appendChild(nameEl);\n meta.appendChild(countEl);\n\n var lane = document.createElement(\"div\");\n lane.className = \"wim-lane\";\n lane.id = \"wim-lane-\" + r;\n\n var avatar = document.createElement(\"div\");\n avatar.className =\n \"wim-avatar \" + (row.merges === maxM ? \"wim-avatar-lead\" : \"wim-avatar-rest\");\n avatar.id = \"wim-avatar-\" + r;\n\n if (row.avatar) {\n var img = document.createElement(\"img\");\n img.id = \"wim-avatar-img-\" + r;\n img.alt = \"\";\n img.src = row.avatar;\n avatar.appendChild(img);\n } else {\n var letters = document.createElement(\"div\");\n letters.className = \"wim-initials\";\n letters.id = \"wim-initials-\" + r;\n letters.textContent = initials(row.name);\n avatar.appendChild(letters);\n }\n\n lane.appendChild(avatar);\n rowEl.appendChild(meta);\n rowEl.appendChild(lane);\n mount.appendChild(rowEl);\n avatars.push({ el: avatar, nameEl: nameEl, speed: speedFor(row.merges, minM, maxM) });\n }\n\n var rowH = avatars.length ? document.getElementById(\"wim-row-0\").clientHeight : 120;\n var avatarPx = Math.max(36, Math.min(96, Math.floor(rowH * 0.62)));\n var namePx = Math.max(18, Math.min(36, Math.floor(rowH * 0.26)));\n var countPx = Math.max(13, Math.min(16, Math.floor(namePx * 0.48)));\n root.style.setProperty(\"--wim-avatar\", avatarPx + \"px\");\n for (var a = 0; a < avatars.length; a++) {\n avatars[a].nameEl.style.fontSize = namePx + \"px\";\n avatars[a].el.querySelector(\".wim-initials\") &&\n (avatars[a].el.querySelector(\".wim-initials\").style.fontSize =\n Math.round(avatarPx * 0.4) + \"px\");\n var count = document.getElementById(\"wim-count-\" + a);\n if (count) count.style.fontSize = countPx + \"px\";\n }\n\n var laneW = avatars.length ? document.getElementById(\"wim-lane-0\").clientWidth : 560;\n var span = Math.max(0, laneW - avatarPx);\n\n function render(t) {\n for (var i = 0; i < avatars.length; i++) {\n var x = bounceX(avatars[i].speed * t, span);\n avatars[i].el.style.transform = \"translateX(\" + x.toFixed(2) + \"px)\";\n }\n }\n\n // GSAP seek calls the setter. A timeline onUpdate does not run on seek.\n var driver = { _t: 0 };\n Object.defineProperty(driver, \"t\", {\n get: function () {\n return this._t;\n },\n set: function (value) {\n this._t = value;\n render(value);\n },\n });\n\n var tl = gsap.timeline({ paused: true });\n tl.to(driver, { t: DURATION, duration: DURATION, ease: \"none\" }, 0);\n render(0);\n\n window.__timelines = window.__timelines || {};\n window.__timelines[\"week-in-merges\"] = tl;\n })();\n </script>\n </div>\n </body>\n</html>\n"}

‎docs/snippets/catalog-gallery-data.mdx‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
{
2+
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
3+
"name": "week-in-merges",
4+
"type": "hyperframes:block",
5+
"title": "Week in Merges",
6+
"description": "Each author's avatar bounces across their row at a speed set by how many pull requests they merged",
7+
"tags": ["data", "chart", "ranking", "leaderboard"],
8+
"dimensions": {
9+
"width": 1080,
10+
"height": 1350
11+
},
12+
"duration": 16,
13+
"variables": [
14+
{
15+
"id": "eyebrow",
16+
"type": "string",
17+
"label": "Eyebrow",
18+
"default": "ACME / WEEK IN MERGES"
19+
},
20+
{
21+
"id": "title",
22+
"type": "string",
23+
"label": "Title",
24+
"default": "Pull requests merged this week"
25+
},
26+
{
27+
"id": "period",
28+
"type": "string",
29+
"label": "Period",
30+
"default": "1 September - 7 September 2026 · America/Los_Angeles"
31+
},
32+
{
33+
"id": "total",
34+
"type": "string",
35+
"label": "Total",
36+
"default": "12 PRs"
37+
},
38+
{
39+
"id": "footer",
40+
"type": "string",
41+
"label": "Footer",
42+
"default": "Speed proportional to merges · By PR author"
43+
},
44+
{
45+
"id": "source",
46+
"type": "string",
47+
"label": "Source line",
48+
"default": "Example org · 1 repo · Bots · 0"
49+
},
50+
{
51+
"id": "rows",
52+
"type": "string",
53+
"label": "Rows (JSON array of name, merges, avatar)",
54+
"default": "[{\"name\":\"Ada\",\"merges\":12,\"avatar\":\"\"},{\"name\":\"Bao\",\"merges\":7,\"avatar\":\"\"},{\"name\":\"Cam\",\"merges\":4,\"avatar\":\"\"},{\"name\":\"Dee\",\"merges\":2,\"avatar\":\"\"},{\"name\":\"Eli\",\"merges\":1,\"avatar\":\"\"}]"
55+
}
56+
],
57+
"files": [
58+
{
59+
"path": "week-in-merges.html",
60+
"target": "compositions/week-in-merges.html",
61+
"type": "hyperframes:composition"
62+
}
63+
],
64+
"params": [
65+
{
66+
"key": "eyebrow",
67+
"label": "Eyebrow",
68+
"type": "text",
69+
"default": "ACME / WEEK IN MERGES"
70+
},
71+
{
72+
"key": "title",
73+
"label": "Title",
74+
"type": "text",
75+
"default": "Pull requests merged this week"
76+
},
77+
{
78+
"key": "period",
79+
"label": "Period",
80+
"type": "text",
81+
"default": "1 September - 7 September 2026 · America/Los_Angeles"
82+
},
83+
{
84+
"key": "total",
85+
"label": "Total",
86+
"type": "text",
87+
"default": "12 PRs"
88+
},
89+
{
90+
"key": "footer",
91+
"label": "Footer",
92+
"type": "text",
93+
"default": "Speed proportional to merges · By PR author"
94+
},
95+
{
96+
"key": "source",
97+
"label": "Source line",
98+
"type": "text",
99+
"default": "Example org · 1 repo · Bots · 0"
100+
},
101+
{
102+
"key": "rows",
103+
"label": "Rows (JSON array of name, merges, avatar)",
104+
"type": "text",
105+
"default": "[{\"name\":\"Ada\",\"merges\":12,\"avatar\":\"\"},{\"name\":\"Bao\",\"merges\":7,\"avatar\":\"\"},{\"name\":\"Cam\",\"merges\":4,\"avatar\":\"\"},{\"name\":\"Dee\",\"merges\":2,\"avatar\":\"\"},{\"name\":\"Eli\",\"merges\":1,\"avatar\":\"\"}]"
106+
}
107+
]
108+
}

0 commit comments

Comments
 (0)