Liveweave: Generative AI Web Editor & HTML/CSS/JS Playground
Initializing
Liveweave
Web
expand_more
home
Home
Palette
Color Explorer
arrow_outward
Polyline
Graphics Editor
arrow_outward
frame_source
Python Playground
arrow_outward
build
Tools
expand_more
restart_alt
Load "Hello Weaver!"
post_add
Generate Lorem ipsum...
code
Format HTML
code_blocks
Format CSS
data_object
Format JavaScript
library_add
Library
expand_more
A
Algolia JS
Animate CSS
Apex Charts JS
B
Bulma CSS
Bootstrap
C
Chart JS
Chartist
Create JS
D
D3
Dojo
F
Foundation
Fullpage JS
G
Granim JS
Google Charts
H
Halfmoon
J
jQuery
M
Materialize
Moment JS
Masonry JS
Milligram CSS
P
Pure CSS
Primer CSS
Popper JS
Pattern CSS
Picnic CSS
R
React JS
Raphael JS
Raisin CSS
S
Semantic UI
Skeleton CSS
Spectre CSS
Tachyons CSS
T
Tailwind
Three JS
U
UI Kit
Vis JS
W
Water CSS
download
Download
expand_more
developer_mode
Download as HTML
folder_zip
Download as .ZIP
cloud_upload
Fork
account_circle
Login
settings
Settings
expand_more
Editor font
14
px
A
A
Night mode
Line number
Mini map
Word wrap
sync_alt
Reset Settings
smart_display
Run
left_panel_close
AI Assistant
dashboard
All
HTML
CSS
JS
visibility
Preview
bolt
Live Preview
terminal
JS Console
<!DOCTYPE html> <html> <head> <title>Particle generator art</title> </head> <body> <!-- Start your code here --> <div class="flow-app"> <header class="topbar"> <div class="brand"> <div class="brand-mark">〰</div> <div class="brand-name">Flow</div> <div class="brand-divider"></div> <div class="brand-subtitle">Generative Art Studio</div> </div> <div class="top-actions"> <button id="pauseBtn" class="top-btn">Ⅱ Pause</button> <button id="resetBtn" class="top-btn">↻ Reset</button> <button id="saveBtn" class="save-btn">↓ Save PNG</button> </div> </header> <main class="workspace"> <section class="art-area"> <canvas id="flowCanvas"></canvas> <div class="canvas-badge"> <span class="status-dot"></span> GENERATIVE CANVAS </div> <div class="canvas-hint"> <span>✥</span> Drag to disturb the flow </div> </section> <aside class="sidebar"> <div class="side-heading"> <div class="sparkle">✧</div> <div> <h2>Controls</h2> <p>Shape, move, and color the flow.</p> </div> </div> <div class="control-group"> <div class="control"> <div class="control-head"> <label for="density">Particle Density</label> <output id="densityOut">3,500</output> </div> <input id="density" type="range" min="500" max="9000" step="250" value="3500"> </div> <div class="control"> <div class="control-head"> <label for="strength">Flow Strength</label> <output id="strengthOut">0.70</output> </div> <input id="strength" type="range" min="0" max="2" step="0.05" value="0.7"> </div> <div class="control"> <div class="control-head"> <label for="turbulence">Turbulence</label> <output id="turbulenceOut">0.45</output> </div> <input id="turbulence" type="range" min="0" max="2" step="0.05" value="0.45"> </div> <div class="control"> <div class="control-head"> <label for="speed">Speed</label> <output id="speedOut">1.00×</output> </div> <input id="speed" type="range" min="0" max="3" step="0.05" value="1"> </div> <div class="control"> <div class="control-head"> <label for="fade">Trail Persistence</label> <output id="fadeOut">0.08</output> </div> <input id="fade" type="range" min="0.02" max="0.35" step="0.01" value="0.08"> </div> <div class="control"> <div class="control-head"> <label for="spread">Spread</label> <output id="spreadOut">1.00</output> </div> <input id="spread" type="range" min="0.3" max="2.5" step="0.05" value="1"> </div> </div> <div class="color-group"> <label class="group-label">Color Mode</label> <div class="mode-buttons"> <button class="mode-btn active" data-mode="aurora">Aurora</button> <button class="mode-btn" data-mode="fire">Fire</button> <button class="mode-btn" data-mode="ocean">Ocean</button> <button class="mode-btn" data-mode="pastel">Pastel</button> <button class="mode-btn" data-mode="custom">Custom</button> </div> <label class="group-label palette-label">Custom Palette</label> <div class="palette"> <input type="color" id="color1" value="#00e5ff"> <input type="color" id="color2" value="#a855f7"> <input type="color" id="color3" value="#ff4d9d"> <input type="color" id="color4" value="#ffd166"> </div> </div> <div class="toggle-group"> <label class="toggle-row"> <span>Glow effect</span> <input type="checkbox" id="glow" checked> <span class="switch"></span> </label> <label class="toggle-row"> <span>Particles</span> <input type="checkbox" id="particles" checked> <span class="switch"></span> </label> </div> <button id="randomBtn" class="random-btn">✦ Generate New Flow</button> </aside> </main> </div> <!-- End your code here --> </body> </html>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); * { box-sizing: border-box; } body { margin: 0; background: #080914; color: #f1f3ff; font-family: Inter, sans-serif; } button, input { font: inherit; } button { cursor: pointer; } .flow-app { padding: 18px; max-width: 1700px; margin: auto; } .topbar { display: flex; align-items: center; justify-content: space-between; gap: 20px; margin-bottom: 16px; } .brand, .top-actions { display: flex; align-items: center; gap: 12px; } .brand-mark { font-size: 38px; color: #d75aff; text-shadow: 0 0 20px #a855f7; } .brand-name { font-size: 29px; font-weight: 700; letter-spacing: -1px; } .brand-divider { height: 28px; width: 1px; background: #33364e; margin: 0 5px; } .brand-subtitle { color: #a5abc4; font-size: 14px; } .top-btn, .save-btn { padding: 11px 17px; border: 1px solid #292d49; border-radius: 12px; background: #111426; color: #dce2ff; } .save-btn { background: linear-gradient(135deg, #9a45ff, #6d42d8); border-color: #a855f7; color: white; box-shadow: 0 0 18px #8b5cf633; } .workspace { display: grid; grid-template-columns: minmax(0, 1fr) 350px; gap: 16px; } .art-area { position: relative; height: min(78vh, 850px); min-height: 450px; overflow: hidden; background: #03050c; border: 1px solid #272344; border-radius: 18px; box-shadow: 0 0 40px #0005; } #flowCanvas { display: block; width: 100%; height: 100%; cursor: crosshair; } .canvas-badge, .canvas-hint { position: absolute; pointer-events: none; background: #0c1022cc; border: 1px solid #2d3151; border-radius: 30px; padding: 10px 14px; color: #b9c1df; font-size: 10px; letter-spacing: 1px; backdrop-filter: blur(8px); } .canvas-badge { top: 15px; left: 15px; } .status-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: #60ffc1; box-shadow: 0 0 10px #60ffc1; margin-right: 7px; } .canvas-hint { bottom: 15px; left: 15px; font-size: 12px; letter-spacing: 0; color: #e1e5ff; } .canvas-hint span { color: #d878ff; margin-right: 8px; } .sidebar { background: linear-gradient(145deg, #111426, #090b16); border: 1px solid #30234c; border-radius: 18px; padding: 20px; } .side-heading { display: flex; gap: 12px; align-items: center; padding-bottom: 18px; border-bottom: 1px solid #292b45; } .sparkle { font-size: 28px; color: #d65aff; } .side-heading h2 { margin: 0; font-size: 21px; } .side-heading p { margin: 6px 0 0; color: #818aa8; font-size: 12px; } .control-group { padding: 18px 0; border-bottom: 1px solid #292b45; } .control { margin-bottom: 22px; } .control:last-child { margin-bottom: 0; } .control-head { display: flex; justify-content: space-between; margin-bottom: 11px; font-size: 12px; } .control-head output { color: #e4baff; font-variant-numeric: tabular-nums; } input[type="range"] { width: 100%; height: 5px; border-radius: 10px; appearance: none; background: #30344e; outline: none; cursor: pointer; } input[type="range"]::-webkit-slider-thumb { appearance: none; width: 15px; height: 15px; border-radius: 50%; background: #fff; border: 3px solid #b85aff; box-shadow: 0 0 10px #c45aff99; } .group-label { display: block; font-size: 12px; margin: 18px 0 12px; color: #d5d9ee; } .mode-buttons { display: flex; flex-wrap: wrap; gap: 6px; } .mode-btn { padding: 9px 10px; border: 1px solid #292d49; background: #101426; border-radius: 8px; color: #9fa8c5; font-size: 11px; } .mode-btn.active { color: white; border-color: #cf70ff; background: #33204b; box-shadow: 0 0 12px #bb55ff33; } .palette { display: flex; justify-content: space-between; gap: 10px; } .palette input { width: 38px; height: 38px; padding: 0; border: 2px solid #333752; border-radius: 50%; overflow: hidden; background: none; cursor: pointer; } .palette input::-webkit-color-swatch-wrapper { padding: 0; } .palette input::-webkit-color-swatch { border: none; border-radius: 50%; } .toggle-group { padding: 15px 0; } .toggle-row { display: flex; align-items: center; justify-content: space-between; margin: 15px 0; color: #d5d9ee; font-size: 13px; cursor: pointer; } .toggle-row input { display: none; } .switch { width: 38px; height: 21px; background: #30344e; border-radius: 20px; position: relative; transition: 0.2s; } .switch::after { content: ""; position: absolute; width: 15px; height: 15px; top: 3px; left: 3px; border-radius: 50%; background: #aaaec4; transition: 0.2s; } .toggle-row input:checked + .switch { background: #923fff; } .toggle-row input:checked + .switch::after { transform: translateX(17px); background: white; } .random-btn { width: 100%; padding: 13px; border: 1px solid #8c48d9; border-radius: 10px; background: linear-gradient(120deg, #47256a, #24183c); color: #e9c8ff; font-size: 13px; } @media (max-width: 1000px) { .workspace { grid-template-columns: 1fr; } .art-area { height: 60vh; } .sidebar { order: 2; } } @media (max-width: 600px) { .flow-app { padding: 8px; } .topbar { flex-wrap: wrap; } .brand-subtitle, .brand-divider { display: none; } .top-actions { gap: 5px; } .top-btn, .save-btn { padding: 9px; font-size: 12px; } .art-area { min-height: 350px; height: 50vh; } }
const canvas = document.getElementById("flowCanvas"); const ctx = canvas.getContext("2d", { alpha: false }); const $ = id => document.getElementById(id); const controls = { density: $("density"), strength: $("strength"), turbulence: $("turbulence"), speed: $("speed"), fade: $("fade"), spread: $("spread") }; const outputs = { density: $("densityOut"), strength: $("strengthOut"), turbulence: $("turbulenceOut"), speed: $("speedOut"), fade: $("fadeOut"), spread: $("spreadOut") }; const palettes = { aurora: ["#00e5ff", "#a855f7", "#ff4d9d", "#ffd166"], fire: ["#ff3b1d", "#ff7b00", "#ffc400", "#fff1a6"], ocean: ["#004cff", "#00b8ff", "#00ffe0", "#a2fff6"], pastel: ["#ffb3de", "#b9a7ff", "#a3e8ff", "#fff0b3"], custom: ["#00e5ff", "#a855f7", "#ff4d9d", "#ffd166"] }; let colorMode = "aurora"; let particles = []; let running = true; let elapsed = 0; let lastTime = 0; let width = 0; let height = 0; let dpr = 1; let seed = Math.random() * 1000; const pointer = { x: 0, y: 0, active: false, down: false, radius: 140 }; // Precomputed vector field grid const GRID_X = 64; const GRID_Y = 40; let fieldX = new Float32Array(GRID_X * GRID_Y); let fieldY = new Float32Array(GRID_X * GRID_Y); function resize() { const rect = canvas.getBoundingClientRect(); width = rect.width; height = rect.height; dpr = Math.min(window.devicePixelRatio || 1, 1.5); canvas.width = Math.floor(width * dpr); canvas.height = Math.floor(height * dpr); ctx.setTransform(dpr, 0, 0, dpr, 0, 0); createParticles(); clearCanvas(); buildField(); } window.addEventListener("resize", resize); function clearCanvas() { ctx.fillStyle = "#03050c"; ctx.fillRect(0, 0, width, height); } function createParticle() { return { x: Math.random() * width, y: Math.random() * height, age: Math.random() * 200, life: 300 + Math.random() * 600, color: Math.floor(Math.random() * 4), size: 0.5 + Math.random() * 0.9 }; } function createParticles() { // Performance guard: prevent excessive particle counts. const count = Math.min(Number(controls.density.value), 6000); particles = new Array(count); for (let i = 0; i < count; i++) { particles[i] = createParticle(); } } function getColors() { if (colorMode === "custom") { return [ $("color1").value, $("color2").value, $("color3").value, $("color4").value ]; } return palettes[colorMode]; } // Precompute the flow field. // Trigonometry is now calculated only 2,560 times, // instead of thousands of times for every particle. function buildField() { const spread = Number(controls.spread.value); const turbulence = Number(controls.turbulence.value); const strength = Number(controls.strength.value); for (let gy = 0; gy < GRID_Y; gy++) { for (let gx = 0; gx < GRID_X; gx++) { const nx = gx / GRID_X; const ny = gy / GRID_Y; const px = nx * spread * 5; const py = ny * spread * 5; const angle = Math.sin(px * 1.4 + elapsed * 0.23 + seed) * Math.cos(py * 1.1 - elapsed * 0.19) * 2.4 + Math.sin((px + py) * 0.8 + elapsed * 0.15) * 1.4 + Math.cos(py * 2 - px * 0.7 + elapsed * 0.12) * turbulence * 2.5; const index = gy * GRID_X + gx; fieldX[index] = Math.cos(angle) * strength; fieldY[index] = Math.sin(angle) * strength; } } } // Bilinear interpolation through the precomputed field. function getVector(x, y) { const gx = Math.max(0, Math.min(GRID_X - 1.001, (x / width) * GRID_X)); const gy = Math.max(0, Math.min(GRID_Y - 1.001, (y / height) * GRID_Y)); const x0 = Math.floor(gx); const y0 = Math.floor(gy); const x1 = x0 + 1; const y1 = y0 + 1; const tx = gx - x0; const ty = gy - y0; const i00 = y0 * GRID_X + x0; const i10 = y0 * GRID_X + x1; const i01 = y1 * GRID_X + x0; const i11 = y1 * GRID_X + x1; const vx = fieldX[i00] * (1 - tx) * (1 - ty) + fieldX[i10] * tx * (1 - ty) + fieldX[i01] * (1 - tx) * ty + fieldX[i11] * tx * ty; const vy = fieldY[i00] * (1 - tx) * (1 - ty) + fieldY[i10] * tx * (1 - ty) + fieldY[i01] * (1 - tx) * ty + fieldY[i11] * tx * ty; return [vx, vy]; } function applyPointer(vx, vy, x, y) { if (!pointer.active) return [vx, vy]; const dx = x - pointer.x; const dy = y - pointer.y; const dist2 = dx * dx + dy * dy; const radius2 = pointer.radius * pointer.radius; if (dist2 >= radius2 || dist2 < 1) return [vx, vy]; const dist = Math.sqrt(dist2); const influence = (1 - dist / pointer.radius) * (pointer.down ? 3.5 : 1.3); return [ vx - (dy / dist) * influence, vy + (dx / dist) * influence ]; } function draw(timestamp) { if (!lastTime) lastTime = timestamp; const delta = Math.min((timestamp - lastTime) / 1000, 0.04); lastTime = timestamp; if (running) { elapsed += delta * Number(controls.speed.value); // Fade old artwork. ctx.fillStyle = `rgba(3,5,12,${controls.fade.value})`; ctx.fillRect(0, 0, width, height); // Update the flow field only about 30 times per second. if (!draw.lastFieldTime || timestamp - draw.lastFieldTime > 33) { buildField(); draw.lastFieldTime = timestamp; } const glow = $("glow").checked; const showParticles = $("particles").checked; const colors = getColors(); const paths = [[], [], [], []]; const velocity = Number(controls.speed.value) * 65; // Update particles and collect line segments. for (let i = 0; i < particles.length; i++) { const p = particles[i]; const oldX = p.x; const oldY = p.y; let [vx, vy] = getVector(p.x, p.y); [vx, vy] = applyPointer(vx, vy, p.x, p.y); p.x += vx * velocity * delta; p.y += vy * velocity * delta; p.age += delta * 60; if ( p.x < 0 || p.x > width || p.y < 0 || p.y > height || p.age > p.life ) { Object.assign(p, createParticle()); continue; } if (showParticles) { paths[p.color].push(oldX, oldY, p.x, p.y); } } ctx.globalCompositeOperation = glow ? "lighter" : "source-over"; // Batch many particle segments into just four draw calls. for (let c = 0; c < 4; c++) { const segments = paths[c]; if (!segments.length) continue; ctx.beginPath(); for (let i = 0; i < segments.length; i += 4) { ctx.moveTo(segments[i], segments[i + 1]); ctx.lineTo(segments[i + 2], segments[i + 3]); } ctx.strokeStyle = colors[c]; ctx.lineWidth = 1; ctx.globalAlpha = 0.35; // Glow is applied per color batch, not per particle. ctx.shadowBlur = glow ? 8 : 0; ctx.shadowColor = colors[c]; ctx.stroke(); } ctx.globalAlpha = 1; ctx.shadowBlur = 0; ctx.globalCompositeOperation = "source-over"; } requestAnimationFrame(draw); } function updateOutputs() { outputs.density.textContent = Number(controls.density.value).toLocaleString(); outputs.strength.textContent = Number(controls.strength.value).toFixed(2); outputs.turbulence.textContent = Number(controls.turbulence.value).toFixed(2); outputs.speed.textContent = Number(controls.speed.value).toFixed(2) + "×"; outputs.fade.textContent = Number(controls.fade.value).toFixed(2); outputs.spread.textContent = Number(controls.spread.value).toFixed(2); } Object.values(controls).forEach(input => { input.addEventListener("input", () => { updateOutputs(); if (input === controls.density) { createParticles(); } }); }); document.querySelectorAll(".mode-btn").forEach(button => { button.addEventListener("click", () => { document.querySelectorAll(".mode-btn").forEach(b => b.classList.remove("active") ); button.classList.add("active"); colorMode = button.dataset.mode; }); }); ["color1", "color2", "color3", "color4"].forEach(id => { $(id).addEventListener("input", () => { colorMode = "custom"; document.querySelectorAll(".mode-btn").forEach(b => b.classList.toggle("active", b.dataset.mode === "custom") ); }); }); function updatePointer(event) { const rect = canvas.getBoundingClientRect(); pointer.x = event.clientX - rect.left; pointer.y = event.clientY - rect.top; pointer.active = true; } canvas.addEventListener("pointerdown", event => { pointer.down = true; updatePointer(event); }); canvas.addEventListener("pointermove", updatePointer); canvas.addEventListener("pointerup", () => { pointer.down = false; }); canvas.addEventListener("pointerleave", () => { pointer.active = false; pointer.down = false; }); $("pauseBtn").addEventListener("click", () => { running = !running; $("pauseBtn").textContent = running ? "Ⅱ Pause" : "▶ Resume"; if (running) lastTime = 0; }); function resetSettings() { controls.density.value = 3500; controls.strength.value = 0.7; controls.turbulence.value = 0.45; controls.speed.value = 1; controls.fade.value = 0.08; controls.spread.value = 1; colorMode = "aurora"; running = true; elapsed = 0; seed = Math.random() * 1000; $("pauseBtn").textContent = "Ⅱ Pause"; document.querySelectorAll(".mode-btn").forEach(button => { button.classList.toggle("active", button.dataset.mode === "aurora"); }); updateOutputs(); createParticles(); clearCanvas(); buildField(); } $("resetBtn").addEventListener("click", resetSettings); $("randomBtn").addEventListener("click", () => { seed = Math.random() * 10000; elapsed = 0; createParticles(); clearCanvas(); buildField(); }); $("saveBtn").addEventListener("click", () => { const link = document.createElement("a"); link.download = "flow-generative-art.png"; link.href = canvas.toDataURL("image/png"); link.click(); }); updateOutputs(); resize(); requestAnimationFrame(draw);
terminal
JavaScript Console
Preview
Clear
close
›
Run