Files
hyperframes/docs/public/catalog/blocks/lt-neon-border.json
Miguel Ángel 9734578e60 feat(registry): bring back the video-primitive moves (#3169)
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.
2026-08-10 18:46:03 -04:00

1 line
15 KiB
JSON

{"html":"<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <script src=\"https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js\"></script>\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n html,\n body {\n background: transparent;\n overflow: hidden;\n }\n body {\n width: 1920px;\n height: 1080px;\n font-family: \"Inter\", sans-serif;\n }\n\n #root .nb-wrap {\n position: absolute;\n left: 50px;\n bottom: 56px;\n width: 1120px;\n height: 510px;\n }\n #root .nb-svg {\n position: absolute;\n inset: 0;\n display: block;\n }\n /* The frame's inner box, inset by the bloom padding so the copy lands\n inside the traced rectangle. */\n #root .nb-panel {\n position: absolute;\n left: 150px;\n top: 150px;\n width: 820px;\n height: 210px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n gap: 14px;\n padding: 0 52px;\n }\n #root .nb-name {\n display: block;\n font-size: 54px;\n font-weight: 700;\n color: #ffffff;\n line-height: 1;\n letter-spacing: -0.015em;\n white-space: nowrap;\n text-shadow: 0 2px 20px rgba(0, 0, 0, 0.75);\n }\n #root .nb-role {\n display: block;\n font-size: 25px;\n font-weight: 400;\n color: #d5dae3;\n line-height: 1.2;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n white-space: nowrap;\n text-shadow: 0 2px 16px rgba(0, 0, 0, 0.75);\n }\n </style>\n </head>\n <body>\n <div\n id=\"root\"\n data-composition-id=\"lt-neon-border\"\n data-composition-variables='[\n {\"id\":\"name\",\"type\":\"string\",\"label\":\"Name\",\"default\":\"Dr. Maya Chen\"},\n {\"id\":\"role\",\"type\":\"string\",\"label\":\"Role\",\"default\":\"Host · Neuroscientist\"},\n {\"id\":\"accent\",\"type\":\"color\",\"label\":\"Accent\",\"default\":\"#cc9149\"},\n {\"id\":\"movement\",\"type\":\"enum\",\"label\":\"Movement\",\"default\":\"step\",\"options\":[{\"value\":\"step\",\"label\":\"Step (snap at corners)\"},{\"value\":\"glide\",\"label\":\"Glide (smooth)\"}]},\n {\"id\":\"speed\",\"type\":\"number\",\"label\":\"Speed\",\"default\":1.6,\"min\":0.2,\"max\":6,\"step\":0.1,\"unit\":\"corners/s\"},\n {\"id\":\"cornerRadius\",\"type\":\"number\",\"label\":\"Corner radius\",\"default\":26,\"min\":0,\"max\":90,\"step\":1,\"unit\":\"px\"},\n {\"id\":\"thickness\",\"type\":\"number\",\"label\":\"Thickness\",\"default\":5,\"min\":1,\"max\":16,\"step\":1,\"unit\":\"px\"},\n {\"id\":\"arcLength\",\"type\":\"number\",\"label\":\"Arc length\",\"default\":22,\"min\":5,\"max\":100,\"step\":1,\"unit\":\"%\"},\n {\"id\":\"glow\",\"type\":\"number\",\"label\":\"Glow\",\"default\":1,\"min\":0,\"max\":2,\"step\":0.05,\"unit\":\"x\"}\n ]'\n data-start=\"0\"\n data-duration=\"4.8\"\n data-width=\"1920\"\n data-height=\"1080\"\n >\n <div id=\"nb-clip\" class=\"clip\" data-start=\"0\" data-duration=\"4.8\" data-track-index=\"0\">\n <div id=\"nb-wrap\" class=\"nb-wrap\">\n <svg\n id=\"nb-svg\"\n class=\"nb-svg\"\n width=\"1120\"\n height=\"510\"\n viewBox=\"0 0 1120 510\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <defs>\n <g id=\"nb-seg-a-glow\"></g>\n <g id=\"nb-seg-a-core\"></g>\n <g id=\"nb-seg-b-glow\"></g>\n <g id=\"nb-seg-b-core\"></g>\n </defs>\n <g transform=\"translate(150,150)\">\n <!-- Wide bloom sits UNDER the panel so the halo reads outside the\n frame and the interior stays hardware-clean. -->\n <g id=\"nb-bloom-under\"></g>\n <path id=\"nb-fill\" d=\"\" fill=\"rgba(9,11,15,0.78)\" />\n <path id=\"nb-rail\" d=\"\" fill=\"none\" stroke-linejoin=\"round\" />\n <g id=\"nb-bloom-over\"></g>\n </g>\n </svg>\n <div id=\"nb-panel\" class=\"nb-panel\">\n <span id=\"nb-name\" class=\"nb-name\" data-var-text=\"name\">Dr. Maya Chen</span>\n <span id=\"nb-role\" class=\"nb-role\" data-var-text=\"role\">Host · Neuroscientist</span>\n </div>\n </div>\n </div>\n </div>\n\n <script>\n (function () {\n window.__timelines = window.__timelines || {};\n\n var SVG_NS = \"http://www.w3.org/2000/svg\";\n var W = 820; // traced rectangle width\n var H = 210; // traced rectangle height\n var DUR = 4.8;\n\n var vars =\n window.__hyperframes && window.__hyperframes.getVariables\n ? window.__hyperframes.getVariables()\n : {};\n\n function num(v, fallback) {\n var n = typeof v === \"number\" ? v : parseFloat(v);\n return isFinite(n) ? n : fallback;\n }\n\n var accent = /^#[0-9a-fA-F]{6}$/.test(String(vars.accent))\n ? String(vars.accent)\n : \"#cc9149\";\n var mode = vars.movement === \"glide\" ? \"glide\" : \"step\";\n var speed = Math.max(0.05, num(vars.speed, 1.6)); // corners per second\n var radius = Math.min(Math.min(W, H) / 2, Math.max(0, num(vars.cornerRadius, 26)));\n var thickness = Math.max(0.5, num(vars.thickness, 5));\n var arcFrac = Math.min(1, Math.max(0.02, num(vars.arcLength, 22) / 100));\n var glow = Math.max(0, num(vars.glow, 1));\n\n // Hot core: the accent lifted most of the way to white, the way a real\n // filament reads brighter than its halo.\n function towardWhite(hex, amount) {\n var v = parseInt(hex.slice(1), 16);\n var out = \"#\";\n for (var s = 16; s >= 0; s -= 8) {\n var c = (v >> s) & 255;\n var m = Math.round(c + (255 - c) * amount);\n out += (m < 16 ? \"0\" : \"\") + m.toString(16);\n }\n return out;\n }\n var hot = towardWhite(accent, 0.78);\n\n // ---- geometry -------------------------------------------------------\n // Rounded rect, clockwise, starting where the top edge leaves the\n // top-left corner arc.\n var r = radius;\n var arc = \"A \" + r + \" \" + r + \" 0 0 1 \";\n var d = [\n \"M \" + r + \" 0\",\n \"H \" + (W - r),\n arc + W + \" \" + r,\n \"V \" + (H - r),\n arc + (W - r) + \" \" + H,\n \"H \" + r,\n arc + \"0 \" + (H - r),\n \"V \" + r,\n arc + r + \" 0\",\n \"Z\",\n ].join(\" \");\n\n var fillEl = document.getElementById(\"nb-fill\");\n var railEl = document.getElementById(\"nb-rail\");\n fillEl.setAttribute(\"d\", d);\n railEl.setAttribute(\"d\", d);\n railEl.setAttribute(\"stroke\", accent);\n railEl.setAttribute(\"stroke-width\", String(thickness));\n railEl.setAttribute(\"stroke-opacity\", \"0.14\");\n\n // Analytic perimeter, then rescaled to whatever length the renderer\n // actually measures for the path so dash distances line up exactly.\n var SW = W - 2 * r; // straight run, horizontal\n var SH = H - 2 * r; // straight run, vertical\n var AC = (Math.PI * r) / 2; // one corner arc\n var P_ANALYTIC = 2 * SW + 2 * SH + 4 * AC;\n var P = railEl.getTotalLength() || P_ANALYTIC;\n var K = P / P_ANALYTIC;\n\n // Distance along the path of the MIDPOINT of each corner arc, so the\n // light parks centred on the corner rather than skidding past it.\n var CORNERS = [\n (SW + AC / 2) * K, // top-right\n (SW + SH + 1.5 * AC) * K, // bottom-right\n (2 * SW + SH + 2.5 * AC) * K, // bottom-left\n (2 * SW + 2 * SH + 3.5 * AC) * K, // top-left\n ];\n\n /** Absolute path distance of corner k, for any integer k (k may be\n * negative or beyond one lap). Pure function of k — no accumulator. */\n function cornerPos(k) {\n var lap = Math.floor(k / 4);\n var i = k - lap * 4;\n return CORNERS[i] + lap * P;\n }\n\n // ---- easing ---------------------------------------------------------\n // The step curve overshoots (y2 = 1.05); that overshoot IS the snap.\n function cubicBezier(x1, y1, x2, y2) {\n function axis(t, a, b) {\n var u = 1 - t;\n return 3 * u * u * t * a + 3 * u * t * t * b + t * t * t;\n }\n return function (x) {\n if (x <= 0) return 0;\n if (x >= 1) return 1;\n var lo = 0;\n var hi = 1;\n var t = x;\n // Fixed iteration count: same input, same output, every run.\n for (var i = 0; i < 32; i++) {\n t = (lo + hi) / 2;\n if (axis(t, x1, x2) < x) lo = t;\n else hi = t;\n }\n return axis(t, y1, y2);\n };\n }\n var ease =\n mode === \"glide\" ? cubicBezier(0.65, 0, 0.35, 1) : cubicBezier(0.72, 0.16, 0.18, 1.05);\n\n var beat = 1 / speed; // seconds per corner hop\n\n /** Head position (path distance) at time t. floor/frac of t/beat is the\n * closed form of the reference's `stepT += dt` accumulator. */\n function headAt(t) {\n var total = t / beat;\n var k = Math.floor(total);\n var u = total - k;\n var a = cornerPos(k);\n var b = cornerPos(k + 1);\n return a + (b - a) * ease(u);\n }\n\n // ---- arc construction ----------------------------------------------\n // Each arc is a run of abutting dash segments whose opacity falls off\n // behind the head; that ramp is the taper. Geometry lives once in\n // <defs> and is shared by every bloom layer through <use>.\n function buildSegments(groupId, count, falloff) {\n var g = document.getElementById(groupId);\n var segs = [];\n for (var i = 0; i < count; i++) {\n var p = document.createElementNS(SVG_NS, \"path\");\n p.setAttribute(\"d\", d);\n p.setAttribute(\"fill\", \"none\");\n p.setAttribute(\"stroke-linecap\", i === 0 ? \"round\" : \"butt\");\n p.setAttribute(\"opacity\", String(Math.pow(1 - i / count, falloff)));\n g.appendChild(p);\n segs.push(p);\n }\n return segs;\n }\n\n var GLOW_SEGS = 20;\n var CORE_SEGS = 26;\n var glowLen = arcFrac * P;\n var coreLen = 0.34 * glowLen;\n\n var arcs = [\n {\n offset: 0,\n glow: buildSegments(\"nb-seg-a-glow\", GLOW_SEGS, 2.6),\n core: buildSegments(\"nb-seg-a-core\", CORE_SEGS, 1.4),\n },\n {\n // Half a lap behind: the diametrically opposite corner. The pairing\n // is most of why the effect reads as designed rather than random.\n offset: 2,\n glow: buildSegments(\"nb-seg-b-glow\", GLOW_SEGS, 2.6),\n core: buildSegments(\"nb-seg-b-core\", CORE_SEGS, 1.4),\n },\n ];\n\n // Three stacked bloom layers (soft / medium / wide) plus the filament.\n // `spread` widens the stroke before it is blurred — that pairing, not\n // the blur alone, is what makes the halo read as emitted light.\n var MAX_REACH = 36;\n var LAYERS = [\n { blur: 30, spread: 1, opacity: 0.18, stroke: accent, deep: true },\n { blur: 15, spread: 0.6, opacity: 0.3, stroke: accent, deep: true },\n { blur: 8, spread: 0.3, opacity: 0.5, stroke: accent, deep: true },\n { blur: 4, spread: 0.05, opacity: 0.9, stroke: hot, core: true },\n { blur: 0, spread: -0.012, opacity: 1, stroke: \"#ffffff\", core: true },\n ];\n\n var under = document.getElementById(\"nb-bloom-under\");\n var over = document.getElementById(\"nb-bloom-over\");\n [\"a\", \"b\"].forEach(function (tag) {\n LAYERS.forEach(function (layer) {\n var u = document.createElementNS(SVG_NS, \"use\");\n u.setAttribute(\"href\", \"#nb-seg-\" + tag + \"-\" + (layer.core ? \"core\" : \"glow\"));\n u.setAttribute(\"stroke\", layer.stroke);\n u.setAttribute(\n \"stroke-width\",\n String(Math.max(0.5, thickness + 2 * layer.spread * MAX_REACH * glow)),\n );\n u.setAttribute(\"opacity\", String(Math.min(1, layer.opacity * glow)));\n if (layer.blur > 0) u.style.filter = \"blur(\" + layer.blur + \"px)\";\n (layer.deep ? under : over).appendChild(u);\n });\n });\n\n /** Lay a segment run out behind `head`. Every value derives from head. */\n function layout(segs, head, length) {\n var step = length / segs.length;\n for (var i = 0; i < segs.length; i++) {\n var start = head - (i + 1) * step;\n start = ((start % P) + P) % P;\n segs[i].setAttribute(\"stroke-dasharray\", step + \" \" + (P - step));\n segs[i].setAttribute(\"stroke-dashoffset\", String(-start));\n }\n }\n\n function render(t) {\n for (var i = 0; i < arcs.length; i++) {\n var head = headAt(t + arcs[i].offset * beat);\n layout(arcs[i].glow, head, glowLen);\n layout(arcs[i].core, head, coreLen);\n }\n }\n\n // ---- timeline -------------------------------------------------------\n var wrap = document.getElementById(\"nb-wrap\");\n var panel = document.getElementById(\"nb-panel\");\n var nameEl = document.getElementById(\"nb-name\");\n var roleEl = document.getElementById(\"nb-role\");\n\n var tl = gsap.timeline({ paused: true });\n\n gsap.set(wrap, { opacity: 0, scale: 0.965, transformOrigin: \"0% 100%\" });\n gsap.set(nameEl, { y: 18, opacity: 0 });\n gsap.set(roleEl, { y: 12, opacity: 0 });\n\n // The driver: linear, spans the whole clip, and every arc position is\n // recomputed from its own time value. Seeking to frame N gives the same\n // state as playing to frame N because nothing carries over.\n var driver = { t: 0 };\n tl.to(\n driver,\n {\n t: DUR,\n duration: DUR,\n ease: \"none\",\n onUpdate: function () {\n render(driver.t);\n },\n },\n 0,\n );\n\n tl.to(wrap, { opacity: 1, scale: 1, duration: 0.55, ease: \"power3.out\" }, 0.08);\n tl.to(nameEl, { y: 0, opacity: 1, duration: 0.5, ease: \"power3.out\" }, 0.3);\n tl.to(roleEl, { y: 0, opacity: 1, duration: 0.5, ease: \"power3.out\" }, 0.44);\n\n tl.to(roleEl, { y: -10, opacity: 0, duration: 0.3, ease: \"power2.in\" }, 4.24);\n tl.to(nameEl, { y: -14, opacity: 0, duration: 0.32, ease: \"power2.in\" }, 4.3);\n tl.to(wrap, { opacity: 0, scale: 0.985, duration: 0.36, ease: \"power2.in\" }, 4.36);\n tl.set(panel, { visibility: \"hidden\" }, 4.76);\n\n render(0);\n\n window.__timelines[\"lt-neon-border\"] = tl;\n })();\n </script>\n </body>\n</html>\n"}