{ "summary": "Research how to render glowing neural-tendril/dendrite text with synapse nodes + energy burst on the web, return implementable techniques", "agentCount": 5, "logs": [], "result": { "spec": { "recommendedApproach": "A baked-SVG hybrid, scoped to a single component (`NeuralSign.svelte`) that sits FIXED at the top of the waitlist page above the stats/proof row. Three layers, all driven by ONE pre-generated asset built at compile time:\n\nLAYER 0 (build-time, run once in Node): Grow real dendrites with space-colonization seeded from the rasterized \"VESTIGE\" glyph mask. Sample the glyph alpha to get inside-pixels as attractors + a sparse outside halo for tendrils that burst past the letters; seed roots at the bottom of each stroke; thicken veins by descendant count; drop a synapse at every junction and tip. Export ONE static `vestige-neural.svg` (branch s + synapse s, grouped per-letter, with per-branch `--depth` and per-node `--i` custom props baked in) plus a JSON of streak anchor points. This is the ONLY technique that produces true branch topology + synapse nodes instead of faking neuron-ness with blur — and it costs ZERO at runtime because the text is fixed.\n\nLAYER 1 (glow, compositor-only): A stacked feGaussianBlur → feColorMatrix → feMerge SVG recolored per blur tier (inner green, mid cyan, outer violet) gives the multi-color bloom + hot-white synapse halos for free.\n\nLAYER 2 (life, cheap per-frame): (a) SVG stroke-dashoffset \"draw-on\" staggered by `--depth` so dendrites grow outward from the letter stems on load; (b) CSS @keyframes pulse on synapse s staggered by `--i` for organic firing; (c) a CSS mask-image gradient SWEEP over a baked radial energy-burst layer behind the letters for streaks; (d) a single gentle feTurbulence+feDisplacementMap \"breathing\" warp gated to desktop + prefers-reduced-motion:no-preference.\n\nOPTIONAL TOP TIER (desktop WebGL2 only, lazy-loaded): a tiny dedicated additive Three.js GPUComputationRenderer particle canvas the size of the sign, homes packed from the baked branch points, curl-noise + spring-to-home, UnrealBloom. Feature-gated behind navigator + canvas size; the SVG baseline IS the product and ships first.\n\nReject the full-GPGPU-as-baseline and the AI-PNG-as-primary routes: GPGPU fails the mobile/graceful-degradation constraint as a baseline, and an AI PNG can't be regenerated deterministically for \"JULY 14TH\"/\"SIGN UP NOW\" or animated per-branch. The baked-SVG core wins both fidelity (real dendrites + synapses) AND clean shipping (one static asset, base-path-safe /inline, 60fps on any phone).", "whyThisWins": "It is the only approach that hits reference fidelity (genuine branching dendrites + bright synapse nodes, not blurred neon) while satisfying every hard constraint at once: static GitHub Pages (pure build-time asset, no server), mobile-readable (SVG is resolution-independent and razor-sharp at any DPI), performant (per-frame cost is a handful of compositor ops — dashoffset, opacity, mask-position — with the expensive organic growth paid once offline), and graceful degradation built in by construction (static SVG → +CSS pulses → +turbulence breathing → +optional WebGL particles, each layer additive and independently droppable). It also slots cleanly into THIS codebase: the page already uses `base` from `$app/paths` for subpath assets, already has a fixed full-screen background canvas pattern, and Three.js is already a dependency for the graph cinema, so the optional top tier reuses existing infra. One self-contained component, one baked asset, zero new runtime dependencies for the baseline.", "buildSteps": [ "1. Build-time generator: add `apps/dashboard/scripts/gen-neural-sign.mjs`. Use node-canvas (or @napi-rs/canvas) to render each line ('VESTIGE', 'JULY 14TH', 'SIGN UP NOW') in a heavy font (Inter 900 / Orbitron) to an offscreen canvas; getImageData; collect alpha>128 pixels as attractors (step 3-4px) + a sparse ring of attractors just outside the outline for outward tendrils.", "2. Run space colonization (Jason Webb's algorithm) per line: KDBush for nearest-node queries, ATTRACT_DIST~24, KILL_DIST~6, SEG~4 in glyph-pixel space; seed roots at lowest inside-pixel of each stroke; iterate until attractors exhausted. Point-in-polygon clamp against the glyph outline so growth fills letters; allow the outside-ring attractors to pull a few tendrils past the edges.", "3. Post-process the branch graph: walk tip→root accumulating descendant count → per-edge stroke-width (thick trunks, thin tips). Mark synapse nodes = junctions (degree>=3) + tips. Compute per-branch depth-from-root.", "4. Emit ONE `static/vestige-neural.svg`: a per letter; each branch as with `stroke-width`, `stroke` cycling green #39ff9d / cyan #22d3ee / violet #b388ff, and inline `style=\"--depth:N\"`; each synapse as with `style=\"--i:K\"`. Inline the bloom + turbulence defs. Also export `vestige-streak-anchors.json` for the burst layer. Commit both to static/.", "5. Pre-bake a radial energy-burst PNG (`static/energy-burst.png`) for the streaks-behind layer (transparent, additive-friendly) — either offline Canvas2D additive wedges or one good AI render; it's purely decorative backing.", "6. Create `src/lib/components/NeuralSign.svelte`. Inline the SVG (import as raw string via `?raw` so CSS custom props/filters animate) OR if you don't need per-element animation — inline is required for dashoffset/pulse, so inline it. Wrap in a fixed container above the proof-row.", "7. Add the CSS life layer in the component: stroke-dashoffset draw-on keyed off `--depth`, synapse pulse keyed off `--i`, mask-sweep on the .streaks layer, and a `@media (prefers-reduced-motion: reduce)` block that freezes everything to a static glowing state.", "8. Gate the breathing turbulence + (optional) WebGL particle canvas behind a desktop/capability check; lazy-import Three.js only when mounting the top tier so mobile never downloads it. Pause all rAF when the sign scrolls out of view (IntersectionObserver).", "9. Place as a fixed element at top of `src/routes/waitlist/+page.svelte` above the stats/proof row; ensure z-index sits above the existing memory-field canvas but below interactive nav. Verify with `pnpm --filter @vestige/dashboard build` and a real device/Chrome mobile emulation that it's readable and 60fps.", "10. Add the three lines as data so the generator can re-run if copy changes: re-running the script is the ONLY step needed to update the artwork — no runtime change." ], "keyCode": "// === BUILD TIME: scripts/gen-neural-sign.mjs (space colonization, the core) ===\nimport { createCanvas } from '@napi-rs/canvas';\nimport KDBush from 'kdbush';\nconst SEG = 4, ATTRACT = 24, KILL = 6;\nfunction growLine(text, fontPx) {\n const W = text.length * fontPx * 0.75, H = fontPx * 1.4;\n const cv = createCanvas(W, H), ctx = cv.getContext('2d');\n ctx.font = `900 ${fontPx}px Inter`; ctx.fillStyle = '#fff';\n ctx.fillText(text, 0, fontPx);\n const px = ctx.getImageData(0, 0, W, H).data;\n const inside = [], outline = [];\n for (let y = 0; y < H; y += 3) for (let x = 0; x < W; x += 3) {\n const a = px[(y * W + x) * 4 + 3] > 128;\n if (a) { inside.push({ x, y });\n // outline test: any 4-neighbor empty\n const nb = i => px[i * 4 + 3] <= 128;\n if (nb((y*W)+x+1)||nb((y*W)+x-1)||nb(((y+1)*W)+x)||nb(((y-1)*W)+x)) outline.push({x,y}); }\n }\n // attractors = inside pixels + sparse halo just outside (outward tendrils)\n let attractors = inside.slice();\n for (const o of outline) if (Math.random() < 0.15)\n attractors.push({ x: o.x + (Math.random()-0.5)*30, y: o.y + (Math.random()-0.5)*30 });\n // roots: lowest inside-pixel per x-band -> letter stems\n const nodes = [];\n const bands = {};\n for (const p of inside) { const b = (p.x/20)|0; if (!bands[b] || p.y > bands[b].y) bands[b] = p; }\n for (const k in bands) nodes.push({ x: bands[k].x, y: bands[k].y, parent: -1, w: 1 });\n // grow\n for (let it = 0; it < 400 && attractors.length; it++) {\n const idx = new KDBush(nodes.length); for (const n of nodes) idx.add(n.x, n.y); idx.finish();\n const inf = nodes.map(() => []);\n for (const a of attractors) {\n let best = -1, bd = ATTRACT * ATTRACT;\n for (const id of idx.within(a.x, a.y, ATTRACT)) {\n const d = (nodes[id].x-a.x)**2 + (nodes[id].y-a.y)**2; if (d < bd) { bd = d; best = id; } }\n if (best >= 0) inf[best].push(a);\n }\n const fresh = [];\n nodes.forEach((n, i) => {\n if (!inf[i].length) return;\n let dx = 0, dy = 0; for (const a of inf[i]) { dx += a.x-n.x; dy += a.y-n.y; }\n const L = Math.hypot(dx, dy) || 1;\n fresh.push({ x: n.x + SEG*dx/L + (Math.random()-0.5),\n y: n.y + SEG*dy/L + (Math.random()-0.5), parent: i, w: 1 });\n });\n const before = nodes.length; nodes.push(...fresh);\n attractors = attractors.filter(a => !nodes.some((n, i) => i >= before-200 &&\n (n.x-a.x)**2 + (n.y-a.y)**2 < KILL*KILL));\n }\n // thickness via descendant count\n for (let i = nodes.length-1; i > 0; i--) if (nodes[i].parent >= 0)\n nodes[nodes[i].parent].w += nodes[i].w * 0.06;\n return { nodes };\n}\n// emit SVG: per-branch with --depth, synapse with --i (omitted for brevity)\n\n/* === RUNTIME: NeuralSign.svelte CSS — the cheap \"alive\" layer === */\n/*\n.dendrite { stroke-dasharray: var(--len); stroke-dashoffset: var(--len);\n animation: draw 1.6s ease-out forwards; animation-delay: calc(var(--depth) * 60ms); }\n@keyframes draw { to { stroke-dashoffset: 0; } }\n.synapse { transform-origin: center; animation: fire 2.4s ease-in-out infinite;\n animation-delay: calc(var(--i) * -0.137s); }\n@keyframes fire { 0%,100%{ r:1.5; opacity:.5 } 50%{ r:3; opacity:1 } }\n.streaks { background: url(/vestige/energy-burst.png) center/cover; mix-blend-mode: screen;\n -webkit-mask-image: linear-gradient(120deg,transparent 0 30%,#000 50%,transparent 70%);\n mask-size: 300% 100%; animation: sweep 6s linear infinite; }\n@keyframes sweep { to { mask-position: -200% 0 } }\n@media (prefers-reduced-motion: reduce) {\n .dendrite { stroke-dashoffset: 0; animation: none }\n .synapse, .streaks { animation: none }\n}\n*/\n\n/* === GLOW + BREATHING FILTER (inline in the baked SVG ) === */\n/*\n\n \n \n \n \n \n \n \n \n\n // desktop-only, applied before #bloom\n \n \n \n \n\n*/", "palette": "Near-black base #05060a (matches existing waitlist memory-field). Tendril stroke colors cycle: green #39ff9d, cyan #22d3ee, violet #b388ff. Bloom tiers recolored via feColorMatrix: inner green (.22/1/.62), mid cyan (.13/.82/1), outer violet (.70/.42/1), hot-white cores via additive overlap. Synapse nodes white-cyan core (#cffff0) bleeding to cyan halo. Energy-burst backing: cyan-green core → violet mid → green-transparent tips. Keep the bg pure dark so mix-blend-mode:screen makes only the luminous strokes/streaks read.", "animationDetails": "On mount: dendrites DRAW ON via stroke-dashoffset animating len→0, staggered by `--depth * 60ms` so branches grow outward from each letter's stem to its tips (radiating growth, ~1.6s total). Continuously: synapse s PULSE (r 1.5↔3, opacity .5↔1) on a 2.4s loop with per-node `--i * -0.137s` negative delay for organic, non-synchronized firing. A narrow bright band SWEEPS across the energy-burst backing layer every 6s via animated mask-position, reading as light pulsing outward behind the letters (mix-blend-mode:screen). On desktop only, a gentle feTurbulence baseFrequency animation (0.018↔0.022 over 9s, scale~14) makes the whole tendril mass BREATHE/writhe like living tissue. Optional WebGL tier: curl-noise particles crawl ALONG the strands and a second emitter shoots streaks outward from outline anchors then springs back. All animation freezes to a static glowing state under prefers-reduced-motion, and rAF/SMIL pauses when the sign scrolls out of the viewport.", "mobilePerfNotes": [ "Baseline is pure SVG + CSS compositor props (dashoffset, opacity, mask-position) — 60fps on low-end phones with zero WebGL; the only one-time cost is initial bloom-blur rasterization.", "Do NOT ship feTurbulence/feDisplacementMap on mobile: per-frame Perlin + displacement re-raster is the cost center. Gate it behind a desktop check AND prefers-reduced-motion:no-preference; mobile gets a static seed (no ).", "Never download Three.js on mobile: lazy dynamic-import the optional WebGL particle tier only after a capability + viewport-width check passes, so the mobile bundle stays SVG-only.", "Cap the synapse count animated on mobile (animate only junction/tip nodes, ~40-80 max) — thousands of simultaneously-animating s thrash the compositor.", "Use IntersectionObserver to pause CSS animations + any rAF when the fixed sign scrolls out of view; the page is long, so this saves battery once the user scrolls past the hero.", "Pre-bake the energy-burst as a single PNG so the streak layer is one GPU-composited texture (background-position/mask animation), not per-frame trig.", "Inline the SVG (not ) so animations work, but keep total path count reasonable (~200-600 branch segments per line) — decimate branches in the generator with a min-length prune so the DOM stays light.", "Reference all static assets via `base` from $app/paths (e.g. `${base}/energy-burst.png`) so they resolve under the GitHub Pages /vestige subpath." ], "fallback": "If space-colonization tuning eats too much time before the July 14 launch, ship the SVG glow+life layer on the GLYPH OUTLINE itself: render VESTIGE as a single-stroke vector path (opentype.js getPath / text outline), apply the SAME bloom + breathe filters and the SAME dashoffset draw-on + mask sweep, and scatter a hand-placed set of synapse s along the strokes. This drops the true branch topology but keeps the alive/glowing/synapse/energy-streak reference look with an hour of work instead of a day, and is a drop-in upgrade path (swap the outline s for the grown dendrite s later, same component, same CSS). Ultimate fallback: a static glowing PNG behind prefers-reduced-motion / no-JS, so the sign always renders something on-brand." }, "sources": [ "https://tympanus.net/codrops/2022/11/08/3d-typing-effects-with-three-js/", "https://medium.com/@jason.webb/space-colonization-algorithm-in-javascript-6f683b743dc5", "https://github.com/jasonwebb/2d-diffusion-limited-aggregation-experiments/blob/master/README.md", "https://css-tricks.com/svg-line-animation-works/", "https://tympanus.net/codrops/2025/12/10/simulating-life-in-the-browser-creating-a-living-particle-system-for-the-untillabs-website/", "https://code.tutsplus.com/how-to-generate-shockingly-good-2d-lightning-effects--gamedev-2681t", "https://github.com/mapbox/tiny-sdf (also SDF text rationale: https://github.com/Chlumsky/msdfgen)", "https://inspirnathan.com/posts/65-glow-shader-in-shadertoy/ (glow=0.01/d, clamp, additive) + sdSegment: https://iquilezles.org/articles/distfunctions2d/", "https://www.shadertoy.com/view/ldjXDD (FBM Lightning Bolt) + https://godotshaders.com/shader/2d-lightning-electric-arc-plasma/", "GLSL flow (fract/mix): https://frontend.horse/articles/making-dynamic-animations-with-shaders/ • SVG chunky-gradient: https://gist.github.com/shshaw/e87dbdfbe9c327b5bfdca2319f709172", "https://github.com/aadebdeb/study-three.js/blob/master/gpgpu-particles-with-curl-noise.html • https://al-ro.github.io/projects/embers/ (curl noise = Bridson 2007, divergence-free)", "Selective bloom: https://waelyasmina.net/articles/unreal-bloom-selective-threejs-post-processing/ + https://threejs.org/examples/webgl_postprocessing_unreal_bloom_selective.html • SVG multi-blur+flicker: https://dev.to/ninjasoards/make-a-flickering-neon-svg-animation-from-scratch-w-illustrator-react-emotion-39gm", "https://blakecrosley.com/blog/glsl-shader-lab (atan/length polar + sin(N*angle) spokes + exp(-k*radius) glow); corona/ray references https://www.shadertoy.com/view/4Xt3Rs and https://www.shadertoy.com/view/lljGDt", "https://developer.nvidia.com/gpugems/gpugems3/part-ii-light-and-shadows/chapter-13-volumetric-light-scattering-post-process and https://github.com/Erkaman/glsl-godrays", "https://tympanus.net/codrops/2024/12/19/crafting-a-dreamy-particle-effect-with-three-js-and-gpgpu/ and https://github.com/NewKrok/three-particles (trail/ribbon renderer + WebGPU compute, 50k-350k particles)", "https://codepen.io/Nasdav/pen/LjjbLE (rotating spokes via transforms + globalCompositeOperation) and https://www.cssscript.com/canvas-particle-animation/ (trail/motion-streak params)", "https://medium.com/@jason.webb/space-colonization-algorithm-in-javascript-6f683b743dc5 (full JS algorithm) and https://github.com/jasonwebb/2d-space-colonization-experiments (canvas impl + SVG export via 'e' key, vein thickening + opacity blending)", "https://9elements.com/blog/creating-an-animated-svg-neon-light-effect/ (exact feOffset->feGaussianBlur->feColorMatrix->feMerge neon filter with per-tier color matrices + step-end flicker keyframes) and https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/feColorMatrix", "https://tympanus.net/codrops/2019/02/19/svg-filter-effects-creating-texture-with-feturbulence/ (feTurbulence+feDisplacementMap distortion, GSAP baseFrequency animation, fractalNoise vs turbulence, baseFrequency 0.02-0.2 guidance) and https://codepen.io/BeardedBear/pen/abvEKpz (live animated feTurbulence playground)", "https://www.sabatino.dev/recreating-the-ios-16-shimmer-effect/ (masked-image shimmer, mask-size 300% + animated mask-position) and https://www.smashingmagazine.com/2024/01/css-blurry-shimmer-effect/ (compositor-thread performance of gradient/position-animated shimmer)", "https://transparify.app/blog/midjourney-transparent-background (white-bg vs black-bg alpha recovery preserving neon glow + soft edges; standard premultiplied-alpha algebra) and https://geekycuriosity.substack.com/p/skip-the-background-remover-ideogram (Ideogram native transparent text generation, handles letterforms well)" ] } }