seinx-nn-canvas-animation
v3.3.0
Published
Neural network canvas animation web components — by SeinX
Maintainers
Readme
Neural Canvas Animation
A breathing neural network animation for the web — two web components, zero dependencies, one CDN tag.
v3.2.0 | Live demo | Built by SeinX
Quick Start
CDN (one tag)
<!DOCTYPE html>
<html>
<body style="margin:0; background:#1a1a1a;">
<neural-canvas style="width:100vw; height:100vh; display:block;"></neural-canvas>
<script type="module" src="https://unpkg.com/seinx-nn-canvas-animation/dist/neural-canvas.min.js"></script>
</body>
</html>That's it. Save it as an HTML file and open it.
npm
npm install seinx-nn-canvas-animation// Canvas only (landing page background)
import { NeuralCanvas } from 'seinx-nn-canvas-animation';
// Canvas + controls (tinkering UI)
import { NeuralCanvas } from 'seinx-nn-canvas-animation/canvas';
import { NeuralCanvasControls } from 'seinx-nn-canvas-animation/controls';What You Get
<neural-canvas>
Shadow DOM headless renderer. Two overlapping neural networks animate on canvas — nodes drift, connections breathe, halos glow. No UI chrome, just the animation.
Attributes:
| Attribute | Purpose |
|-----------|---------|
| preset-data | Base64-encoded 41-parameter preset string |
| paused | Pause the animation loop (boolean attribute) |
| no-attr | Skip the SeinX attribution overlay |
| no-brake | Disable the auto safety brake (boolean attribute) |
| uniform-random | Switch reshuffle() + cold-load to full-uniform sampling instead of family-based |
Methods:
const canvas = document.querySelector('neural-canvas');
// Apply a full preset from base64
canvas.applyPreset('eyJub2RlRGVuc2l0eSI6MTMy...');
// Tweak individual parameters (partial config)
canvas.applyConfig({ speed: 0.05, glowSpread: 3.0 });
// Randomize with the family-based randomizer
canvas.reshuffle();
// Nudge a vibe dimension (strength: -2, -1, 1, or 2)
canvas.nudgeVibe('depth', 2);
// Get current state as a shareable base64 string
const preset = canvas.getPresetData();
// Crossfade to a random preset over 3 seconds
canvas.morphToRandom(3000);
// Force a clean re-render (useful after external DOM changes)
canvas.rerenderFresh();Events:
| Event | Payload | When |
|-------|---------|------|
| neural-canvas:stats | { fps, frameTime, nodeCount, connectionCount, breathPhase } | Every frame |
| neural-canvas:ready | — | Component initialized, first frame rendered |
| neural-canvas:low-fps | { fps, brakeEngaged } | Sustained low FPS detected (brake engaged if _safetyBrakeEnabled) |
Accessibility:
canvas.reducedMotion = true; // Freeze all motion (also auto-detects OS preference)
canvas.highContrast = true; // Override opacities to 1.0, kill glow
canvas.safetyBrake = false; // Disable the auto FPS-triggered reduced-motion latch
canvas.uniformRandom = true; // reshuffle() samples uniformly instead of from families<neural-canvas-controls>
The tinkering panel. Binds to a <neural-canvas> via for attribute or DOM proximity.
Contains: 10 vibe buttons, 41 parameter sliders, 3 preset slots, 2 color pickers, stats display, fullscreen toggle.
Mobile (viewport <= 768px): The desktop panel is hidden. A single frosted-glass RANDOM button appears instead — tap it to reshuffle. That's the only mobile affordance.
Keyboard shortcuts:
| Key | Action |
|-----|--------|
| Space | Reshuffle (randomize) |
| S | Save to current preset slot |
| Ctrl+C / Ctrl+V | Copy / paste preset as base64 |
| Y | Toggle panel visibility |
| P | Toggle stats overlay |
| Ctrl+H / ⌘H | Toggle high contrast |
| Ctrl+J / ⌘J | Toggle reduced motion |
| Ctrl+B / ⌘B | Toggle safety brake |
| Z | Toggle screensaver mode (includes fullscreen) |
| 1 / 2 / 3 | Load saved preset slot |
| Escape | Exit screensaver if active, otherwise blur active element |
Screensaver Mode
Press Z, click the screensaver button, or call controls.toggleScreensaver() from JavaScript. The animation:
- Hides the controls element (panel, screensaver button, mobile RANDOM FAB) and the
Built by SeinXattribution — clean kiosk view - Requests fullscreen (granted only on user gesture; falls back gracefully if denied)
- Crossfades to a new random preset every 30 seconds (configurable, 30s–15m)
- Each crossfade takes 5 seconds (configurable, 1–15s) using
morphToRandom() - The cursor auto-hides after 3 seconds of inactivity
- Press Z or Escape to exit — controls and attribution restore to their pre-screensaver state
?screensaver URL parameter (kiosk launch)
index.html accepts ?screensaver (also ?screensaver=1, ?screensaver=true) to drop straight into screensaver mode on page load. Useful for digital-signage / kiosk / shareable demo links:
https://nn.seinx.ai/?screensaver
https://nn.seinx.ai/?preset=<base64>&screensaverNote: load-triggered screensaver does not engage fullscreen (browsers require a user gesture). The clean kiosk visual still applies.
Under the hood, the crossfade system uses 4 canvas layers — two live canvases and two overlay canvases for smooth transitions. The old frame is painted to an overlay, the new preset starts rendering underneath, and the overlay fades out.
Examples
Landing page background
<style>
body { margin: 0; overflow: hidden; }
neural-canvas { position: fixed; inset: 0; z-index: -1; }
</style>
<neural-canvas></neural-canvas>
<main style="position: relative; color: white; padding: 2rem;">
<h1>Your content here</h1>
</main>Full interactive page
<neural-canvas id="bg"></neural-canvas>
<neural-canvas-controls for="bg"></neural-canvas-controls>
<script type="module" src="https://unpkg.com/seinx-nn-canvas-animation/dist/neural-canvas.min.js"></script>Tweak from JavaScript
const canvas = document.querySelector('neural-canvas');
// Make it breathe faster with stronger glow
canvas.applyConfig({
breathSpeed: 0.8,
glowSpread: 3.5,
glowIntensity: 0.7,
});
// Push toward more organic visuals
canvas.nudgeVibe('organic', 2);
// Randomize everything
canvas.reshuffle();Share a preset
// Export
const preset = canvas.getPresetData();
const url = `https://nn.seinx.ai?preset=${encodeURIComponent(preset)}`;
// Import
canvas.applyPreset(presetFromUrl);
// Or via HTML attribute
// <neural-canvas preset-data="eyJub2RlRGVuc2l0eSI6MTMy..."></neural-canvas>Parameters (41)
Full reference: docs/parameters.md
| Category | Parameters |
|----------|-----------|
| Structure | nodeDensity, nodeSpread, connDist |
| Density dynamics | nodeDensityVar, densityPull, densityJitter, nodeSpawnRate, nodeFadeTime |
| Appearance | curveSoftness, nodeSize, nodeSizeVar, lineOpacity, nodeOpacity, glowSpread, glowIntensity, lineWidth, lineWidthVar |
| Motion | speed, breathIntensity, breathSpeed, curveSpeed |
| Breath sync | breathDistSync, breathCurveSync, breathSizeSync, breathOpacitySync, breathLineWidthSync, breathGlowSync, breathSpawnSync, breathDepthSync, breathLineStyleSync |
| Breath shape | breathEnvelope (sine, triangle, exponential, sawtooth) |
| Colors | net1Color, net2Color, bgColor |
| Hub & Line Style | hubEmphasis, lineStyle, lineStyleAmount |
| Depth | depthAmount, depthSpeedRatio |
| Per-network | lineWidthLengthBias, lineWidth2 |
Line Styles (5 modes)
Set via lineStyle (integer 0–4). Only takes effect when lineStyleAmount > 0.
| Mode | Name | Character | |------|------|-----------| | 0 | Uniform | Standard straight or curved connections. Clean and even. | | 1 | Bowtie | Thick at endpoints, thin in the middle. Connections taper inward. | | 2 | Tube | Thin at endpoints, thick in the middle. Connections bulge outward. | | 3 | Wave | Sinusoidal displacement along the connection path. Organic ripple. | | 4 | Glow | Luminous filament rendering. Neon-like quality. |
lineStyleAmount controls intensity (0 = off, 1 = full). Can pulse with breathing via breathLineStyleSync.
Vibes (10)
Vibes are high-level nudges across multiple parameters. Call canvas.nudgeVibe(id, strength) where strength is -2, -1, 1, or 2.
| ID | Label | What it nudges |
|----|-------|----------------|
| energy | Energy | speed, curveSpeed |
| density | Density | nodeDensity, connDist, densityPull |
| organic | Organic | curveSoftness, nodeSizeVar, nodeSpread, lineWidthVar, lineStyleAmount |
| breathing | Breathing | breathSpeed, all breath syncs, densityJitter |
| glow | Glow | glowSpread, glowIntensity, nodeSize |
| chaos | Chaos | nodeSpread (down), nodeSizeVar, speed, nodeDensityVar |
| connect | Connect | connDist, lineOpacity, lineWidth, lineWidthVar |
| weight | Weight | nodeSize, lineWidth, nodeOpacity, lineOpacity, hubEmphasis |
| morph | Morph | nodeDensityVar, nodeSpawnRate, nodeFadeTime, breathSpawnSync |
| depth | Depth | depthAmount, depthSpeedRatio (inverse) |
Visual Families (8)
The randomizer (reshuffle()) picks from 8 families, each committing to a distinct visual axis. Desktop selection is uniform across all families; mobile (viewport ≤ 768px) uses a weighted pick that favors dense families and downweights naturally sparse ones so phone rolls land on visually rich configs more often.
| Family | Mobile weight | Character |
|--------|--------|-----------|
| crystal-mesh | 2.5× | Dense, short connDist, near-static breath. Geometric lattice feel. |
| clustered | 1.8× | Tight Gaussian clusters (low nodeSpread) with long inter-cluster connections. Negative length bias. |
| variance-garden | 1.0× | Medium density, extreme size and width variation. Organic, heterogeneous look. |
| motion-drift | 1.0× | Higher node speed, moderate density. Motion comes from speed, not curve oscillation. |
| wave-line | 1.0× | Sinusoidal wave connections (lineStyle=3). Gentle organic ripple. |
| asymmetric-duet | 0.5× | Two visually distinct networks — net 2 is 3–5× thicker than net 1, bowtie line style. |
| sparse-depth | 0.4× | Few large glowing nodes with strong z-parallax. Slow drift, high curve softness. |
| sparse-glow-line | 0.4× | Glow filament connections (lineStyle=4). Ambient node glow reduced so the lines carry the luminosity. |
After family sampling, universal pins (e.g., breathDistSync pinned to 0, breathIntensity pinned to 1.0) and clamps (e.g., speed capped at 0.1, glowIntensity capped at 0.25) enforce quality rules derived from blind grading. On mobile, additional floors apply (connDist ≥ 0.185, lineOpacity ≥ 0.7, nodeSize ≤ 1.6, nodeDensity ≥ 170) plus family-specific tweaks for sparse-glow-line and variance-garden.
Parameters never randomized: breathEnvelope, net1Color, net2Color, bgColor.
Wallpaper Rendering
When node density is high and connection distance is large, the animation automatically extends beyond the visible viewport via a "bleed ring" — extra nodes are spawned outside the canvas edges so connections don't abruptly clip at the border. This is not a mode you toggle; it's a first-class rendering feature that activates automatically for dense presets.
The bleed distance is computed by computeBleed():
bleed = smoothstep(100, 180, nodeDensity) * connDist * 1.2Capped at min(viewportWidth, viewportHeight) * 0.35. On mobile, further capped to prevent excessive node counts. Node count is scaled by the bleed area so density remains consistent.
Performance
The animation is designed to run well everywhere:
- Startup benchmark: Renders 500 nodes for 30 frames to measure device capability
- Budget system: Benchmark result sets the density cap, connection budget, and glow cap
- Runtime auto-scaling: Continuous frame-time monitoring adjusts budgets down if the device is struggling
- Reduced motion: Detects OS
prefers-reduced-motionpreference. Renders single high-quality static frames on demand. - Safety brake: If FPS drops below 20 for 3 consecutive frames, auto-engages reduced motion and dispatches
neural-canvas:low-fpswithbrakeEngaged: true. Released automatically on anyreshuffle()/applyPreset()so a new preset always gets a fresh evaluation window. Disable viacanvas.safetyBrake = falseor theno-brakeattribute.
Bundle size (dist/neural-canvas.min.js): ~107 KB raw / ~28 KB gzip. Single runtime dependency: seinx-design-tokens.
The Story
This animation was designed through convergent evaluation — a custom tool that generated random presets and let a human vote on them, narrowing the parameter space over ~340 evaluations across five rounds. The nine visual families emerged from patterns in what was consistently chosen. The algorithm learned taste from reactions.
v2.0.0 Breaking Changes
<neural-canvas> moved from light DOM to shadow DOM. This means:
- The 8 CSS custom properties (
--nc-color-net1,--nc-color-net2, etc.) are removed - The
.refreshStyles()method is removed - External CSS selectors targeting internal canvases no longer match
Migration: Replace all CSS custom property usage with canvas.applyConfig({ ... }). See CHANGELOG.md for details.
Development
npm install
npm run build # esbuild + tsc declarations
npm test # vitest unit tests (235 tests)
npm run typecheck # tsc --noEmitLicense
Built by SeinX
