app
Circuit Trace Generators
Public Made by Adomby adom
Two dependency-free, seeded circuit-background generators: a seamless torus tile with ambient or progressive reveal, and the v9 continuous field with obstacles, scroll reveal, and five ambient systems.
master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428
import { createConductorAnimation } from "./circuit-animation-conductor.js?v=1";
import { createHandshakeAnimation } from "./circuit-animation-handshake.js?v=2";
import { createTraceFlowReveal } from "./circuit-reveal-flow.js?v=1";
import {
createScrollTraceReveal,
SCROLL_REVEAL_DEFAULTS,
TRACE_SPAWN_PRESETS,
} from "./circuit-reveal-scroll.js?v=1";
import { createStreamingCircuitField } from "./circuit-field-stream.js?v=1";
import { createStreamingCircuitFieldWorker } from "./circuit-field-stream-client.js?v=1";
import {
createStreamingTraceReveal,
STREAM_REVEAL_DEFAULTS,
} from "./circuit-reveal-stream.js?v=1";
import {
createReflectionAnimation,
REFLECTION_DEFAULTS,
} from "./circuit-animation-reflection.js?v=1";
import {
createCircuitBackground as createLegacyCircuitBackground,
DEFAULTS as GEOMETRY_DEFAULTS,
ANIMATION_PRESETS as LEGACY_ANIMATION_PRESETS,
generateCircuitField,
pathData,
} from "./circuit-background-v4.js?geometry=1";
const SVG_NS = "http://www.w3.org/2000/svg";
const DEFAULTS = Object.freeze({
...GEOMETRY_DEFAULTS,
animationSystem: "none",
motion: false,
revealSystem: "instant",
revealMotion: false,
revealPower: 0.72,
revealDuration: 7600,
revealGlow: 0.82,
...SCROLL_REVEAL_DEFAULTS,
traceDuration: STREAM_REVEAL_DEFAULTS.traceDuration,
revealLaunchRate: STREAM_REVEAL_DEFAULTS.revealLaunchRate,
scrollSpeedResponse: 0.6,
maxActiveTraces: STREAM_REVEAL_DEFAULTS.maxActiveTraces,
unlockLead: 0.025,
continuousGeneration: false,
generationAheadScreens: 2,
generationChunkScreens: 0.42,
generationExtendScreens: 2,
liveliness: 0,
atmosphereStrength: 0,
handshakeInterval: 1650,
handshakeSpeed: 230,
handshakeLength: 48,
maxHandshakes: 3,
...REFLECTION_DEFAULTS,
});
const ANIMATION_PRESETS = Object.freeze({
none: LEGACY_ANIMATION_PRESETS.none,
conductor: LEGACY_ANIMATION_PRESETS.conductor,
routed: LEGACY_ANIMATION_PRESETS.adom,
handshake: Object.freeze({
animationSystem: "handshake",
motion: true,
liveliness: 0.72,
handshakeInterval: 1650,
handshakeSpeed: 230,
handshakeLength: 48,
maxHandshakes: 3,
atmosphereStrength: 0,
}),
reflection: Object.freeze({
animationSystem: "reflection",
motion: true,
liveliness: 0.68,
reflectionStrength: 0.62,
reflectionCycle: 14800,
reflectionWidth: 0.3,
reflectionAngle: 18,
reflectionDrift: 6,
reflectionRest: 0.12,
reflectionQuiet: 0.84,
reflectionPointer: true,
atmosphereStrength: 0.14,
}),
});
const REVEAL_PRESETS = Object.freeze({
instant: Object.freeze({
revealSystem: "instant",
revealMotion: false,
}),
flow: Object.freeze({
revealSystem: "flow",
revealMotion: true,
revealPower: 0.72,
revealDuration: 7600,
revealGlow: 0.88,
spawnMode: "flow",
spawnAngle: 90,
spawnSwirl: 0.075,
revealMaxActiveTraces: 48,
}),
scroll: Object.freeze({
revealSystem: "scroll",
revealMotion: true,
revealPower: 0.78,
...TRACE_SPAWN_PRESETS.downstream,
traceWindow: 0.095,
revealScrollLead: 0.74,
revealGlow: 0.82,
traceDuration: 950,
revealLaunchRate: 24,
maxActiveTraces: 72,
scrollSpeedResponse: 0.6,
unlockLead: 0.025,
continuousGeneration: true,
generationAheadScreens: 2,
}),
});
let instanceCount = 0;
function hashSeed(value) {
let hash = 2166136261;
for (const char of String(value)) {
hash ^= char.charCodeAt(0);
hash = Math.imul(hash, 16777619);
}
return hash >>> 0;
}
function mulberry32(seed) {
return function random() {
let value = (seed += 0x6d2b79f5);
value = Math.imul(value ^ (value >>> 15), value | 1);
value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
return ((value ^ (value >>> 14)) >>> 0) / 4294967296;
};
}
function svgElement(name, attributes = {}) {
const element = document.createElementNS(SVG_NS, name);
for (const [key, value] of Object.entries(attributes)) element.setAttribute(key, String(value));
return element;
}
function createModernCircuitBackground(mount, options = {}) {
if (!(mount instanceof Element)) throw new TypeError("mount must be a DOM element");
const id = `circuit-field-${++instanceCount}`;
let config = { ...DEFAULTS, ...options };
let field;
let svg;
let conductor;
let handshake;
let reflection;
let flowReveal;
let scrollReveal;
let resizeObserver;
let resizeTimer;
let pointer = null;
let lastSize = "";
const interactionTarget = mount.parentElement || mount;
const readingZone = interactionTarget.querySelector?.("[data-circuit-reading-zone]") || null;
const motionPreference = window.matchMedia("(prefers-reduced-motion: reduce)");
let reducedMotion = motionPreference.matches;
const originalPosition = mount.style.position;
const originalOverflow = mount.style.overflow;
if (getComputedStyle(mount).position === "static") mount.style.position = "relative";
mount.style.overflow = "hidden";
function life() {
return Math.max(0, Math.min(1.5, config.liveliness));
}
function featureScale() {
return Math.max(0.25, config.featureScale);
}
function addVia(group, point, role) {
const scale = featureScale();
const circles = [
svgElement("circle", {
cx: point[0], cy: point[1], r: config.viaOuterRadius * scale, fill: config.traceColor,
}),
svgElement("circle", {
cx: point[0], cy: point[1], r: config.viaInnerRadius * scale,
fill: "var(--circuit-hole, #0d1117)",
}),
];
if (!role) {
group.append(...circles);
return circles;
}
const via = svgElement("g", { "data-trace-via": role });
via.append(...circles);
group.append(via);
return via;
}
function syncAmbientRevealClip(progress = null) {
const bottom = progress === null ? null : Math.max(0, 100 - progress * 100);
for (const layer of mount.querySelectorAll("[data-animation]")) {
if (layer.getAttribute("data-animation") === "scroll-trace-reveal") continue;
if (bottom === null) layer.style.removeProperty("clip-path");
else layer.style.clipPath = "inset(0 0 " + bottom + "% 0)";
}
}
function destroyAnimationSystems() {
conductor?.destroy();
handshake?.destroy();
reflection?.destroy();
conductor = undefined;
handshake = undefined;
reflection = undefined;
}
function destroyRevealSystems() {
syncAmbientRevealClip(null);
flowReveal?.destroy();
scrollReveal?.destroy();
flowReveal = undefined;
scrollReveal = undefined;
}
function createAnimationSystems() {
const animationNamespace = config.animationSystem === "conductor"
? "signals"
: config.animationSystem === "handshake"
? "handshakes"
: config.animationSystem;
const animationRandom = mulberry32(hashSeed([config.seed, lastSize, animationNamespace].join(":")));
if (config.animationSystem === "conductor") {
conductor = createConductorAnimation(svg, field, config, animationRandom);
} else if (
config.animationSystem === "handshake"
&& config.motion
&& !reducedMotion
&& life() > 0.02
) {
handshake = createHandshakeAnimation(mount, field, config, animationRandom, { readingZone });
} else if (config.animationSystem === "reflection") {
reflection = createReflectionAnimation(mount, field, config, animationRandom, { readingZone });
}
}
function build(force = false) {
const pixelWidth = Math.max(1, mount.clientWidth);
const pixelHeight = Math.max(1, mount.clientHeight);
const worldWidth = pixelWidth / config.scale;
const worldHeight = pixelHeight / config.scale;
const pitch = config.pitch * featureScale();
const size = `${Math.floor(worldWidth / pitch)}x${Math.floor(worldHeight / pitch)}`;
if (!force && size === lastSize) return false;
lastSize = size;
destroyAnimationSystems();
destroyRevealSystems();
svg?.remove();
field = generateCircuitField({ ...config, width: worldWidth, height: worldHeight });
const revealRandom = mulberry32(hashSeed(`${config.seed}:${size}:reveal:${config.revealSystem}`));
svg = svgElement("svg", {
id,
"aria-hidden": true,
focusable: false,
viewBox: `0 0 ${field.width} ${field.height}`,
preserveAspectRatio: "none",
});
Object.assign(svg.style, {
position: "absolute",
inset: "0",
width: "100%",
height: "100%",
display: "block",
pointerEvents: "none",
});
const traceLayer = svgElement("g", { "data-static-traces": "" });
const flowMarkup = config.revealSystem === "flow";
for (const [index, trace] of field.traces.entries()) {
const tracePath = svgElement("path", {
...(flowMarkup ? { "data-trace-path": "" } : {}),
d: pathData(trace.points),
fill: "none",
stroke: config.traceColor,
"stroke-width": config.traceWidth * config.featureScale,
"stroke-linecap": "butt",
"stroke-linejoin": "miter",
});
if (!flowMarkup) {
traceLayer.append(tracePath);
if (trace.viaStart) addVia(traceLayer, trace.points[0]);
if (trace.viaEnd) addVia(traceLayer, trace.points.at(-1));
continue;
}
const traceGroup = svgElement("g", { "data-circuit-trace": index });
traceGroup.append(tracePath);
if (trace.viaStart) addVia(traceGroup, trace.points[0], "start");
if (trace.viaEnd) addVia(traceGroup, trace.points.at(-1), "end");
traceLayer.append(traceGroup);
}
svg.append(traceLayer);
mount.prepend(svg);
createAnimationSystems();
if (config.revealSystem === "flow") {
flowReveal = createTraceFlowReveal(svg, field, config, revealRandom, {
traceLayer,
onComplete: () => startMotion(),
});
} else if (config.revealSystem === "scroll") {
const revealLead = Math.max(0.25, Math.min(0.95, Number(config.revealScrollLead) || 0.74));
scrollReveal = createScrollTraceReveal(svg, field, config, {
traceLayer,
rangeElement: mount,
scrollRoot: window,
progressSource: config.progressSource || "scroll",
spawnMode: config.spawnMode,
spawnAngle: config.spawnAngle,
spawnSwirl: config.spawnSwirl,
spawnOriginX: config.spawnOriginX,
spawnOriginY: config.spawnOriginY,
traceWindow: config.traceWindow,
unlockLead: config.unlockLead,
maxActiveTraces: config.maxActiveTraces,
traceDuration: config.traceDuration,
revealLaunchRate: config.revealLaunchRate,
scrollSpeedResponse: config.scrollSpeedResponse,
revealGlow: config.revealGlow,
startOffset: Number.isFinite(Number(config.revealStartOffset))
? Number(config.revealStartOffset)
: -window.innerHeight * revealLead,
endOffset: config.endOffset,
once: config.once !== false,
autoStart: false,
onProgress: (progress) => syncAmbientRevealClip(progress),
});
}
return true;
}
function onPointerMove(event) {
if (!field) return;
const rect = mount.getBoundingClientRect();
pointer = {
x: (event.clientX - rect.left) / rect.width * field.width,
y: (event.clientY - rect.top) / rect.height * field.height,
at: performance.now(),
};
conductor?.pointerMoved(pointer);
handshake?.pointerMoved(pointer);
reflection?.pointerMoved(pointer);
}
function onPointerLeave() {
if (pointer) pointer.at = performance.now() - 3000;
handshake?.pointerLeft();
reflection?.pointerLeft();
}
function onClick(event) {
onPointerMove(event);
if (config.animationSystem === "conductor") conductor?.clicked(pointer);
else if (config.animationSystem === "handshake") handshake?.clicked(pointer);
else if (config.animationSystem === "reflection") reflection?.clicked(pointer);
}
function stopMotion() {
conductor?.stop();
handshake?.stop();
reflection?.stop();
}
function startMotion() {
stopMotion();
if (config.animationSystem === "none" || !config.motion || reducedMotion || life() <= 0.02) return;
if (flowReveal?.stats?.running) return;
if (config.animationSystem === "conductor") conductor?.start();
else if (config.animationSystem === "handshake") handshake?.start();
else if (config.animationSystem === "reflection") reflection?.start();
}
function stopReveal() {
syncAmbientRevealClip(null);
flowReveal?.stop();
scrollReveal?.stop();
}
function startReveal() {
if (reducedMotion || config.revealSystem === "instant" || !config.revealMotion) {
flowReveal?.stop();
scrollReveal?.complete();
return;
}
if (config.revealSystem === "flow") {
flowReveal?.start();
} else if (config.revealSystem === "scroll") {
scrollReveal?.start();
syncAmbientRevealClip(scrollReveal?.visualProgress || 0);
}
}
function onMotionPreferenceChange(event) {
reducedMotion = event.matches;
if (reducedMotion) {
stopMotion();
stopReveal();
scrollReveal?.complete();
return;
}
lastSize = "";
build(true);
startReveal();
startMotion();
}
function regenerate(nextOptions = {}) {
stopMotion();
stopReveal();
config = { ...config, ...nextOptions };
lastSize = "";
build(true);
startReveal();
startMotion();
return api;
}
function setReveal(nextOptions = {}) {
return regenerate(nextOptions);
}
function setAnimation(nextOptions = {}) {
stopMotion();
destroyAnimationSystems();
config = { ...config, ...nextOptions };
createAnimationSystems();
if (config.revealSystem === "scroll") syncAmbientRevealClip(scrollReveal?.visualProgress || 0);
startMotion();
return api;
}
function triggerAnimation(origin = null) {
if (config.animationSystem === "conductor") return conductor?.trigger(origin) || false;
if (config.animationSystem === "handshake") return handshake?.trigger(origin) || false;
if (config.animationSystem === "reflection") return reflection?.trigger(origin) || false;
return false;
}
function triggerReveal() {
if (config.revealSystem === "flow") {
stopMotion();
startReveal();
return true;
}
if (config.revealSystem === "scroll") return false;
return false;
}
function setRevealProgress(progress, options) {
scrollReveal?.setProgress(progress, options);
return api;
}
function exportSvg() {
const clone = svg.cloneNode(true);
clone.querySelectorAll("[data-animation]").forEach((layer) => layer.remove());
clone.querySelector("[data-static-traces]")?.style.removeProperty("visibility");
clone.querySelector("[data-static-traces]")?.style.removeProperty("display");
clone.querySelectorAll("[data-trace-path]").forEach((path) => {
path.removeAttribute("stroke-dasharray");
path.removeAttribute("stroke-dashoffset");
path.style.removeProperty("opacity");
path.style.removeProperty("will-change");
});
clone.querySelectorAll("[data-circuit-trace]").forEach((group, index) => {
const path = group.querySelector("[data-trace-path]");
const trace = field.traces[index];
if (path && trace) path.setAttribute("d", pathData(trace.points));
});
clone.querySelectorAll("[data-trace-via]").forEach((via) => {
via.style.removeProperty("opacity");
via.style.removeProperty("will-change");
});
clone.removeAttribute("style");
return `<?xml version="1.0" encoding="UTF-8"?>\n${clone.outerHTML}`;
}
function destroy() {
stopMotion();
stopReveal();
destroyAnimationSystems();
destroyRevealSystems();
window.clearTimeout(resizeTimer);
resizeObserver?.disconnect();
interactionTarget.removeEventListener("pointermove", onPointerMove);
interactionTarget.removeEventListener("pointerleave", onPointerLeave);
interactionTarget.removeEventListener("click", onClick);
if (motionPreference.removeEventListener) {
motionPreference.removeEventListener("change", onMotionPreferenceChange);
} else {
motionPreference.removeListener?.(onMotionPreferenceChange);
}
svg?.remove();
mount.style.position = originalPosition;
mount.style.overflow = originalOverflow;
}
const api = {
regenerate,
setReveal,
setAnimation,
triggerAnimation,
triggerBloom: triggerAnimation,
triggerReveal,
setRevealProgress,
exportSvg,
destroy,
get field() { return field; },
get animationStats() {
if (config.animationSystem === "none") {
return { system: "none", animationNodes: 0, frameCount: 0, reducedMotion };
}
if (config.animationSystem === "conductor") return conductor?.stats;
if (config.animationSystem === "handshake") {
return handshake?.stats || {
system: "adom-handshake",
animationNodes: 0,
activeTransactions: 0,
frameCount: 0,
reducedMotion,
};
}
if (config.animationSystem === "reflection") {
return reflection?.stats || {
system: "adom-reflection",
animationNodes: 0,
activeReflections: 0,
frameCount: 0,
reducedMotion,
};
}
return { system: "unknown", animationNodes: 0, frameCount: 0, reducedMotion };
},
get revealStats() {
if (config.revealSystem === "instant") {
return { system: "instant", traceCount: field?.traces.length || 0, completed: true, reducedMotion };
}
if (config.revealSystem === "flow") {
return flowReveal?.stats || {
system: "trace-flow-sweep",
traceCount: field?.traces.length || 0,
activeTraces: 0,
reducedMotion,
};
}
if (config.revealSystem === "scroll") {
return scrollReveal?.stats || {
system: "scroll-trace-reveal",
traceCount: field?.traces.length || 0,
activeTraces: 0,
completedTraces: 0,
progress: 0,
reducedMotion,
};
}
return { system: "unknown", traceCount: 0, reducedMotion };
},
};
build(true);
resizeObserver = new ResizeObserver(() => {
window.clearTimeout(resizeTimer);
resizeTimer = window.setTimeout(() => {
if (build()) {
startReveal();
startMotion();
}
}, 220);
});
resizeObserver.observe(mount);
if (config.interactive) {
interactionTarget.addEventListener("pointermove", onPointerMove, { passive: true });
interactionTarget.addEventListener("pointerleave", onPointerLeave, { passive: true });
interactionTarget.addEventListener("click", onClick);
}
if (motionPreference.addEventListener) {
motionPreference.addEventListener("change", onMotionPreferenceChange);
} else {
motionPreference.addListener?.(onMotionPreferenceChange);
}
startReveal();
startMotion();
return api;
}
function createStreamingCircuitBackground(mount, options = {}) {
if (!(mount instanceof Element)) throw new TypeError("mount must be a DOM element");
const id = `circuit-stream-${++instanceCount}`;
let config = { ...DEFAULTS, ...options };
let stream;
let reveal;
let svg;
let unsubscribe;
let fallbackUnsubscribe;
let conductor;
let handshake;
let reflection;
let resizeObserver;
let resizeTimer;
let ambientRefreshTimer;
let coordinatorFrame = 0;
let clipFrame = 0;
let lastAmbientClipBottom = null;
let ambientClipLayers = null;
let generationRequest = 0;
let generationRequestType = "";
let generationPromise = null;
let streamUsesWorker = false;
let buildEpoch = 0;
let generationTargetWorldY = 0;
let destroyed = false;
let pointer = null;
let lastPixelWidth = Math.max(1, mount.clientWidth);
let logicalPixelHeight = Math.max(1, mount.clientHeight);
let previousViewportTop = 0;
let previousViewportTime = performance.now();
let latestViewport = null;
const interactionTarget = mount.parentElement || mount;
const readingZone = interactionTarget.querySelector?.("[data-circuit-reading-zone]") || null;
const motionPreference = window.matchMedia("(prefers-reduced-motion: reduce)");
let reducedMotion = motionPreference.matches;
const originalMountPosition = mount.style.position;
const originalMountOverflow = mount.style.overflow;
const originalTargetMinHeight = interactionTarget.style.minHeight;
if (getComputedStyle(mount).position === "static") mount.style.position = "relative";
mount.style.overflow = "hidden";
function clamp(value, minimum = 0, maximum = 1) {
return Math.max(minimum, Math.min(maximum, value));
}
function worldScale() {
return Math.max(0.01, Number(config.scale) || 1);
}
function featureScale() {
return Math.max(0.25, Number(config.featureScale) || 1);
}
function life() {
return clamp(Number(config.liveliness) || 0, 0, 1.5);
}
function logicalWorldHeight() {
return logicalPixelHeight / worldScale();
}
function viewportPixelHeight() {
return Math.max(1, window.innerHeight || document.documentElement.clientHeight || 1);
}
function viewportWorldHeight() {
return viewportPixelHeight() / worldScale();
}
function generationChunkRows() {
const pitch = Math.max(1, (Number(config.pitch) || 7.895) * featureScale());
const screens = clamp(Number(config.generationChunkScreens) || 0.42, 0.25, 1.5);
const rowsForScreens = viewportWorldHeight() / pitch * screens;
const estimatedColumns = Math.max(8, Math.floor(lastPixelWidth / worldScale() / pitch));
const rowsForWorkBudget = Math.floor(6500 / estimatedColumns);
return Math.round(clamp(Math.min(rowsForScreens, rowsForWorkBudget), 24, 96));
}
function currentViewport() {
const rect = interactionTarget.getBoundingClientRect();
const heightPixels = viewportPixelHeight();
const topPixels = clamp(-rect.top, 0, Math.max(0, logicalPixelHeight - heightPixels));
const now = performance.now();
const elapsed = Math.max(1, now - previousViewportTime);
const forwardPixels = Math.max(0, topPixels - previousViewportTop);
const velocity = forwardPixels / worldScale() / elapsed;
previousViewportTop = topPixels;
previousViewportTime = now;
latestViewport = {
topPixels,
bottomPixels: Math.min(logicalPixelHeight, topPixels + heightPixels),
heightPixels,
topWorldY: topPixels / worldScale(),
bottomWorldY: Math.min(logicalPixelHeight, topPixels + heightPixels) / worldScale(),
heightWorld: heightPixels / worldScale(),
velocity,
};
return latestViewport;
}
const fieldFacade = {
get width() { return stream?.width || lastPixelWidth / worldScale(); },
get height() { return logicalWorldHeight(); },
get traces() { return stream?.traces || []; },
};
function updateSvgExtent() {
if (!svg || !stream) return;
svg.setAttribute("viewBox", `0 0 ${stream.width} ${logicalWorldHeight()}`);
}
function syncAmbientRevealClip() {
if (!reveal || !svg || config.animationSystem === "none") return;
const stats = reveal.stats;
const through = Number(stats.revealedThroughWorldY);
const progress = Number.isFinite(through)
? clamp(through / Math.max(1, logicalWorldHeight()))
: 0;
const bottom = Math.max(0, 100 - progress * 100);
if (bottom === lastAmbientClipBottom) return;
lastAmbientClipBottom = bottom;
const layers = ambientClipLayers || Array.from(mount.querySelectorAll("[data-animation]"))
.filter((layer) => layer.getAttribute("data-animation") !== "stream-trace-reveal");
ambientClipLayers = layers;
const clipPath = "inset(0 0 " + bottom + "% 0)";
for (const layer of layers) layer.style.clipPath = clipPath;
}
function scheduleAmbientClip() {
if (
clipFrame
|| destroyed
|| config.animationSystem === "none"
|| (!conductor && !handshake && !reflection)
) return;
const tick = () => {
clipFrame = 0;
syncAmbientRevealClip();
const stats = reveal?.stats;
if (
config.animationSystem !== "none"
&& (conductor || handshake || reflection)
&& stats
&& (stats.active > 0 || stats.queued > 0)
) {
clipFrame = window.requestAnimationFrame(tick);
}
};
clipFrame = window.requestAnimationFrame(tick);
}
function clearAmbientClip() {
window.cancelAnimationFrame(clipFrame);
clipFrame = 0;
lastAmbientClipBottom = null;
const layers = ambientClipLayers || Array.from(mount.querySelectorAll("[data-animation]"));
for (const layer of layers) {
if (layer.getAttribute("data-animation") !== "stream-trace-reveal") {
layer.style.removeProperty("clip-path");
}
}
ambientClipLayers = null;
}
function stopMotion() {
conductor?.stop();
handshake?.stop();
reflection?.stop();
}
function destroyAnimationSystems() {
window.clearTimeout(ambientRefreshTimer);
ambientRefreshTimer = 0;
stopMotion();
conductor?.destroy();
handshake?.destroy();
reflection?.destroy();
conductor = undefined;
handshake = undefined;
reflection = undefined;
ambientClipLayers = null;
lastAmbientClipBottom = null;
}
function createAnimationSystems() {
if (!svg || !stream || config.animationSystem === "none") return;
const namespace = config.animationSystem === "conductor"
? "stream-signals"
: `stream-${config.animationSystem}`;
const random = mulberry32(hashSeed([
config.seed,
Math.round(stream.width),
Math.round(logicalWorldHeight()),
namespace,
].join(":")));
if (config.animationSystem === "conductor") {
conductor = createConductorAnimation(svg, fieldFacade, config, random);
} else if (config.animationSystem === "handshake") {
handshake = createHandshakeAnimation(mount, fieldFacade, config, random, { readingZone });
} else if (config.animationSystem === "reflection") {
reflection = createReflectionAnimation(mount, fieldFacade, config, random, { readingZone });
}
ambientClipLayers = Array.from(mount.querySelectorAll("[data-animation]"))
.filter((layer) => layer.getAttribute("data-animation") !== "stream-trace-reveal");
syncAmbientRevealClip();
}
function startMotion() {
stopMotion();
if (config.animationSystem === "none" || !config.motion || reducedMotion || life() <= 0.02) return;
if (config.animationSystem === "conductor") conductor?.start();
else if (config.animationSystem === "handshake") handshake?.start();
else if (config.animationSystem === "reflection") reflection?.start();
}
function refreshGrowingCanvasAnimation() {
if (!reflection && !handshake) return;
window.clearTimeout(ambientRefreshTimer);
const epoch = buildEpoch;
const candidate = stream;
ambientRefreshTimer = window.setTimeout(() => {
ambientRefreshTimer = 0;
if (destroyed || epoch !== buildEpoch || candidate !== stream) return;
destroyAnimationSystems();
createAnimationSystems();
startMotion();
}, 180);
}
function stopReveal() {
reveal?.stop();
}
function startReveal() {
if (!reveal) return;
if (reducedMotion || !config.revealMotion) reveal.complete();
else reveal.start();
scheduleAmbientClip();
}
function cancelGenerationRequest() {
if (!generationRequest) return;
if (generationRequestType === "idle" && window.cancelIdleCallback) {
window.cancelIdleCallback(generationRequest);
} else {
window.clearTimeout(generationRequest);
}
generationRequest = 0;
generationRequestType = "";
}
function extendPageWhenReady(viewport = currentViewport()) {
if (!stream) return false;
const triggerScreens = 1.45;
const distancePixels = logicalPixelHeight - viewport.bottomPixels;
if (distancePixels > viewport.heightPixels * triggerScreens) return false;
const extendScreens = clamp(Number(config.generationExtendScreens) || 2, 0.75, 4);
const extensionPixels = viewport.heightPixels * extendScreens;
const nextPixelHeight = logicalPixelHeight + extensionPixels;
const nextWorldHeight = nextPixelHeight / worldScale();
if (stream.generatedThrough + stream.stats.pitch * 0.25 < nextWorldHeight) return false;
logicalPixelHeight = nextPixelHeight;
interactionTarget.style.minHeight = `${Math.ceil(logicalPixelHeight)}px`;
updateSvgExtent();
refreshGrowingCanvasAnimation();
return true;
}
function pumpGeneration() {
generationRequest = 0;
generationRequestType = "";
if (destroyed || !stream || stream.generatedThrough >= generationTargetWorldY) return;
const stats = stream.stats;
const chunkHeight = Math.max(stats.pitch, stats.pitch * stats.chunkRows);
const through = Math.min(generationTargetWorldY, stream.generatedThrough + chunkHeight);
stream.ensureThrough(through);
const viewport = latestViewport || currentViewport();
const extended = extendPageWhenReady(viewport);
if (extended) coordinate(viewport);
if (stream.generatedThrough < generationTargetWorldY) scheduleGeneration();
}
function scheduleWorkerGeneration() {
if (generationPromise || !stream || stream.generatedThrough >= generationTargetWorldY) return;
const candidate = stream;
const epoch = buildEpoch;
const target = generationTargetWorldY;
generationPromise = candidate.ensureThrough(target)
.then(() => {
if (destroyed || candidate !== stream || epoch !== buildEpoch) return;
const viewport = latestViewport || currentViewport();
const extended = extendPageWhenReady(viewport);
if (extended) coordinate(viewport);
})
.catch((error) => {
if (
!destroyed
&& candidate === stream
&& epoch === buildEpoch
&& error?.name !== "AbortError"
) {
fallbackToSynchronousStream(candidate);
}
})
.finally(() => {
if (candidate !== stream || epoch !== buildEpoch) return;
generationPromise = null;
if (stream.generatedThrough < generationTargetWorldY) scheduleGeneration();
});
}
function scheduleGeneration() {
if (destroyed || !stream || stream.generatedThrough >= generationTargetWorldY) return;
if (streamUsesWorker) {
scheduleWorkerGeneration();
return;
}
if (generationRequest) return;
if (typeof window.requestIdleCallback === "function") {
generationRequestType = "idle";
generationRequest = window.requestIdleCallback(pumpGeneration, { timeout: 120 });
} else {
generationRequestType = "timer";
generationRequest = window.setTimeout(pumpGeneration, 16);
}
}
function coordinate(measured = null) {
if (destroyed || !stream || !reveal) return;
logicalPixelHeight = Math.max(logicalPixelHeight, mount.clientHeight);
const viewport = measured || currentViewport();
reveal.setViewport({
topWorldY: viewport.topWorldY,
bottomWorldY: viewport.bottomWorldY,
height: viewport.heightWorld,
velocity: viewport.velocity,
});
const aheadScreens = clamp(Number(config.generationAheadScreens) || 2, 0.5, 6);
generationTargetWorldY = Math.max(
generationTargetWorldY,
viewport.bottomWorldY + viewport.heightWorld * aheadScreens,
);
const distancePixels = logicalPixelHeight - viewport.bottomPixels;
if (distancePixels <= viewport.heightPixels * 1.45) {
const extendScreens = clamp(Number(config.generationExtendScreens) || 2, 0.75, 4);
generationTargetWorldY = Math.max(
generationTargetWorldY,
logicalWorldHeight() + viewport.heightWorld * extendScreens,
);
}
extendPageWhenReady(viewport);
scheduleGeneration();
scheduleAmbientClip();
}
function scheduleCoordinate() {
if (coordinatorFrame || destroyed) return;
coordinatorFrame = window.requestAnimationFrame(() => {
coordinatorFrame = 0;
coordinate();
});
}
function createSvg() {
svg = svgElement("svg", {
id,
"aria-hidden": true,
focusable: false,
viewBox: `0 0 ${stream.width} ${logicalWorldHeight()}`,
preserveAspectRatio: "none",
"data-continuous-circuit": "v7",
});
Object.assign(svg.style, {
position: "absolute",
inset: "0",
width: "100%",
height: "100%",
display: "block",
pointerEvents: "none",
});
mount.prepend(svg);
}
function fallbackToSynchronousStream(candidate) {
if (destroyed || candidate !== stream) return;
build(false);
}
async function initializeStream(candidate, streamOptions, epoch, useWorker) {
try {
if (useWorker) await candidate.ready;
if (destroyed || candidate !== stream || epoch !== buildEpoch) return;
createSvg();
reveal = createStreamingTraceReveal(svg, config, {
...config,
width: candidate.width,
autoStart: false,
});
const viewport = currentViewport();
reveal.setViewport({
topWorldY: viewport.topWorldY,
bottomWorldY: viewport.bottomWorldY,
height: viewport.heightWorld,
velocity: 0,
});
unsubscribe = candidate.onAppend((traces) => {
if (destroyed || candidate !== stream || epoch !== buildEpoch || !reveal) return;
reveal.appendTraces(traces);
if (reducedMotion || !config.revealMotion) reveal.complete();
refreshGrowingCanvasAnimation();
scheduleAmbientClip();
});
const initialAhead = Math.min(
clamp(Number(config.generationAheadScreens) || 2, 0.5, 6),
0.35,
);
const initialThrough = Math.min(
logicalWorldHeight(),
viewport.bottomWorldY + viewport.heightWorld * initialAhead,
);
await Promise.resolve(candidate.ensureThrough(initialThrough));
if (destroyed || candidate !== stream || epoch !== buildEpoch) return;
generationTargetWorldY = Math.max(
logicalWorldHeight(),
viewport.bottomWorldY + viewport.heightWorld * (Number(config.generationAheadScreens) || 2),
);
createAnimationSystems();
startReveal();
startMotion();
coordinate(viewport);
scheduleGeneration();
} catch (error) {
if (destroyed || candidate !== stream || epoch !== buildEpoch || error?.name === "AbortError") return;
if (useWorker) fallbackToSynchronousStream(candidate);
else console.error("Circuit field generation failed", error, streamOptions);
}
}
function build(useWorker = true) {
buildEpoch += 1;
const epoch = buildEpoch;
cancelGenerationRequest();
generationPromise = null;
window.cancelAnimationFrame(coordinatorFrame);
coordinatorFrame = 0;
clearAmbientClip();
destroyAnimationSystems();
unsubscribe?.();
fallbackUnsubscribe?.();
unsubscribe = undefined;
fallbackUnsubscribe = undefined;
reveal?.destroy();
reveal = undefined;
stream?.destroy();
stream = undefined;
svg?.remove();
svg = undefined;
lastPixelWidth = Math.max(1, mount.clientWidth);
logicalPixelHeight = Math.max(logicalPixelHeight, mount.clientHeight, viewportPixelHeight() * 3);
interactionTarget.style.minHeight = Math.ceil(logicalPixelHeight) + "px";
const streamOptions = {
...config,
width: lastPixelWidth / worldScale(),
chunkRows: generationChunkRows(),
};
streamUsesWorker = useWorker;
stream = useWorker
? createStreamingCircuitFieldWorker(streamOptions)
: createStreamingCircuitField(streamOptions);
const candidate = stream;
if (useWorker) {
fallbackUnsubscribe = candidate.onFallback(() => {
fallbackToSynchronousStream(candidate);
});
}
initializeStream(candidate, streamOptions, epoch, useWorker);
}
function onPointerMove(event) {
if (!stream) return;
const rect = mount.getBoundingClientRect();
if (rect.width <= 0 || rect.height <= 0) return;
pointer = {
x: (event.clientX - rect.left) / rect.width * fieldFacade.width,
y: (event.clientY - rect.top) / rect.height * fieldFacade.height,
at: performance.now(),
};
conductor?.pointerMoved(pointer);
handshake?.pointerMoved(pointer);
reflection?.pointerMoved(pointer);
}
function onPointerLeave() {
if (pointer) pointer.at = performance.now() - 3000;
handshake?.pointerLeft();
reflection?.pointerLeft();
}
function onClick(event) {
onPointerMove(event);
if (config.animationSystem === "conductor") conductor?.clicked(pointer);
else if (config.animationSystem === "handshake") handshake?.clicked(pointer);
else if (config.animationSystem === "reflection") reflection?.clicked(pointer);
}
function onMotionPreferenceChange(event) {
reducedMotion = event.matches;
if (reducedMotion) {
stopMotion();
reveal?.complete();
} else {
reveal?.start();
startMotion();
}
scheduleAmbientClip();
}
function regenerate(nextOptions = {}) {
if (destroyed) throw new Error("Cannot regenerate a destroyed circuit background");
config = { ...config, ...nextOptions };
build();
coordinate();
return api;
}
function setReveal(nextOptions = {}) {
if (destroyed) throw new Error("Cannot update a destroyed circuit background");
config = { ...config, ...nextOptions };
reveal?.updateOptions(config);
if (reducedMotion || !config.revealMotion) reveal?.complete();
else reveal?.start();
coordinate();
return api;
}
function setAnimation(nextOptions = {}) {
if (destroyed) throw new Error("Cannot update a destroyed circuit background");
config = { ...config, ...nextOptions };
destroyAnimationSystems();
createAnimationSystems();
startMotion();
scheduleAmbientClip();
return api;
}
function triggerAnimation(origin = null) {
if (config.animationSystem === "conductor") return conductor?.trigger(origin) || false;
if (config.animationSystem === "handshake") return handshake?.trigger(origin) || false;
if (config.animationSystem === "reflection") return reflection?.trigger(origin) || false;
return false;
}
function triggerReveal() {
if (!reveal || reducedMotion || !config.revealMotion) return false;
reveal.reset();
reveal.start();
coordinate();
return true;
}
function setRevealProgress(progress, progressOptions = {}) {
if (!reveal) return api;
const amount = clamp(Number(progress) || 0);
const height = viewportWorldHeight();
const topWorldY = Math.max(0, (logicalWorldHeight() - height) * amount);
reveal.setViewport({
topWorldY,
bottomWorldY: topWorldY + height,
height,
velocity: Math.max(0, Number(progressOptions.velocity) || 0),
});
scheduleAmbientClip();
return api;
}
function addExportVia(group, point) {
const scale = featureScale();
group.append(
svgElement("circle", {
cx: point[0],
cy: point[1],
r: config.viaOuterRadius * scale,
fill: config.traceColor,
}),
svgElement("circle", {
cx: point[0],
cy: point[1],
r: config.viaInnerRadius * scale,
fill: "var(--circuit-hole, #0d1117)",
}),
);
}
function exportSvg() {
const exportHeight = Math.max(logicalWorldHeight(), stream?.generatedThrough || 0);
const exported = svgElement("svg", {
xmlns: SVG_NS,
"aria-hidden": true,
focusable: false,
viewBox: `0 0 ${fieldFacade.width} ${exportHeight}`,
preserveAspectRatio: "none",
});
const paths = svgElement("g", { "data-static-traces": "" });
const vias = svgElement("g", { "data-static-vias": "" });
for (const trace of fieldFacade.traces) {
paths.append(svgElement("path", {
d: pathData(trace.points),
fill: "none",
stroke: config.traceColor,
"stroke-width": config.traceWidth * featureScale(),
"stroke-linecap": "butt",
"stroke-linejoin": "miter",
}));
addExportVia(vias, trace.points[0]);
addExportVia(vias, trace.points.at(-1));
}
exported.append(paths, vias);
return `<?xml version="1.0" encoding="UTF-8"?>\n${exported.outerHTML}`;
}
function destroy() {
if (destroyed) return;
destroyed = true;
buildEpoch += 1;
generationPromise = null;
cancelGenerationRequest();
window.cancelAnimationFrame(coordinatorFrame);
window.cancelAnimationFrame(clipFrame);
window.clearTimeout(resizeTimer);
window.clearTimeout(ambientRefreshTimer);
resizeObserver?.disconnect();
window.removeEventListener("scroll", scheduleCoordinate);
window.removeEventListener("resize", scheduleCoordinate);
interactionTarget.removeEventListener("pointermove", onPointerMove);
interactionTarget.removeEventListener("pointerleave", onPointerLeave);
interactionTarget.removeEventListener("click", onClick);
if (motionPreference.removeEventListener) {
motionPreference.removeEventListener("change", onMotionPreferenceChange);
} else {
motionPreference.removeListener?.(onMotionPreferenceChange);
}
destroyAnimationSystems();
unsubscribe?.();
fallbackUnsubscribe?.();
reveal?.destroy();
stream?.destroy();
svg?.remove();
mount.style.position = originalMountPosition;
mount.style.overflow = originalMountOverflow;
interactionTarget.style.minHeight = originalTargetMinHeight;
}
const api = {
regenerate,
setReveal,
setAnimation,
triggerAnimation,
triggerBloom: triggerAnimation,
triggerReveal,
setRevealProgress,
exportSvg,
destroy,
get field() { return fieldFacade; },
get animationStats() {
if (config.animationSystem === "none") {
return { system: "none", animationNodes: 0, frameCount: 0, reducedMotion };
}
if (config.animationSystem === "conductor") return conductor?.stats;
if (config.animationSystem === "handshake") return handshake?.stats;
if (config.animationSystem === "reflection") return reflection?.stats;
return { system: "unknown", animationNodes: 0, frameCount: 0, reducedMotion };
},
get revealStats() {
const stats = reveal?.stats || {};
return {
system: "scroll-trace-reveal",
continuous: true,
traceCount: fieldFacade.traces.length,
activeTraces: stats.active || 0,
queuedTraces: stats.queued || 0,
completedTraces: stats.completed || 0,
unlockedTraces: stats.unlocked || 0,
generatedThrough: stream?.generatedThrough || 0,
pageWorldHeight: logicalWorldHeight(),
reducedMotion,
};
},
};
build();
resizeObserver = new ResizeObserver(() => {
const nextWidth = Math.max(1, mount.clientWidth);
if (Math.abs(nextWidth - lastPixelWidth) < 1) return;
window.clearTimeout(resizeTimer);
resizeTimer = window.setTimeout(() => {
if (!destroyed) regenerate();
}, 180);
});
resizeObserver.observe(mount);
window.addEventListener("scroll", scheduleCoordinate, { passive: true });
window.addEventListener("resize", scheduleCoordinate, { passive: true });
if (config.interactive) {
interactionTarget.addEventListener("pointermove", onPointerMove, { passive: true });
interactionTarget.addEventListener("pointerleave", onPointerLeave, { passive: true });
interactionTarget.addEventListener("click", onClick);
}
if (motionPreference.addEventListener) {
motionPreference.addEventListener("change", onMotionPreferenceChange);
} else {
motionPreference.addListener?.(onMotionPreferenceChange);
}
coordinate();
return api;
}
function normalizeOptions(options = {}) {
const normalized = { ...options };
if (normalized.animationSystem === "growth") {
normalized.animationSystem = "none";
if (!normalized.revealSystem) normalized.revealSystem = "flow";
if (normalized.revealMotion === undefined) normalized.revealMotion = true;
}
if (normalized.spawnSystem && !normalized.revealSystem) {
normalized.revealSystem = normalized.spawnSystem === "front"
? "flow"
: normalized.spawnSystem;
}
return normalized;
}
export function createCircuitBackground(mount, options = {}) {
let config = { ...DEFAULTS, ...normalizeOptions(options) };
let destroyed = false;
const engineKind = (value) => {
if (value.animationSystem === "adom") return "routed";
if (value.revealSystem === "scroll" && value.continuousGeneration) return "streaming";
return "finite";
};
const createEngine = () => {
const kind = engineKind(config);
if (kind === "routed") {
return createLegacyCircuitBackground(mount, { ...config, revealSystem: "instant" });
}
if (kind === "streaming") return createStreamingCircuitBackground(mount, config);
return createModernCircuitBackground(mount, config);
};
let engine = createEngine();
function recreateEngine() {
engine.destroy();
engine = createEngine();
}
const api = {
regenerate(nextOptions = {}) {
if (destroyed) throw new Error("Cannot regenerate a destroyed circuit background");
const previousKind = engineKind(config);
const normalized = normalizeOptions(nextOptions);
config = { ...config, ...normalized };
if (previousKind !== engineKind(config)) recreateEngine();
else engine.regenerate(normalized);
return api;
},
setReveal(nextOptions = {}) {
if (destroyed) throw new Error("Cannot update a destroyed circuit background");
const previousKind = engineKind(config);
const normalized = normalizeOptions(nextOptions);
config = { ...config, ...normalized };
if (previousKind !== engineKind(config)) recreateEngine();
else if (engineKind(config) !== "routed") engine.setReveal?.(normalized);
return api;
},
setAnimation(nextOptions = {}) {
if (destroyed) throw new Error("Cannot update a destroyed circuit background");
const previousKind = engineKind(config);
const normalized = normalizeOptions(nextOptions);
config = { ...config, ...normalized };
if (previousKind !== engineKind(config)) recreateEngine();
else if (engineKind(config) === "routed") engine.regenerate(normalized);
else engine.setAnimation(normalized);
return api;
},
triggerAnimation(origin = null) {
return engine.triggerAnimation?.(origin) || false;
},
triggerBloom(origin = null) {
return engine.triggerBloom?.(origin) || engine.triggerAnimation?.(origin) || false;
},
triggerReveal() {
if (engineKind(config) === "routed") return false;
return engine.triggerReveal?.() || false;
},
setRevealProgress(progress, progressOptions) {
if (engineKind(config) !== "routed") {
engine.setRevealProgress?.(progress, progressOptions);
}
return api;
},
exportSvg() {
return engine.exportSvg();
},
destroy() {
if (destroyed) return;
destroyed = true;
engine.destroy();
},
get field() { return engine.field; },
get animationStats() { return engine.animationStats; },
get revealStats() {
if (engineKind(config) === "routed") {
return { system: "unavailable-routed", traceCount: engine.field?.traces.length || 0 };
}
return engine.revealStats;
},
};
return api;
}
export {
DEFAULTS,
ANIMATION_PRESETS,
REVEAL_PRESETS,
TRACE_SPAWN_PRESETS,
generateCircuitField,
pathData,
};