+ {{ partial (printf "template-partials/hero-animation/%s.html" $heroAnimation) . }}
+
+ {{ else if eq $heroVisual "code" }}
{{ partial "fingerprinted-img.html" (dict "src" $image "alt" $imageAlt "class" "w-full h-auto" "loading" "eager" "fetchpriority" "high" "style" ($imageStyle | safeCSS)) }}
@@ -218,7 +240,7 @@
@@ -240,7 +262,7 @@
{{ . | markdownify }}
{{ end }}
- {{ else if $videoYouTubeId }}
+ {{ else if eq $heroVisual "video" }}
- {{ else if $image }}
+ {{ else if eq $heroVisual "image" }}
{{ partial "fingerprinted-img.html" (dict "src" $image "alt" $imageAlt "class" "w-full h-auto rounded-lg" "loading" "eager" "fetchpriority" "high" "style" ($imageStyle | safeCSS) "maxWidth" 1500 "quality" 95) }}
diff --git a/theme/package.json b/theme/package.json
index c5551b0764b0..4b4d5b9a65b3 100644
--- a/theme/package.json
+++ b/theme/package.json
@@ -5,15 +5,16 @@
"private": true,
"license": "Apache-2.0",
"dependencies": {
- "@pulumi/design-tokens": "github:pulumi/pulumi-design-system#v0.6.1",
"@algolia/autocomplete-js": "^1.19.9",
"@algolia/autocomplete-plugin-tags": "^1.19.9",
"@algolia/autocomplete-theme-classic": "^1.19.9",
"@algolia/client-search": "^5.56.0",
"@growthbook/growthbook": "^1.6.5",
+ "@pulumi/design-tokens": "github:pulumi/pulumi-design-system#v0.6.1",
"@types/marked": "^4.0.8",
"algoliasearch": "^5.56.0",
"clipboard-polyfill": "^3.0.3",
+ "gsap": "^3.15.0",
"marked": "^4.3.0",
"search-insights": "^2.17.2"
},
@@ -31,6 +32,7 @@
"prettier": "2.8.8",
"sass": "^1.102.0",
"sass-loader": "^16.0.8",
+ "shiki": "^4.4.3",
"tailwindcss": "^4",
"terser-webpack-plugin": "^5.6.1",
"ts-loader": "^9.6.2",
@@ -45,6 +47,7 @@
"start": "yarn run concurrently 'yarn --cwd stencil run start' 'yarn run webpack --watch' --raw --kill-others",
"lint": "prettier --check .",
"lint-fix": "prettier --write .",
+ "gen:hero-text": "node scripts/gen-hero-text.mjs",
"test-support-form": "tsc -p tsconfig.test.json && node ../scripts/check-test-suites-compiled.js src/ts bin-test theme/tsconfig.test.json && node --test bin-test/*.test.js"
}
}
diff --git a/theme/scripts/gen-hero-text.mjs b/theme/scripts/gen-hero-text.mjs
new file mode 100644
index 000000000000..34600876165b
--- /dev/null
+++ b/theme/scripts/gen-hero-text.mjs
@@ -0,0 +1,190 @@
+// Regenerates the colored text runs in data/hero_agent_loop.yaml from the
+// plain-text sources at the top of that file, using shiki with the min-light
+// theme — each editor language's own grammar, bash for the terminal — the
+// same highlighting the design's storyboards used. Run with:
+//
+// yarn --cwd theme gen:hero-text
+//
+// shiki is a devDependency used only here; nothing from it ships in a bundle.
+
+import { createHighlighter } from "shiki";
+import fs from "fs";
+
+const FILE = new URL("../../data/hero_agent_loop.yaml", import.meta.url).pathname;
+const OUT_FILE = new URL("../../data/hero_agent_loop_generated.yaml", import.meta.url).pathname;
+
+const SHIKI_LANGS = {
+ typescript: "ts",
+ python: "python",
+ go: "go",
+ csharp: "csharp",
+ java: "java",
+ hcl: "hcl",
+ yaml: "yaml",
+};
+
+// Stable, readable keys for min-light's palette; anything new falls back to a
+// hex-derived key.
+const COLOR_KEYS = {
+ "#D32F2F": "kw",
+ "#1976D2": "id",
+ "#22863A": "str",
+ "#6F42C1": "mem",
+ "#24292E": "txt",
+ "#212121": "pun",
+ "#2B5581": "tbl",
+ "#C2C3C5": "dim",
+};
+const COLOR_NOTES = {
+ kw: "keywords",
+ id: "identifiers, numbers",
+ str: "strings",
+ mem: "members, methods; terminal commands",
+ txt: "plain text",
+ pun: "punctuation",
+ tbl: "terminal arguments, table text",
+ dim: "comments, faint punctuation",
+};
+
+function editorSources(src) {
+ const section = src.match(/^editor_sources:\n([\s\S]*?)(?=^\w)/m);
+ if (!section) {
+ throw new Error(`missing "editor_sources:" section in ${FILE}`);
+ }
+ const out = {};
+ const re = /^ (\w+): \|\n((?: .*\n|\n)*)/gm;
+ let m;
+ while ((m = re.exec(section[1]))) {
+ out[m[1]] = m[2].replace(/^ /gm, "").replace(/\n+$/, "");
+ }
+ return out;
+}
+
+function terminalSource(src) {
+ const m = src.match(/^terminal_source: \|\n((?: .*\n|\n)*)/m);
+ if (!m) {
+ throw new Error(`missing "terminal_source: |" block in ${FILE}`);
+ }
+ return m[1].replace(/^ /gm, "").replace(/\n+$/, "");
+}
+
+function normalizeColor(color) {
+ return (color || "#24292E").toUpperCase().replace(/FF$/, "");
+}
+
+function toRuns(highlighter, code, lang, palette) {
+ const { tokens } = highlighter.codeToTokens(code, { lang, theme: "min-light" });
+ const lines = tokens.map(line => {
+ const chars = [];
+ for (const token of line) {
+ for (const ch of token.content) {
+ chars.push({ ch, color: ch === " " ? null : normalizeColor(token.color) });
+ }
+ }
+ while (chars.length && chars[chars.length - 1].ch === " ") {
+ chars.pop();
+ }
+ // Segments are [colorKey, startColumn, text] and never begin with a
+ // space or contain runs of 2+ spaces: hugo --minify collapses
+ // whitespace inside the tspans, so indentation and column alignment
+ // must live in explicit column offsets, with only single interior
+ // spaces (which the minifier preserves) inside a segment.
+ const runs = [];
+ let i = 0;
+ while (i < chars.length) {
+ if (chars[i].ch === " ") {
+ i++;
+ continue;
+ }
+ const color = chars[i].color;
+ const start = i;
+ let text = "";
+ let j = i;
+ while (j < chars.length) {
+ const c = chars[j];
+ if (c.ch !== " ") {
+ if (c.color !== color) {
+ break;
+ }
+ text += c.ch;
+ j++;
+ } else if (j + 1 < chars.length && chars[j + 1].ch !== " " && chars[j + 1].color === color) {
+ text += " ";
+ j++;
+ } else {
+ break;
+ }
+ }
+ let key = COLOR_KEYS[color];
+ if (!key) {
+ key = "c" + color.slice(1);
+ COLOR_KEYS[color] = key;
+ }
+ palette[key] = color;
+ runs.push([key, start, text]);
+ i = j;
+ }
+ return runs;
+ });
+ while (lines.length && !lines[lines.length - 1].length) {
+ lines.pop();
+ }
+ return lines;
+}
+
+function yamlLines(lines, indent) {
+ const buf = [];
+ for (const line of lines) {
+ if (!line.length) {
+ buf.push(`${indent}- []`);
+ continue;
+ }
+ buf.push(`${indent}- - [${line[0][0]}, ${line[0][1]}, ${JSON.stringify(line[0][2])}]`);
+ for (const item of line.slice(1)) {
+ buf.push(`${indent} - [${item[0]}, ${item[1]}, ${JSON.stringify(item[2])}]`);
+ }
+ }
+ return buf.join("\n");
+}
+
+const src = fs.readFileSync(FILE, "utf8");
+const sources = editorSources(src);
+const terminal = terminalSource(src);
+
+const highlighter = await createHighlighter({
+ themes: ["min-light"],
+ langs: Object.keys(SHIKI_LANGS)
+ .map(l => SHIKI_LANGS[l])
+ .concat(["bash"]),
+});
+
+const palette = {};
+const editorSections = [];
+for (const lang of Object.keys(sources)) {
+ const shikiLang = SHIKI_LANGS[lang];
+ if (!shikiLang) {
+ throw new Error(`no shiki lang mapping for "${lang}"`);
+ }
+ const lines = toRuns(highlighter, sources[lang], shikiLang, palette);
+ if (lines.length > 13) {
+ throw new Error(`${lang} example is ${lines.length} rows; the code panel fits 13`);
+ }
+ editorSections.push(` ${lang}:\n` + yamlLines(lines, " "));
+}
+const terminalRuns = toRuns(highlighter, terminal, "bash", palette);
+
+const colors = ["colors:"].concat(Object.keys(palette).map(key => ` ${key}: "${palette[key]}"${COLOR_NOTES[key] ? " # " + COLOR_NOTES[key] : ""}`)).join("\n");
+
+const HEADER =
+ "# GENERATED FILE — DO NOT EDIT.\n" +
+ "# Written by theme/scripts/gen-hero-text.mjs from the plain-text sources in\n" +
+ "# data/hero_agent_loop.yaml; edit those and run\n" +
+ "# yarn --cwd theme gen:hero-text\n" +
+ "#\n" +
+ "# Runs are [color, startColumn, text] segments; _code-lines.html positions\n" +
+ "# each from its column offset (Monaspace Neon, 7.44141px advance at\n" +
+ "# font-size 12). Segment text carries no leading or doubled spaces, since\n" +
+ "# the production build's HTML minifier collapses whitespace inside tspans.\n";
+
+fs.writeFileSync(OUT_FILE, HEADER + "\n" + colors + "\n\neditors:\n" + editorSections.join("\n") + "\n\nterminal:\n" + yamlLines(terminalRuns, " ") + "\n");
+console.log(`wrote ${OUT_FILE}: ${Object.keys(sources).length} editors, ${terminalRuns.length} terminal rows, ${Object.keys(palette).length} colors`);
diff --git a/theme/src/scss/_marketing.scss b/theme/src/scss/_marketing.scss
index b74f193c50e4..05256b19e0fe 100644
--- a/theme/src/scss/_marketing.scss
+++ b/theme/src/scss/_marketing.scss
@@ -22,6 +22,10 @@
// pricing, reinvent, releases). Ships in main.scss too; both bundles need it.
@import "shared/badge";
+ // Homepage animated hero. Lives in the marketing bundle (the homepage loads
+ // it) so it stays out of bundle.css.
+ @import "marketing/hero-animation";
+
body {
@apply overflow-x-hidden;
diff --git a/theme/src/scss/marketing/_hero-animation.scss b/theme/src/scss/marketing/_hero-animation.scss
new file mode 100644
index 000000000000..c2b0747e5204
--- /dev/null
+++ b/theme/src/scss/marketing/_hero-animation.scss
@@ -0,0 +1,16 @@
+.hero-animation {
+ width: 100%;
+ max-width: 620px;
+
+ svg {
+ display: block;
+ width: 100%;
+ height: auto;
+ }
+ :is(rect, path, circle)[stroke]:not([data-a-stroke], [data-badge-rect]) {
+ vector-effect: non-scaling-stroke;
+ }
+}
+.hero-animation.hal-pending .hal-scene {
+ visibility: hidden;
+}
diff --git a/theme/src/ts/hero-animation.ts b/theme/src/ts/hero-animation.ts
new file mode 100644
index 000000000000..7cbdf2f4dcbf
--- /dev/null
+++ b/theme/src/ts/hero-animation.ts
@@ -0,0 +1,819 @@
+import { gsap } from "gsap";
+
+const CW = 7.44141;
+const CW_PROMPT = 13.5 * (CW / 12) - 0.675;
+
+const PROMPT_TEXT_X = 213;
+const CODE_TEXT_X = 122;
+
+const HALO_PAD = 6;
+const HALO_GROW = 12.8;
+const HALO_RX = 19.5;
+const PANEL_FILL = { x: 112, y: 157, width: 520, height: 257, rx: 16 };
+const PANEL_STROKE = { x: 112.5, y: 157.5, width: 519, height: 256, rx: 15.5 };
+const PANEL_OUTER = { x: 104.5, y: 149.5, width: 535, height: 272, rx: 21.5 };
+const PILL_BOTTOM = 521.5;
+const CI_ROW_RIDE = 62;
+const SLOT_REDISTRIBUTE = 10;
+
+const PERCH = { x: 371.5, bottom: 136.89 };
+const GLYPH_AT_TAB = -30;
+const GLYPH_AT_PLATE = -70;
+
+const AGENT_WEIGHTS: { [agent: string]: number } = {
+ "claude-code": 66,
+ "codex": 15,
+ "cursor": 8,
+ "copilot": 3.5,
+ "neo": 3.5,
+ "opencode": 4,
+};
+
+// Per-pass language odds: real usage share, with brand-new HCL boosted to
+// just above YAML and Java at the floor.
+const LANG_WEIGHTS: { [lang: string]: number } = {
+ typescript: 40,
+ python: 30,
+ go: 10,
+ csharp: 10,
+ hcl: 7,
+ yaml: 2,
+ java: 1,
+};
+
+// Unselected agent and language logos recede to violet-400 when the choice
+// lands; everything is authored (and reset to) the full violet-700.
+const LOGO_FILL = "#5A30C5";
+const LOGO_DIM_FILL = "#9077F3";
+
+// Where the chosen language's icon parks in the code panel: tucked into the
+// top-right corner and kept small so a long second code line never runs
+// underneath it (the storyboard's 23.4px icon at 596,168 sat lower and
+// larger).
+const PANEL_LANG_CENTER = { x: 615.5, y: 172.5 };
+const PANEL_LANG_SIZE = 15;
+
+// The language pill's flow layout (storyboard frame 1): labels at font-size 10
+// with 0.05em tracking, centered in the pill with a constant gap; the chosen
+// item grows an 18px icon prefix and a ring with asymmetric padding.
+const LANG_ADV = 10 * (CW / 12) + 0.5;
+const LANG_PILL = { x: 203, width: 338, gap: 18.6, minPad: 10, restPad: 22 };
+const LANG_ICON = { size: 18, gap: 7, centerY: 426 };
+const LANG_RING_PAD = { left: 10.5, right: 9.5 };
+
+// The write beat always takes the same wall time; per-character speed adapts
+// to the chosen language's example. Bursts (blank-line groups in the source)
+// pause for a fixed number of character-units.
+const TYPE_SECONDS = 1.5;
+const TYPE_PAUSE_UNITS = 50;
+const TYPE_CHUNK_MIN = 5;
+const TYPE_CHUNK_MAX = 13;
+
+const DASH_HIDDEN = { "stroke-dasharray": "1 2", "stroke-dashoffset": "1.5" };
+
+const CI_PR_REST_X = -10;
+
+const CUBE_H = 84.752;
+const TERM_SCROLL_END = -862;
+const TERM_FOLD_Y = 410;
+
+function q
(root: Element, sel: string): T {
+ return root.querySelector(sel) as T;
+}
+
+function qa(root: Element, sel: string): T[] {
+ return Array.prototype.slice.call(root.querySelectorAll(sel));
+}
+
+function init(): void {
+ const root = document.getElementById("hero-agent-loop");
+ if (!root) {
+ return;
+ }
+
+ const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
+ if (reduceMotion.matches) {
+ root.classList.remove("hal-pending");
+ return;
+ }
+
+ const tiles = qa(root, "[data-tile]");
+ const tileRects = qa(root, "[data-tile-rect]");
+ const tileLogos = qa(root, "[data-tile-logo]");
+ const prompt = q(root, "[data-prompt]");
+ const promptClip = q(root, "[data-prompt-clip]");
+ const promptCaret = q(root, "[data-prompt-caret]");
+
+ const aFill = q(root, "[data-a-fill]");
+ const aStroke = q(root, "[data-a-stroke]");
+ const aOuter = q(root, "[data-a-outer]");
+ const bFill = q(root, "[data-b-fill]");
+ const bStroke = q(root, "[data-b-stroke]");
+ const bOuter = q(root, "[data-b-outer]");
+
+ const glyph = q(root, "[data-glyph]");
+ const glyphFloat = q(root, "[data-glyph-f]");
+ const glyphStatic = q(root, "[data-glyph-static]");
+ if (glyphStatic && glyphStatic.parentNode) {
+ glyphStatic.parentNode.removeChild(glyphStatic);
+ }
+
+ const codeLines = qa(root, "[data-code-line]");
+ const lineClips = qa(root, "[data-lc]");
+ const editorGroups = qa(root, "[data-editor-lang]");
+ const caret = q(root, "[data-caret]");
+
+ const langRow = q(root, "[data-lang-row]");
+ const langShorts = qa(root, "[data-lang-short]");
+ const langFulls = qa(root, "[data-lang-full]");
+ const langRing = q(root, "[data-lang-ring]");
+ const langIcons = q(root, "[data-lang-icons]");
+ const langLogos = qa(root, "[data-lang-logo]");
+ const panelLang = q(root, "[data-panel-lang]");
+
+ const termClip = q(root, "[data-term-clip]");
+ const termScroll = q(root, "[data-term-scroll]");
+ const termLines = qa(root, "[data-term-line]");
+ const tab = q(root, "[data-tab]");
+
+ const ciRow = q(root, "[data-ci-row]");
+ const ciPr = q(root, "[data-ci-pr]");
+ const ciMerge = q(root, "[data-ci-merge]");
+ const ciSlots = qa(root, "[data-ci-slot]");
+ const ciSpins = qa(root, "[data-ci-spin]");
+ const ciArcs = qa(root, "[data-ci-arc]");
+ const ciChecks = qa(root, "[data-ci-check]");
+ const slotX = ciSlots.map(s => {
+ const m = (s.getAttribute("transform") || "").match(/translate\(([-\d.]+)/);
+ return m ? parseFloat(m[1]) : 0;
+ });
+
+ const badgeTests = q(root, "[data-badge-tests]");
+ const badgePolicy = q(root, "[data-badge-policy]");
+ const badgeRects = qa(root, "[data-badge-rect]");
+ const badgeBodies = qa(root, "[data-badge-body]");
+
+ const policyRow = q(root, "[data-policy-row]");
+ const polShield = q(root, "[data-pol-shield]");
+ const polStacks = qa(root, "[data-pol-stack]");
+ const polFiles = qa(root, "[data-pol-file]");
+ const polChecks = qa(root, "[data-pol-check]");
+
+ const diagram = q(root, "[data-diagram]");
+ const plate = q(root, "[data-plate]");
+ const plateFill = q(root, "[data-plate-fill]");
+ const plateDetail = q(root, "[data-plate-detail]");
+ const cubes = qa(root, "[data-cube]");
+ const cubeLabels = qa(root, "[data-cube-label]");
+
+ let chosen = 0;
+ const perch = { x: 0, y: 0 };
+
+ const tileWeights = tiles.map(tile => AGENT_WEIGHTS[tile.getAttribute("data-agent") || ""] || 0);
+
+ function pickWeighted(weights: number[]): number {
+ let sum = 0;
+ weights.forEach(w => (sum += w));
+ let r = Math.random() * sum;
+ for (let i = 0; i < weights.length; i++) {
+ r -= weights[i];
+ if (r < 0) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
+ function cellFill(): { x: number; y: number; width: number; height: number; rx: number } {
+ const r = tileRects[chosen];
+ return {
+ x: parseFloat(r.getAttribute("x") || "0"),
+ y: parseFloat(r.getAttribute("y") || "0"),
+ width: parseFloat(r.getAttribute("width") || "0"),
+ height: parseFloat(r.getAttribute("height") || "0"),
+ rx: 16,
+ };
+ }
+
+ function cellHalo(): { x: number; y: number; width: number; height: number; rx: number } {
+ const c = cellFill();
+ return { x: c.x - HALO_PAD, y: c.y - HALO_PAD, width: c.width + HALO_GROW, height: c.height + HALO_GROW, rx: HALO_RX };
+ }
+
+ const loopProxies: Array<{ obj: any; initial: any }> = [];
+
+ function trackProxy(obj: T): T {
+ loopProxies.push({ obj: obj, initial: Object.assign({}, obj) });
+ return obj;
+ }
+
+ function adoptProtagonist(): void {
+ glyphFloat.appendChild(tileLogos[chosen]);
+ gsap.set(tiles[chosen], { autoAlpha: 0 });
+ gsap.set([aFill, glyph], { autoAlpha: 1 });
+ }
+
+ // ------------------------------------------------------------------
+ // The language. A uniform-random pick each pass; the chosen label gains
+ // the selection ring, expands to its full name (where one exists), grows
+ // its icon, and the icon travels to the code panel's corner while the
+ // matching editor example types in.
+ // ------------------------------------------------------------------
+ let chosenLang = 0;
+ const langNames = langShorts.map(t => t.getAttribute("data-language") || "");
+ const langWeights = langNames.map(name => LANG_WEIGHTS[name] || 0);
+ const langShortW = langShorts.map(t => (t.textContent || "").length * LANG_ADV);
+ const langFullByName: { [name: string]: SVGTextElement } = {};
+ langFulls.forEach(t => {
+ langFullByName[t.getAttribute("data-language") || ""] = t;
+ });
+
+ const agentPaths = tileLogos.map(g => qa(g, "path"));
+
+ function dimUnselected(paths: SVGPathElement[][], keep: number): void {
+ const targets: SVGPathElement[] = [];
+ paths.forEach((ps, i) => {
+ if (i !== keep) {
+ targets.push.apply(targets, ps);
+ }
+ });
+ gsap.to(targets, { attr: { fill: LOGO_DIM_FILL }, duration: 0.35 });
+ }
+
+ function chosenFull(): SVGTextElement | null {
+ return langFullByName[langNames[chosenLang]] || null;
+ }
+
+ // Pill flow layout: labels centered with a constant gap; the chosen item
+ // takes an icon prefix and (if it has one) its full name. restX/selX are
+ // the label positions before and after selection.
+ const restX: number[] = [];
+ const selX: number[] = [];
+ const ringBox = { x: 0, w: 0 };
+ const iconRow = { x: 0, y: LANG_ICON.centerY };
+
+ function layoutLangRow(): void {
+ const fullEl = chosenFull();
+ const fullW = fullEl ? (fullEl.textContent || "").length * LANG_ADV : langShortW[chosenLang];
+ const selWidths = langShortW.slice();
+ selWidths[chosenLang] = LANG_ICON.size + LANG_ICON.gap + fullW;
+ const place = (ws: number[], out: number[]) => {
+ let total = -LANG_PILL.gap;
+ ws.forEach(w => (total += w + LANG_PILL.gap));
+ let x = LANG_PILL.x + Math.max(LANG_PILL.minPad, (LANG_PILL.width - total) / 2);
+ for (let i = 0; i < ws.length; i++) {
+ out[i] = x;
+ x += ws[i] + LANG_PILL.gap;
+ }
+ };
+ // At rest the labels are justified across the pill; selection gathers
+ // them into the tighter centered flow the storyboard shows.
+ let restTotal = 0;
+ langShortW.forEach(w => (restTotal += w));
+ const restGap = (LANG_PILL.width - 2 * LANG_PILL.restPad - restTotal) / (langShortW.length - 1);
+ let rx = LANG_PILL.x + LANG_PILL.restPad;
+ for (let i = 0; i < langShortW.length; i++) {
+ restX[i] = rx;
+ rx += langShortW[i] + restGap;
+ }
+ const itemX: number[] = [];
+ place(selWidths, itemX);
+ for (let i = 0; i < itemX.length; i++) {
+ selX[i] = itemX[i];
+ }
+ selX[chosenLang] = itemX[chosenLang] + LANG_ICON.size + LANG_ICON.gap;
+ ringBox.x = itemX[chosenLang] - LANG_RING_PAD.left;
+ ringBox.w = selWidths[chosenLang] + LANG_RING_PAD.left + LANG_RING_PAD.right;
+ iconRow.x = itemX[chosenLang] + LANG_ICON.size / 2;
+ }
+
+ // The chosen language's icon is positioned by one manual transform
+ // (translate + scale about the origin, placing its native bbox center),
+ // so GSAP transforms must never touch panelLang — only its opacity.
+ const iconT = { x: 0, y: 0, k: 1 };
+ const iconCenter = { x: 0, y: 0 };
+ let panelK = 1;
+ let iconPop: any = null;
+
+ function applyIconT(): void {
+ const tx = iconT.x - iconT.k * iconCenter.x;
+ const ty = iconT.y - iconT.k * iconCenter.y;
+ panelLang.setAttribute("transform", "translate(" + tx + " " + ty + ") scale(" + iconT.k + ")");
+ }
+
+ function selectLanguage(): void {
+ const fullEl = chosenFull();
+ if (fullEl) {
+ gsap.to(langShorts[chosenLang], { autoAlpha: 0, duration: 0.2 });
+ gsap.fromTo(fullEl, { autoAlpha: 0 }, { autoAlpha: 1, duration: 0.3, delay: 0.08 });
+ }
+ gsap.to(
+ langShorts.filter((_t, i) => i !== chosenLang),
+ { attr: { fill: LOGO_DIM_FILL }, duration: 0.35 },
+ );
+ }
+
+ function adoptLanguage(): void {
+ panelLang.appendChild(langLogos[chosenLang]);
+ const kEnd = iconT.k;
+ iconT.k = kEnd * 0.7;
+ applyIconT();
+ iconPop = gsap.to(iconT, { k: kEnd, duration: 0.35, ease: "back.out(1.7)", onUpdate: applyIconT });
+ gsap.fromTo(panelLang, { autoAlpha: 0 }, { autoAlpha: 1, duration: 0.25 });
+ }
+
+ // Typing state for the active language, rebuilt each pass: per-line
+ // character counts plus pause segments between blank-line bursts, mapped
+ // onto one 0..1 progress tween.
+ type TypeSegment = { line: number; startU: number; units: number; chunks: number[] };
+ let typeSegments: TypeSegment[] = [];
+ let typeTotal = 1;
+ let activeEditor: SVGGElement | null = null;
+ let activeLines: SVGTextElement[] = [];
+ let activeClips: SVGRectElement[] = [];
+
+ function prepareTyping(): void {
+ const lang = langNames[chosenLang];
+ activeEditor = editorGroups.filter(g => g.getAttribute("data-editor-lang") === lang)[0];
+ activeLines = qa(activeEditor, "[data-code-line]");
+ activeClips = qa(root as HTMLElement, '[data-lc="' + lang + '"]');
+ typeSegments = [];
+ let u = 0;
+ let prevRow = -1;
+ for (let i = 0; i < activeLines.length; i++) {
+ const row = Math.round((parseFloat(activeClips[i].getAttribute("y") || "166") - 166) / 18);
+ if (prevRow >= 0 && row - prevRow > 1) {
+ u += TYPE_PAUSE_UNITS;
+ }
+ const chars = parseInt(activeLines[i].getAttribute("data-chars") || "0", 10);
+ // Text lands in chunks, like tokens streaming, rather than
+ // character by character; boundaries are re-rolled each pass.
+ const chunks: number[] = [];
+ let c = 0;
+ while (c < chars) {
+ c = Math.min(chars, c + TYPE_CHUNK_MIN + Math.floor(Math.random() * (TYPE_CHUNK_MAX - TYPE_CHUNK_MIN + 1)));
+ chunks.push(c);
+ }
+ typeSegments.push({ line: i, startU: u, units: chars, chunks: chunks });
+ u += chars;
+ prevRow = row;
+ }
+ typeTotal = Math.max(1, u);
+ }
+
+ function renderTyping(p: number): void {
+ const u = p * typeTotal;
+ let caretClip: SVGRectElement | null = null;
+ let caretChars = 0;
+ for (let i = 0; i < typeSegments.length; i++) {
+ const seg = typeSegments[i];
+ const clip = activeClips[seg.line];
+ if (u >= seg.startU + seg.units) {
+ clip.setAttribute("width", String(seg.units * CW + 2));
+ caretClip = clip;
+ caretChars = seg.units;
+ } else if (u > seg.startU) {
+ const raw = u - seg.startU;
+ let c = 0;
+ for (let k = 0; k < seg.chunks.length; k++) {
+ if (raw >= seg.chunks[k]) {
+ c = seg.chunks[k];
+ } else {
+ break;
+ }
+ }
+ clip.setAttribute("width", String(c * CW + 2));
+ caretClip = clip;
+ caretChars = c;
+ } else {
+ break;
+ }
+ }
+ if (caretClip) {
+ caret.setAttribute("x", String(CODE_TEXT_X + caretChars * CW));
+ caret.setAttribute("y", caretClip.getAttribute("y") || "166");
+ }
+ }
+
+ function reset(): void {
+ for (let i = 0; i < tileLogos.length; i++) {
+ if (tileLogos[i].parentNode !== tiles[i]) {
+ tiles[i].appendChild(tileLogos[i]);
+ }
+ }
+ for (let i = 0; i < langLogos.length; i++) {
+ if (langLogos[i].parentNode !== langIcons) {
+ langIcons.appendChild(langLogos[i]);
+ }
+ }
+ chosen = pickWeighted(tileWeights);
+ const bb = tileLogos[chosen].getBBox();
+ perch.x = PERCH.x - (bb.x + bb.width / 2);
+ perch.y = PERCH.bottom - (bb.y + bb.height);
+
+ chosenLang = pickWeighted(langWeights);
+ layoutLangRow();
+ if (iconPop) {
+ iconPop.kill();
+ iconPop = null;
+ }
+ const lb = langLogos[chosenLang].getBBox();
+ const lSize = Math.max(lb.width, lb.height);
+ iconCenter.x = lb.x + lb.width / 2;
+ iconCenter.y = lb.y + lb.height / 2;
+ panelK = PANEL_LANG_SIZE / lSize;
+ iconT.x = iconRow.x;
+ iconT.y = iconRow.y;
+ iconT.k = LANG_ICON.size / lSize;
+ applyIconT();
+ prepareTyping();
+
+ const allAgentPaths: SVGPathElement[] = [];
+ agentPaths.forEach(ps => {
+ allAgentPaths.push.apply(allAgentPaths, ps);
+ });
+ gsap.killTweensOf(allAgentPaths);
+ gsap.set(allAgentPaths, { attr: { fill: LOGO_FILL } });
+
+ const allLabels = (langShorts as SVGTextElement[]).concat(langFulls);
+ gsap.killTweensOf(allLabels);
+ gsap.set(langShorts, { autoAlpha: 1, attr: { fill: LOGO_FILL } });
+ langShorts.forEach((t, i) => gsap.set(t, { x: restX[i] }));
+ gsap.set(langFulls, { autoAlpha: 0 });
+ const fullEl = chosenFull();
+ if (fullEl) {
+ gsap.set(fullEl, { x: selX[chosenLang] });
+ }
+
+ gsap.set(langRow, { autoAlpha: 0, y: 16 });
+ gsap.set(langRing, { autoAlpha: 0, attr: { x: ringBox.x, width: ringBox.w } });
+ gsap.set(panelLang, { autoAlpha: 0 });
+
+ gsap.set(editorGroups, { autoAlpha: 0 });
+ if (activeEditor) {
+ gsap.set(activeEditor, { autoAlpha: 1 });
+ }
+
+ gsap.set(tiles, { autoAlpha: 0, scale: 0.9, transformOrigin: "50% 50%" });
+ gsap.set(prompt, { autoAlpha: 0, scale: 0.96, transformOrigin: "50% 50%" });
+ gsap.set(promptClip, { attr: { width: 0 } });
+ gsap.set(promptCaret, { opacity: 0, attr: { x: PROMPT_TEXT_X } });
+
+ const cf = cellFill();
+ gsap.set(aFill, { autoAlpha: 0, scale: 1, attr: cf });
+ gsap.set(aStroke, {
+ autoAlpha: 0,
+ attr: {
+ "x": cf.x,
+ "y": cf.y,
+ "width": cf.width,
+ "height": cf.height,
+ "rx": cf.rx,
+ "stroke-dasharray": DASH_HIDDEN["stroke-dasharray"],
+ "stroke-dashoffset": DASH_HIDDEN["stroke-dashoffset"],
+ },
+ });
+ gsap.set(aOuter, { autoAlpha: 0, attr: cellHalo() });
+
+ gsap.set(glyph, { x: 0, y: 0, autoAlpha: 0 });
+
+ for (let i = 0; i < loopProxies.length; i++) {
+ Object.assign(loopProxies[i].obj, loopProxies[i].initial);
+ }
+
+ gsap.set(lineClips, { attr: { width: 0 } });
+ gsap.set(codeLines, { y: 0, opacity: 1 });
+ gsap.set(caret, { opacity: 0 });
+
+ gsap.set(bOuter, { autoAlpha: 0, attr: { x: PANEL_OUTER.x, y: PANEL_OUTER.y, width: PANEL_OUTER.width, height: 0, rx: PANEL_OUTER.rx } });
+ gsap.set(bFill, { autoAlpha: 0, attr: { x: PANEL_FILL.x, y: PANEL_FILL.y, width: PANEL_FILL.width, height: 0, rx: PANEL_FILL.rx } });
+ gsap.set(bStroke, {
+ autoAlpha: 0,
+ attr: {
+ "x": PANEL_STROKE.x,
+ "y": PANEL_STROKE.y,
+ "width": PANEL_STROKE.width,
+ "height": 0,
+ "rx": PANEL_STROKE.rx,
+ "stroke-dasharray": "none",
+ "stroke-dashoffset": "0",
+ },
+ });
+ gsap.set(tab, { y: 34 });
+ gsap.set(termClip, { opacity: 0 });
+ gsap.set(termScroll, { y: 0 });
+ gsap.set(termLines, { opacity: 0 });
+
+ gsap.set(ciRow, { autoAlpha: 1, y: -CI_ROW_RIDE });
+ gsap.set(badgeTests, { autoAlpha: 1, y: -CI_ROW_RIDE });
+ gsap.set(badgePolicy, { autoAlpha: 1 });
+ ciSlots.forEach((slot, i) => gsap.set(slot, { x: slotX[i] + SLOT_REDISTRIBUTE }));
+ gsap.set(ciPr, { autoAlpha: 0, scale: 1, transformOrigin: "50% 50%" });
+ gsap.set(ciMerge, { autoAlpha: 0 });
+ gsap.set(ciSpins, { autoAlpha: 0, scale: 0.6, transformOrigin: "50% 50%" });
+ gsap.set(ciChecks, { autoAlpha: 0 });
+
+ gsap.set(badgeRects, { attr: DASH_HIDDEN });
+ gsap.set(badgeBodies, { autoAlpha: 0 });
+
+ gsap.set(policyRow, { autoAlpha: 1 });
+ gsap.set(polShield, { autoAlpha: 0, x: -12 });
+ gsap.set(polStacks, { autoAlpha: 0, scale: 0.85, transformOrigin: "50% 50%" });
+ gsap.set(polFiles, { opacity: 0.5, attr: { fill: "#1F1B21" } });
+ gsap.set(polChecks, { attr: { fill: "#F49709" }, scale: 1, transformOrigin: "50% 50%" });
+
+ gsap.set(diagram, { autoAlpha: 1, scale: 1, svgOrigin: "372 240" });
+ gsap.set(plate, { autoAlpha: 0, scale: 0.85, svgOrigin: "372 260" });
+ gsap.set(plateDetail, { autoAlpha: 0 });
+ gsap.set(cubes, { autoAlpha: 0 });
+ gsap.set(qa(root as HTMLElement, "[data-cube-top]"), { y: CUBE_H });
+ gsap.set(qa(root as HTMLElement, "[data-cube-edge]"), { scaleY: 0.001, transformOrigin: "50% 100%" });
+ gsap.set(cubeLabels, { autoAlpha: 0 });
+ }
+
+ const ambient: any[] = [];
+
+ ambient.push(gsap.to(glyphFloat, { y: -2, duration: 1.5, ease: "sine.inOut", yoyo: true, repeat: -1 }));
+ ambient.push(gsap.to(plateFill, { opacity: 0.55, duration: 2, ease: "sine.inOut", yoyo: true, repeat: -1 }));
+
+ const SPIN_PHASE = [0, 137, 244, 71];
+ const spin = { a: 0 };
+ ambient.push(
+ gsap.to(spin, {
+ a: 360,
+ duration: 1,
+ ease: "none",
+ repeat: -1,
+ onUpdate: () => {
+ for (let i = 0; i < ciArcs.length; i++) {
+ ciArcs[i].setAttribute("transform", "rotate(" + (spin.a + SPIN_PHASE[i % SPIN_PHASE.length]) + ")");
+ }
+ },
+ }),
+ );
+
+ const caretBlink = gsap.to(caret, { opacity: 0, duration: 0.45, ease: "steps(1)", yoyo: true, repeat: -1, paused: true });
+ const promptBlink = gsap.to(promptCaret, { opacity: 0, duration: 0.45, ease: "steps(1)", yoyo: true, repeat: -1, paused: true });
+
+ function blink(tween: any, target: SVGRectElement, on: boolean): void {
+ if (on) {
+ tween.play(0);
+ } else {
+ tween.pause(0);
+ gsap.set(target, { opacity: 1 });
+ }
+ }
+
+ reset();
+ const tl = gsap.timeline({ repeat: 0, paused: true, repeatRefresh: true, onRepeat: reset });
+
+ const gridDelays = [0.12, 0, 0.18, 0.24, 0.06, 0.3];
+ tiles.forEach((tile, i) => {
+ tl.to(tile, { autoAlpha: 1, scale: 1, duration: 0.35, ease: "back.out(1.4)" }, gridDelays[i]);
+ });
+
+ tl.to(prompt, { autoAlpha: 1, scale: 1, duration: 0.3, ease: "power2.out" }, 0.5);
+ const promptChars = 40;
+ const promptType = trackProxy({ c: 0 });
+ tl.call(() => gsap.set(promptCaret, { opacity: 1 }), undefined, 0.75);
+ tl.to(
+ promptType,
+ {
+ c: promptChars,
+ duration: promptChars * 0.026,
+ ease: "none",
+ snap: { c: 1 },
+ onUpdate: () => {
+ const w = promptType.c * CW_PROMPT;
+ promptClip.setAttribute("width", String(w));
+ promptCaret.setAttribute("x", String(PROMPT_TEXT_X + w + 1));
+ },
+ },
+ 0.75,
+ );
+ tl.call(() => blink(promptBlink, promptCaret, true), undefined, 1.8);
+
+ tl.call(() => gsap.to(tiles[chosen], { scale: 0.965, duration: 0.12, yoyo: true, repeat: 1, ease: "power1.inOut", transformOrigin: "50% 50%" }), undefined, 1.8);
+ tl.set(aStroke, { autoAlpha: 1 }, 1.95);
+ tl.to(aStroke, { attr: { "stroke-dashoffset": "0" }, duration: 0.35, ease: "power1.inOut" }, 1.95);
+ tl.call(() => dimUnselected(agentPaths, chosen), undefined, 2.0);
+ tl.set(aStroke, { attr: { "stroke-dasharray": "none" } }, 2.32);
+ tl.fromTo(
+ aOuter,
+ {
+ autoAlpha: 0,
+ attr: { x: () => cellFill().x, y: () => cellFill().y, width: () => cellFill().width, height: () => cellFill().height, rx: 16 },
+ },
+ {
+ autoAlpha: 1,
+ attr: { x: () => cellHalo().x, y: () => cellHalo().y, width: () => cellHalo().width, height: () => cellHalo().height, rx: HALO_RX },
+ duration: 0.3,
+ ease: "power2.out",
+ immediateRender: false,
+ },
+ 2.1,
+ );
+
+ tl.fromTo(langRow, { autoAlpha: 0, y: 16 }, { autoAlpha: 1, y: 0, duration: 0.4, ease: "power2.out", immediateRender: false }, 0.45);
+ // The reflow is proxy-driven: per-target function values proved unreliable
+ // under repeatRefresh on later loops, while proxies rewound by reset()
+ // re-read the current pass's layout on every update.
+ const slideT = trackProxy({ p: 0 });
+ tl.to(
+ slideT,
+ {
+ p: 1,
+ duration: 0.4,
+ ease: "power2.inOut",
+ onUpdate: () => {
+ for (let i = 0; i < langShorts.length; i++) {
+ gsap.set(langShorts[i], { x: restX[i] + (selX[i] - restX[i]) * slideT.p });
+ }
+ },
+ },
+ 2.45,
+ );
+ tl.call(selectLanguage, undefined, 2.45);
+ tl.fromTo(
+ langRing,
+ { autoAlpha: 0, attr: { x: () => ringBox.x + 5, y: 413, width: () => ringBox.w - 10, height: 26, rx: 13 } },
+ {
+ autoAlpha: 1,
+ attr: { x: () => ringBox.x, y: 410.5, width: () => ringBox.w, height: 31, rx: 15.5 },
+ duration: 0.3,
+ ease: "power2.out",
+ immediateRender: false,
+ },
+ 2.55,
+ );
+ tl.call(adoptLanguage, undefined, 2.55);
+
+ tl.call(adoptProtagonist, undefined, 3.5);
+ tl.call(() => blink(promptBlink, promptCaret, false), undefined, 3.51);
+ tl.to([tiles[5], tiles[3], tiles[2], tiles[4], tiles[1], tiles[0], prompt], { autoAlpha: 0, scale: 0.85, duration: 0.2, stagger: 0.04, ease: "power2.in" }, 3.5);
+ tl.to(langRow, { autoAlpha: 0, duration: 0.25 }, 3.65);
+ tl.to(iconT, { x: PANEL_LANG_CENTER.x, y: PANEL_LANG_CENTER.y, k: () => panelK, duration: 0.7, ease: "power2.inOut", onUpdate: applyIconT }, 3.65);
+ tl.to(aFill, { attr: PANEL_FILL, duration: 0.7, ease: "power2.inOut" }, 3.65);
+ tl.to(aStroke, { attr: PANEL_STROKE, duration: 0.7, ease: "power2.inOut" }, 3.65);
+ tl.to(aOuter, { attr: PANEL_OUTER, duration: 0.7, ease: "power2.inOut" }, 3.65);
+ tl.to(glyph, { x: () => perch.x, duration: 0.7, ease: "power2.inOut" }, 3.65);
+ tl.to(glyph, { y: () => perch.y, duration: 0.75, ease: "power2.out" }, 3.6);
+
+ const TYPE_START = 4.45;
+ const typeProxy = trackProxy({ p: 0 });
+ tl.call(
+ () => {
+ blink(caretBlink, caret, false);
+ gsap.set(caret, { opacity: 1, attr: { x: CODE_TEXT_X, y: activeClips.length ? (activeClips[0].getAttribute("y") as string) : "166" } });
+ },
+ undefined,
+ TYPE_START - 0.01,
+ );
+ tl.to(typeProxy, { p: 1, duration: TYPE_SECONDS, ease: "none", onUpdate: () => renderTyping(typeProxy.p) }, TYPE_START);
+ tl.call(() => blink(caretBlink, caret, true), undefined, TYPE_START + TYPE_SECONDS + 0.01);
+ const doneWriting = TYPE_START + TYPE_SECONDS + 0.35;
+
+ tl.to(aStroke, { autoAlpha: 0, duration: 0.25 }, doneWriting);
+ tl.call(() => blink(caretBlink, caret, false), undefined, doneWriting - 0.02);
+ tl.call(() => gsap.to(caret, { opacity: 0, duration: 0.15 }), undefined, doneWriting);
+ tl.to(aOuter, { attr: { height: 310 }, duration: 0.45, ease: "power2.out" }, doneWriting + 0.05);
+
+ const ciAt = doneWriting + 0.45;
+ tl.fromTo(ciPr, { autoAlpha: 0, x: CI_PR_REST_X - 16 }, { autoAlpha: 1, x: CI_PR_REST_X, duration: 0.35, ease: "power2.out" }, ciAt);
+ tl.to(ciSpins, { autoAlpha: 1, scale: 1, duration: 0.3, stagger: 0.06, ease: "back.out(1.7)" }, ciAt + 0.15);
+
+ const flipsAt = ciAt + 0.75;
+ ciSlots.forEach((_slot, i) => {
+ const at = flipsAt + i * 0.35;
+ tl.to(ciSpins[i], { autoAlpha: 0, duration: 0.15 }, at);
+ tl.fromTo(ciChecks[i], { autoAlpha: 0, scale: 0.6, transformOrigin: "50% 50%" }, { autoAlpha: 1, scale: 1, duration: 0.35, ease: "back.out(1.7)" }, at + 0.05);
+ });
+ const mergedAt = flipsAt + 4 * 0.35 + 0.1;
+ tl.to(ciPr, { autoAlpha: 0, scale: 0.6, transformOrigin: "50% 50%", duration: 0.16, ease: "power2.in" }, mergedAt);
+ tl.fromTo(ciMerge, { autoAlpha: 0, scale: 0.5, transformOrigin: "50% 50%" }, { autoAlpha: 1, scale: 1, duration: 0.32, ease: "back.out(1.7)" }, mergedAt + 0.1);
+
+ const testsBadgeAt = mergedAt + 0.35;
+ tl.to(badgeRects[0], { attr: { "stroke-dashoffset": "0" }, duration: 0.4, ease: "power1.inOut" }, testsBadgeAt);
+ tl.set(badgeRects[0], { attr: { "stroke-dasharray": "none" } }, testsBadgeAt + 0.45);
+ tl.to(badgeBodies[0], { autoAlpha: 1, duration: 0.3 }, testsBadgeAt + 0.15);
+
+ const rollA = testsBadgeAt + 0.75;
+ // The wipe targets whichever language's lines are active this pass, so it
+ // spawns from a callback rather than a fixed-target timeline child.
+ tl.call(() => gsap.to(activeLines, { y: -22, opacity: 0, duration: 0.2, stagger: 0.02, ease: "power1.in" }), undefined, rollA - 0.15);
+ tl.to(panelLang, { autoAlpha: 0, duration: 0.3 }, rollA);
+ tl.to(aFill, { autoAlpha: 0, duration: 0.35 }, rollA);
+ const shellA = trackProxy({ top: PANEL_OUTER.y, h: 310, rx: 21.5 });
+ tl.to(
+ shellA,
+ {
+ top: 479.5,
+ h: 42,
+ rx: 21,
+ duration: 0.8,
+ ease: "power2.inOut",
+ onUpdate: () => {
+ aOuter.setAttribute("y", String(shellA.top));
+ aOuter.setAttribute("height", String(shellA.h));
+ aOuter.setAttribute("rx", String(shellA.rx));
+ const dy = shellA.top + shellA.h - PILL_BOTTOM;
+ gsap.set([ciRow, badgeTests], { y: dy });
+ const k = -dy / CI_ROW_RIDE;
+ for (let i = 0; i < ciSlots.length; i++) {
+ gsap.set(ciSlots[i], { x: slotX[i] + SLOT_REDISTRIBUTE * k });
+ }
+ },
+ },
+ rollA,
+ );
+
+ const unfoldAt = rollA + 0.85;
+ tl.set([bOuter, bFill, bStroke], { autoAlpha: 1 }, unfoldAt);
+ tl.to(bOuter, { attr: { height: 319 }, duration: 0.7, ease: "power3.out" }, unfoldAt);
+ tl.to(bFill, { attr: { height: 257 }, duration: 0.7, ease: "power3.out" }, unfoldAt);
+ tl.to(bStroke, { attr: { height: 256 }, duration: 0.7, ease: "power3.out" }, unfoldAt);
+ tl.to(tab, { y: 0, duration: 0.35, ease: "power2.out" }, unfoldAt + 0.1);
+ tl.to(glyph, { y: () => perch.y + GLYPH_AT_TAB, duration: 0.35, ease: "power2.out" }, unfoldAt + 0.16);
+ tl.set(termClip, { opacity: 1 }, unfoldAt + 0.1);
+
+ const streamAt = unfoldAt + 0.55;
+ const aboveFold = termLines.filter(line => parseFloat(line.getAttribute("y") || "0") <= TERM_FOLD_Y);
+ const belowFold = termLines.filter(line => parseFloat(line.getAttribute("y") || "0") > TERM_FOLD_Y);
+ tl.to(aboveFold, { opacity: 1, duration: 0.04, stagger: 0.028 }, streamAt);
+ tl.set(belowFold, { opacity: 1 }, streamAt + 0.35);
+
+ const polAt = streamAt + 0.45;
+ tl.to(polShield, { autoAlpha: 1, x: 0, duration: 0.3, ease: "power2.out" }, polAt);
+ tl.to(polStacks, { autoAlpha: 1, scale: 1, duration: 0.3, stagger: 0.06, ease: "back.out(1.4)" }, polAt + 0.1);
+
+ const polFlipsAt = polAt + 1.0;
+ polStacks.forEach((_stack, i) => {
+ const at = polFlipsAt + i * 0.45;
+ tl.to(polFiles[i], { opacity: 1, attr: { fill: "#21C45D" }, duration: 0.3 }, at);
+ tl.fromTo(polChecks[i], { scale: 0.6, transformOrigin: "50% 50%" }, { scale: 1, duration: 0.35, ease: "back.out(1.7)" }, at);
+ tl.to(polChecks[i], { attr: { fill: "#1C7D41" }, duration: 0.25 }, at);
+ });
+
+ const scrollAt = polFlipsAt + 0.35;
+ tl.to(termScroll, { y: TERM_SCROLL_END, duration: 1.7, ease: "power1.inOut" }, scrollAt);
+
+ const packsAt = scrollAt + 2.1;
+ tl.to(badgeRects[1], { attr: { "stroke-dashoffset": "0" }, duration: 0.4, ease: "power1.inOut" }, packsAt);
+ tl.set(badgeRects[1], { attr: { "stroke-dasharray": "none" } }, packsAt + 0.45);
+ tl.to(badgeBodies[1], { autoAlpha: 1, duration: 0.3 }, packsAt + 0.15);
+
+ const rollB = packsAt + 0.9;
+ tl.to(termClip, { opacity: 0, duration: 0.25 }, rollB - 0.1);
+ tl.to(tab, { y: 34, duration: 0.3, ease: "power2.in" }, rollB - 0.1);
+ tl.to([bFill, bStroke], { autoAlpha: 0, duration: 0.3 }, rollB);
+ tl.to(bOuter, { attr: { y: 421.5, height: 42, rx: 21 }, duration: 0.8, ease: "power2.inOut" }, rollB);
+
+ const plateAt = rollB + 0.9;
+ tl.to(plate, { autoAlpha: 1, scale: 1, duration: 0.6, ease: "power3.out" }, plateAt);
+ tl.to(plateDetail, { autoAlpha: 1, duration: 0.4 }, plateAt + 0.15);
+ tl.to(glyph, { y: () => perch.y + GLYPH_AT_PLATE, duration: 0.5, ease: "power2.out" }, plateAt + 0.05);
+
+ const cubesAt = plateAt + 0.6;
+ cubes.forEach((cube, i) => {
+ const at = cubesAt + parseInt(cube.getAttribute("data-order") || String(i), 10) * 0.15;
+ tl.to(cube, { autoAlpha: 1, duration: 0.2 }, at);
+ tl.to(qa(cube, "[data-cube-top]"), { y: 0, duration: 0.55, ease: "back.out(1.2)" }, at + 0.1);
+ tl.to(qa(cube, "[data-cube-edge]"), { scaleY: 1, duration: 0.55, ease: "back.out(1.2)" }, at + 0.1);
+ tl.to(cubeLabels[i], { autoAlpha: 1, duration: 0.25 }, at + 0.5);
+ });
+
+ const outAt = cubesAt + 1.1 + 2.0;
+ tl.to(root, { autoAlpha: 1, duration: 0.3 }, outAt + 0.5);
+
+ root.classList.remove("hal-pending");
+
+ let inView = true;
+ function updatePlayState(): void {
+ const running = inView && document.visibilityState !== "hidden";
+ if (running) {
+ tl.play();
+ ambient.forEach(t => t.play());
+ } else {
+ tl.pause();
+ ambient.forEach(t => t.pause());
+ }
+ }
+
+ if ("IntersectionObserver" in window) {
+ new IntersectionObserver(
+ entries => {
+ inView = entries[0].isIntersecting;
+ updatePlayState();
+ },
+ { threshold: 0.05 },
+ ).observe(root);
+ }
+ document.addEventListener("visibilitychange", updatePlayState);
+ updatePlayState();
+}
+
+if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", init);
+} else {
+ init();
+}
diff --git a/theme/webpack.config.js b/theme/webpack.config.js
index 35b4512b8972..314c243237c0 100644
--- a/theme/webpack.config.js
+++ b/theme/webpack.config.js
@@ -18,6 +18,7 @@ module.exports = function (env, argv = {}) {
"algolia": "./src/ts/algolia-entry.ts",
"consent-manager": "./src/ts/consent-manager/index.ts",
"header-nav": "./src/ts/header-nav.ts",
+ "hero-animation": "./src/ts/hero-animation.ts",
},
output: {
filename: "[name].[contenthash:8].js",
diff --git a/theme/yarn.lock b/theme/yarn.lock
index 1da10a80e9ba..798b12b99548 100644
--- a/theme/yarn.lock
+++ b/theme/yarn.lock
@@ -415,6 +415,70 @@
version "0.6.1"
resolved "https://codeload.github.com/pulumi/pulumi-design-system/tar.gz/6f2c4dcea050f07b40c7c48d289dd87b3fef140a"
+"@shikijs/core@4.4.3":
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-4.4.3.tgz#00a942fa45ad0e4146ac6dbbac32b8b704b42e3f"
+ integrity sha512-QCR4q2ZO/ILJEuwiBMel4wdcTDb1JGwfjKTxPDF6x8ixOaluPrVqIn06C99AcRPhmYlBR56d/Fb+GN58GzExpg==
+ dependencies:
+ "@shikijs/primitive" "4.4.3"
+ "@shikijs/types" "4.4.3"
+ "@shikijs/vscode-textmate" "^10.0.2"
+ "@types/hast" "^3.0.5"
+ hast-util-to-html "^9.0.5"
+
+"@shikijs/engine-javascript@4.4.3":
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/@shikijs/engine-javascript/-/engine-javascript-4.4.3.tgz#42dbdc18ec2f86003624674839a8f090cdd7cb62"
+ integrity sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ==
+ dependencies:
+ "@shikijs/types" "4.4.3"
+ "@shikijs/vscode-textmate" "^10.0.2"
+ oniguruma-to-es "^4.3.6"
+
+"@shikijs/engine-oniguruma@4.4.3":
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-4.4.3.tgz#a1754f9f42e0f35a55cda9a977599041a2ad5b07"
+ integrity sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w==
+ dependencies:
+ "@shikijs/types" "4.4.3"
+ "@shikijs/vscode-textmate" "^10.0.2"
+
+"@shikijs/langs@4.4.3":
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/@shikijs/langs/-/langs-4.4.3.tgz#113282396f119dbba8d3b5e86668258fa8df6e7b"
+ integrity sha512-ePic0yfAJGOF83D5wBHK/00EjK65oahBYxFk5epgq33WRv7X9UuxLEV8PtR0szC0z8dl7INIpIodB99JRFlR+A==
+ dependencies:
+ "@shikijs/types" "4.4.3"
+
+"@shikijs/primitive@4.4.3":
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/@shikijs/primitive/-/primitive-4.4.3.tgz#86490cea63b3e2c56b8d9163046e010258844d81"
+ integrity sha512-m0wBeLDQDeIxRdUmrCPdQqfuUamDwRL5isCfYbguKD6NiaKpVbsv+3J81DyIKgNW5h4WAIIr8T4EkgQrBBxvaQ==
+ dependencies:
+ "@shikijs/types" "4.4.3"
+ "@shikijs/vscode-textmate" "^10.0.2"
+ "@types/hast" "^3.0.5"
+
+"@shikijs/themes@4.4.3":
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/@shikijs/themes/-/themes-4.4.3.tgz#8310a78261f4cf742663e07e2028df046a02bd72"
+ integrity sha512-w8UHjeUnIR965KMWJHUPXOc2mNJUnK3vpVLYLvw5IYU2mnTTJ89E24OrJDBNiJDQ0qzb0tc4l7mrIXx5cFeIyw==
+ dependencies:
+ "@shikijs/types" "4.4.3"
+
+"@shikijs/types@4.4.3":
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-4.4.3.tgz#019aff19f0cbfb21642c59f6f8432ced74e27b45"
+ integrity sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g==
+ dependencies:
+ "@shikijs/vscode-textmate" "^10.0.2"
+ "@types/hast" "^3.0.5"
+
+"@shikijs/vscode-textmate@^10.0.2":
+ version "10.0.2"
+ resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz#a90ab31d0cc1dfb54c66a69e515bf624fa7b2224"
+ integrity sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==
+
"@tailwindcss/node@4.3.3":
version "4.3.3"
resolved "https://registry.yarnpkg.com/@tailwindcss/node/-/node-4.3.3.tgz#38ff04309ff036ea3589a7bad9069c44ec9d3883"
@@ -543,6 +607,13 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
+"@types/hast@^3.0.0", "@types/hast@^3.0.5":
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.5.tgz#48020de4c0e63492f4ca9db42068c108f68b7f8f"
+ integrity sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==
+ dependencies:
+ "@types/unist" "*"
+
"@types/jquery@^3.5.34":
version "3.5.34"
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.34.tgz#c1993eaac0db03cf9db974976dd8f07bbf7c5708"
@@ -560,6 +631,13 @@
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.3.2.tgz#e2e0ad02ebf5626bd215c5bae2aff6aff0ce9eac"
integrity sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w==
+"@types/mdast@^4.0.0":
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6"
+ integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==
+ dependencies:
+ "@types/unist" "*"
+
"@types/node@*":
version "25.0.10"
resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.10.tgz#4864459c3c9459376b8b75fd051315071c8213e7"
@@ -572,6 +650,16 @@
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.10.tgz#277a542aff6776d8a9b15f2ac682a663e3e94bbd"
integrity sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==
+"@types/unist@*", "@types/unist@^3.0.0":
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c"
+ integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==
+
+"@ungap/structured-clone@^1.0.0":
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.3.tgz#094041e1a4cb1987f038335421281ac8be390bcc"
+ integrity sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==
+
"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1":
version "1.14.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6"
@@ -859,6 +947,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001806:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz#1bc8e502b723fa393455dfbedd5ccec0c29bb74e"
integrity sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==
+ccount@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5"
+ integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==
+
chalk@^4.1.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
@@ -867,6 +960,16 @@ chalk@^4.1.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+character-entities-html4@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b"
+ integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==
+
+character-entities-legacy@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b"
+ integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==
+
chokidar@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-5.0.0.tgz#949c126a9238a80792be9a0265934f098af369a5"
@@ -931,6 +1034,11 @@ combined-stream@^1.0.8:
dependencies:
delayed-stream "~1.0.0"
+comma-separated-tokens@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee"
+ integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
+
commander@^12.1.0:
version "12.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3"
@@ -1123,11 +1231,23 @@ delayed-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
+dequal@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
+ integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
+
detect-libc@^2.0.3:
version "2.1.2"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad"
integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==
+devlop@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018"
+ integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==
+ dependencies:
+ dequal "^2.0.0"
+
dom-mutator@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/dom-mutator/-/dom-mutator-0.6.0.tgz#079d7a4b3e8981a562cd777548b99baab51d65c5"
@@ -1372,6 +1492,11 @@ graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
+gsap@^3.15.0:
+ version "3.15.0"
+ resolved "https://registry.yarnpkg.com/gsap/-/gsap-3.15.0.tgz#7851baaffc77642f2db3b1749d3634f9b5a19d14"
+ integrity sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==
+
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
@@ -1403,6 +1528,30 @@ hasown@^2.0.4:
dependencies:
function-bind "^1.1.2"
+hast-util-to-html@^9.0.5:
+ version "9.0.5"
+ resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz#ccc673a55bb8e85775b08ac28380f72d47167005"
+ integrity sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/unist" "^3.0.0"
+ ccount "^2.0.0"
+ comma-separated-tokens "^2.0.0"
+ hast-util-whitespace "^3.0.0"
+ html-void-elements "^3.0.0"
+ mdast-util-to-hast "^13.0.0"
+ property-information "^7.0.0"
+ space-separated-tokens "^2.0.0"
+ stringify-entities "^4.0.0"
+ zwitch "^2.0.4"
+
+hast-util-whitespace@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621"
+ integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+
htm@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/htm/-/htm-3.1.1.tgz#49266582be0dc66ed2235d5ea892307cc0c24b78"
@@ -1415,6 +1564,11 @@ html-encoding-sniffer@^4.0.0:
dependencies:
whatwg-encoding "^3.1.1"
+html-void-elements@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7"
+ integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==
+
http-proxy-agent@^7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e"
@@ -1721,6 +1875,21 @@ math-intrinsics@^1.1.0:
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
+mdast-util-to-hast@^13.0.0:
+ version "13.2.1"
+ resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz#d7ff84ca499a57e2c060ae67548ad950e689a053"
+ integrity sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ "@ungap/structured-clone" "^1.0.0"
+ devlop "^1.0.0"
+ micromark-util-sanitize-uri "^2.0.0"
+ trim-lines "^3.0.0"
+ unist-util-position "^5.0.0"
+ unist-util-visit "^5.0.0"
+ vfile "^6.0.0"
+
mdn-data@2.0.14:
version "2.0.14"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
@@ -1731,6 +1900,38 @@ merge-stream@^2.0.0:
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+micromark-util-character@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6"
+ integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==
+ dependencies:
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-encode@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8"
+ integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==
+
+micromark-util-sanitize-uri@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7"
+ integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-encode "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-symbol@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8"
+ integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==
+
+micromark-util-types@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e"
+ integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==
+
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
@@ -1808,6 +2009,20 @@ nwsapi@^2.2.12:
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.24.tgz#f8927043d4c9b516abdebe804a32c8d1f9484d1f"
integrity sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==
+oniguruma-parser@^0.12.2:
+ version "0.12.2"
+ resolved "https://registry.yarnpkg.com/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz#e27ca446f7fcf0969662a3ab9b4f43176d62b139"
+ integrity sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==
+
+oniguruma-to-es@^4.3.6:
+ version "4.3.6"
+ resolved "https://registry.yarnpkg.com/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz#43e640280241b0d687a314e7a641d476407a1c4d"
+ integrity sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==
+ dependencies:
+ oniguruma-parser "^0.12.2"
+ regex "^6.1.0"
+ regex-recursion "^6.0.2"
+
p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
@@ -2162,6 +2377,11 @@ prettier@2.8.8:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
+property-information@^7.0.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.2.0.tgz#0809b34264e995c0bfcd3227028a1e35210af80a"
+ integrity sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==
+
punycode@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
@@ -2179,6 +2399,25 @@ rechoir@^0.8.0:
dependencies:
resolve "^1.20.0"
+regex-recursion@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/regex-recursion/-/regex-recursion-6.0.2.tgz#a0b1977a74c87f073377b938dbedfab2ea582b33"
+ integrity sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==
+ dependencies:
+ regex-utilities "^2.3.0"
+
+regex-utilities@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/regex-utilities/-/regex-utilities-2.3.0.tgz#87163512a15dce2908cf079c8960d5158ff43280"
+ integrity sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==
+
+regex@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/regex/-/regex-6.1.0.tgz#d7ce98f8ee32da7497c13f6601fca2bc4a6a7803"
+ integrity sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==
+ dependencies:
+ regex-utilities "^2.3.0"
+
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@@ -2306,6 +2545,20 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+shiki@^4.4.3:
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/shiki/-/shiki-4.4.3.tgz#31fb41c5c82435779a0b5a9b92a3b0377b061e15"
+ integrity sha512-Mb/GvXPHBAXdgGIcnfU5L3ldpn1XcxrGkPHwqgRx17/I2XRfqlFKk2vGkHWINn1kdXvzJZeuO3is6I9KLPFm0g==
+ dependencies:
+ "@shikijs/core" "4.4.3"
+ "@shikijs/engine-javascript" "4.4.3"
+ "@shikijs/engine-oniguruma" "4.4.3"
+ "@shikijs/langs" "4.4.3"
+ "@shikijs/themes" "4.4.3"
+ "@shikijs/types" "4.4.3"
+ "@shikijs/vscode-textmate" "^10.0.2"
+ "@types/hast" "^3.0.5"
+
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
@@ -2329,6 +2582,11 @@ source-map@^0.7.4:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.6.tgz#a3658ab87e5b6429c8a1f3ba0083d4c61ca3ef02"
integrity sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==
+space-separated-tokens@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f"
+ integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==
+
spawn-command@^0.0.2-1:
version "0.0.2"
resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e"
@@ -2348,6 +2606,14 @@ string-width@^4.1.0, string-width@^4.2.0:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
+stringify-entities@^4.0.0:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.4.tgz#b3b79ef5f277cc4ac73caeb0236c5ba939b3a4f3"
+ integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==
+ dependencies:
+ character-entities-html4 "^2.0.0"
+ character-entities-legacy "^3.0.0"
+
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
@@ -2461,6 +2727,11 @@ tree-kill@^1.2.2:
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
+trim-lines@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338"
+ integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==
+
ts-loader@^9.6.2:
version "9.6.2"
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.6.2.tgz#a08f4935ddb87edbd58ce33b7b2558c2a77fdd47"
@@ -2490,6 +2761,44 @@ undici-types@~7.16.0:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46"
integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==
+unist-util-is@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.1.tgz#d0a3f86f2dd0db7acd7d8c2478080b5c67f9c6a9"
+ integrity sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-position@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4"
+ integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-stringify-position@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2"
+ integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-visit-parents@^6.0.0:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz#777df7fb98652ce16b4b7cd999d0a1a40efa3a02"
+ integrity sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-is "^6.0.0"
+
+unist-util-visit@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.1.0.tgz#9a2a28b0aa76a15e0da70a08a5863a2f060e2468"
+ integrity sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-is "^6.0.0"
+ unist-util-visit-parents "^6.0.0"
+
update-browserslist-db@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d"
@@ -2503,6 +2812,22 @@ util-deprecate@^1.0.2:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+vfile-message@^4.0.0:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.3.tgz#87b44dddd7b70f0641c2e3ed0864ba73e2ea8df4"
+ integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-stringify-position "^4.0.0"
+
+vfile@^6.0.0:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab"
+ integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ vfile-message "^4.0.0"
+
w3c-xmlserializer@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c"
@@ -2669,3 +2994,8 @@ yargs@^16.2.0:
string-width "^4.2.0"
y18n "^5.0.5"
yargs-parser "^20.2.2"
+
+zwitch@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"
+ integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==