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>Kinetic particle simulator</title> </head> <body> <!-- Start your code here --> <div class="kinetic-app"> <header class="topbar"> <div class="brand"> <div class="brand-mark"> <span></span><span></span><span></span> </div> <div> <h1>KINETIC<span>.</span></h1> <p>INTERACTIVE PARTICLE LAB</p> </div> </div> <div class="status"> <span class="status-dot"></span> LIVE SIMULATION </div> </header> <main class="workspace"> <section class="simulation-panel"> <div class="panel-top"> <div> <span class="eyebrow">PLAYGROUND / 001</span> <h2>Motion is everything.</h2> <p>Move your pointer. Disturb the universe.</p> </div> <div class="sim-indicator"> <span class="pulse"></span> RUNNING </div> </div> <div class="canvas-container"> <canvas id="particleCanvas"></canvas> <div class="canvas-label top-left"> <span class="small-dot"></span> REAL-TIME PHYSICS </div> <div class="canvas-label top-right"> <span id="modeLabel">ATTRACT MODE</span> </div> <div class="canvas-crosshair"> <span>+</span> </div> <div class="canvas-bottom"> <span>MOVE TO INTERACT</span> <span>CLICK OR TAP TO BURST</span> </div> </div> <div class="simulation-footer"> <div class="stat"> <span class="stat-label">PARTICLES</span> <strong id="particleStat">110</strong> </div> <div class="stat"> <span class="stat-label">GRAVITY</span> <strong id="gravityStat">0</strong> </div> <div class="stat"> <span class="stat-label">INTERACTION</span> <strong id="interactionStat">ATTRACT</strong> </div> <div class="stat"> <span class="stat-label">ENGINE</span> <strong>CANVAS 2D</strong> </div> </div> </section> <aside class="controls-panel"> <div class="controls-header"> <span class="eyebrow">CONTROL CENTER</span> <h2>Shape the motion.</h2> <p>Experiment with forces, motion, and color.</p> </div> <div class="control-group"> <div class="group-title"> <h3>INTERACTION MODE</h3> <span>01</span> </div> <div class="mode-grid"> <button class="mode-btn active" data-mode="attract" type="button"> <span class="mode-icon">◎</span> <strong>Attract</strong> <small>Pull inward</small> </button> <button class="mode-btn" data-mode="repel" type="button"> <span class="mode-icon">✳</span> <strong>Repel</strong> <small>Push outward</small> </button> <button class="mode-btn" data-mode="swirl" type="button"> <span class="mode-icon">◉</span> <strong>Swirl</strong> <small>Orbit around</small> </button> </div> </div> <div class="control-group"> <div class="group-title"> <h3>GRAVITY</h3> <span>02</span> </div> <div class="slider-heading"> <span>Vertical force</span> <strong id="gravityValue">0</strong> </div> <input id="gravitySlider" type="range" min="-100" max="100" value="0"> <div class="range-labels"> <span>UPWARD</span> <span>NEUTRAL</span> <span>DOWNWARD</span> </div> </div> <div class="control-group"> <div class="group-title"> <h3>PARTICLE DENSITY</h3> <span>03</span> </div> <div class="slider-heading"> <span>Particle count</span> <strong id="countValue">110</strong> </div> <input id="countSlider" type="range" min="30" max="250" step="10" value="110"> <div class="range-labels"> <span>MINIMAL</span> <span>DENSE</span> </div> </div> <div class="control-group"> <div class="group-title"> <h3>COLOR PALETTE</h3> <span>04</span> </div> <div class="palette-grid"> <button class="palette-btn active" data-palette="cosmic" type="button" aria-label="Cosmic palette"> <span class="palette cosmic"></span> </button> <button class="palette-btn" data-palette="ocean" type="button" aria-label="Ocean palette"> <span class="palette ocean"></span> </button> <button class="palette-btn" data-palette="fire" type="button" aria-label="Fire palette"> <span class="palette fire"></span> </button> <button class="palette-btn" data-palette="forest" type="button" aria-label="Forest palette"> <span class="palette forest"></span> </button> <button class="palette-btn" data-palette="mono" type="button" aria-label="Monochrome palette"> <span class="palette mono"></span> </button> </div> </div> <div class="control-group"> <div class="group-title"> <h3>VISUAL EFFECTS</h3> <span>05</span> </div> <label class="toggle-row"> <span> <strong>Particle trails</strong> <small>Leave glowing motion paths</small> </span> <input type="checkbox" id="trailsToggle" checked> <span class="toggle-switch"></span> </label> <label class="toggle-row"> <span> <strong>Glow effect</strong> <small>Soft luminous particles</small> </span> <input type="checkbox" id="glowToggle" checked> <span class="toggle-switch"></span> </label> </div> <div class="action-buttons"> <button id="burstBtn" class="burst-btn" type="button"> ✳ Particle Burst </button> <button id="resetBtn" class="reset-btn" type="button"> ↺ Reset Simulation </button> </div> </aside> </main> <footer class="footer"> <span>KINETIC LAB / EXPERIMENT 001</span> <span>BUILT WITH HTML, CSS & JAVASCRIPT</span> </footer> </div> <!-- End your code here --> </body> </html>
:root { --bg: #090e19; --panel: #121a2a; --panel-alt: #172236; --border: #26344b; --text: #edf2ff; --muted: #8c9bb5; --accent: #a78bfa; --accent-light: #c4b5fd; } * { box-sizing: border-box; } body { margin: 0; background: var(--bg); color: var(--text); font-family: Arial, Helvetica, sans-serif; -webkit-font-smoothing: antialiased; } button, input { font: inherit; } button { cursor: pointer; } button:focus-visible, input:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; } .kinetic-app { max-width: 1440px; margin: 0 auto; padding: 30px; } /* HEADER */ .topbar, .brand, .status, .workspace, .panel-top, .sim-indicator, .canvas-bottom, .simulation-footer, .group-title, .slider-heading, .range-labels, .toggle-row, .footer { display: flex; align-items: center; justify-content: space-between; } .topbar { margin-bottom: 30px; gap: 20px; } .brand { justify-content: flex-start; gap: 15px; } .brand-mark { position: relative; width: 46px; height: 46px; border: 1px solid #51437c; background: #211b3b; border-radius: 13px; } .brand-mark span { position: absolute; border-radius: 50%; background: var(--accent); box-shadow: 0 0 12px var(--accent); } .brand-mark span:nth-child(1) { width: 9px; height: 9px; top: 12px; left: 11px; } .brand-mark span:nth-child(2) { width: 6px; height: 6px; top: 25px; left: 24px; } .brand-mark span:nth-child(3) { width: 4px; height: 4px; top: 10px; left: 30px; } .brand h1 { margin: 0; font-size: 23px; letter-spacing: 2px; } .brand h1 span { color: var(--accent); } .brand p { margin: 5px 0 0; color: var(--muted); font-size: 10px; letter-spacing: 2px; } .status { justify-content: flex-start; gap: 9px; padding: 10px 14px; border: 1px solid var(--border); border-radius: 30px; color: #aebdd1; font-size: 10px; letter-spacing: 1px; } .status-dot, .pulse, .small-dot { display: inline-block; width: 7px; height: 7px; background: #5fe2b7; border-radius: 50%; box-shadow: 0 0 12px #5fe2b788; } .pulse { animation: pulse 1.7s infinite; } @keyframes pulse { 50% { opacity: .35; } } /* MAIN LAYOUT */ .workspace { align-items: stretch; gap: 22px; } .simulation-panel { flex: 1; min-width: 0; overflow: hidden; background: var(--panel); border: 1px solid var(--border); border-radius: 18px; } .panel-top { gap: 16px; padding: 28px; } .eyebrow { font-size: 10px; font-weight: bold; letter-spacing: 1.7px; color: var(--accent); } .panel-top h2, .controls-header h2 { margin: 10px 0; font-size: 27px; letter-spacing: -.7px; } .panel-top p, .controls-header p { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.6; } .sim-indicator { justify-content: flex-start; gap: 8px; color: #a2b4cb; font-size: 10px; letter-spacing: 1px; } /* CANVAS */ .canvas-container { position: relative; margin: 0 18px; height: 460px; overflow: hidden; background: radial-gradient( ellipse at 50% 45%, #151e37 0%, #080d1b 75% ); border: 1px solid #273550; border-radius: 12px; } .canvas-container::before { content: ""; position: absolute; inset: 0; pointer-events: none; background-image: linear-gradient(#ffffff06 1px, transparent 1px), linear-gradient(90deg, #ffffff06 1px, transparent 1px); background-size: 35px 35px; } #particleCanvas { display: block; width: 100%; height: 100%; position: relative; z-index: 1; touch-action: none; cursor: crosshair; } .canvas-label { position: absolute; z-index: 2; top: 18px; font-size: 10px; letter-spacing: 1.2px; color: #8190b0; pointer-events: none; } .top-left { left: 20px; display: flex; align-items: center; gap: 8px; } .top-right { right: 20px; color: #c4b5fd; } .canvas-crosshair { position: absolute; z-index: 0; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #ffffff12; font-size: 80px; font-weight: 100; pointer-events: none; } .canvas-bottom { position: absolute; z-index: 2; bottom: 17px; left: 20px; right: 20px; color: #8291b0; font-size: 9px; letter-spacing: 1.1px; pointer-events: none; } /* STATISTICS */ .simulation-footer { padding: 24px 28px; gap: 14px; flex-wrap: wrap; } .stat { display: flex; flex-direction: column; gap: 8px; } .stat-label { color: #7889a5; font-size: 9px; letter-spacing: 1.4px; } .stat strong { font-size: 15px; font-weight: 600; } /* CONTROL PANEL */ .controls-panel { width: 345px; flex-shrink: 0; padding: 26px; background: var(--panel); border: 1px solid var(--border); border-radius: 18px; } .controls-header h2 { margin-top: 13px; } .control-group { margin-top: 26px; padding-top: 22px; border-top: 1px solid var(--border); } .group-title { margin-bottom: 17px; } .group-title h3 { margin: 0; color: #b1c0d6; font-size: 10px; letter-spacing: 1.5px; } .group-title > span { color: #53647e; font-size: 11px; } /* MODES */ .mode-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; } .mode-btn { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 16px 4px; background: #1a263a; border: 1px solid #30405a; border-radius: 10px; color: #aebdd4; transition: .2s; } .mode-btn:hover { border-color: var(--accent); } .mode-btn.active { background: #302647; border-color: var(--accent); color: #e2d8ff; } .mode-icon { font-size: 26px; line-height: 1; } .mode-btn strong { font-size: 12px; } .mode-btn small { color: #8292ad; font-size: 10px; } /* SLIDERS */ .slider-heading { margin-bottom: 10px; } .slider-heading span { color: #a1afc5; font-size: 12px; } .slider-heading strong { color: var(--accent-light); font-size: 13px; } input[type="range"] { display: block; width: 100%; margin: 20px 0 13px; accent-color: var(--accent); cursor: pointer; } .range-labels { gap: 8px; color: #6f809b; font-size: 9px; letter-spacing: .6px; } /* PALETTES */ .palette-grid { display: flex; gap: 10px; } .palette-btn { padding: 4px; width: 45px; height: 45px; border: 1px solid #35435d; border-radius: 11px; background: #1b2638; } .palette-btn.active { border: 2px solid var(--accent); } .palette { display: block; width: 100%; height: 100%; border-radius: 7px; } .cosmic { background: linear-gradient(135deg, #a78bfa, #67e8f9, #f9a8d4); } .ocean { background: linear-gradient(135deg, #38bdf8, #06b6d4, #1d4ed8); } .fire { background: linear-gradient(135deg, #f97316, #ef4444, #facc15); } .forest { background: linear-gradient(135deg, #34d399, #84cc16, #0d9488); } .mono { background: linear-gradient(135deg, #ffffff, #94a3b8, #475569); } /* TOGGLES */ .toggle-row { position: relative; padding: 10px 0; gap: 12px; cursor: pointer; } .toggle-row > span:first-child { display: flex; flex-direction: column; gap: 5px; } .toggle-row strong { font-size: 12px; font-weight: 500; } .toggle-row small { color: #8191aa; font-size: 10px; } .toggle-row input { position: absolute; right: 0; width: 40px; height: 24px; opacity: 0; cursor: pointer; } .toggle-switch { position: relative; width: 40px; height: 23px; flex-shrink: 0; border-radius: 20px; background: #354159; transition: background .2s; pointer-events: none; } .toggle-switch::after { content: ""; position: absolute; width: 17px; height: 17px; left: 3px; top: 3px; border-radius: 50%; background: white; transition: transform .2s; } .toggle-row input:checked + .toggle-switch { background: #8b5cf6; } .toggle-row input:checked + .toggle-switch::after { transform: translateX(17px); } .toggle-row input:focus-visible + .toggle-switch { outline: 2px solid var(--accent-light); outline-offset: 3px; } /* ACTIONS */ .action-buttons { display: grid; gap: 10px; margin-top: 28px; } .burst-btn, .reset-btn { width: 100%; padding: 14px; border-radius: 9px; font-size: 12px; font-weight: bold; transition: .2s; } .burst-btn { background: var(--accent); color: #17102d; border: none; } .burst-btn:hover { background: #c4b5fd; } .reset-btn { background: #202c40; color: #b6c4d7; border: 1px solid #34435b; } .reset-btn:hover { border-color: var(--accent); } /* FOOTER */ .footer { gap: 12px; margin-top: 28px; color: #667993; font-size: 10px; letter-spacing: 1.3px; } /* RESPONSIVE */ @media (max-width: 950px) { .workspace { flex-direction: column; } .controls-panel { width: 100%; } .canvas-container { height: 420px; } } @media (max-width: 560px) { .kinetic-app { padding: 12px; } .topbar { margin-bottom: 18px; } .brand h1 { font-size: 19px; } .brand p { font-size: 8px; } .status, .sim-indicator { display: none; } .panel-top { padding: 20px; } .panel-top h2 { font-size: 23px; } .canvas-container { margin: 0 10px; height: 360px; } .canvas-bottom { font-size: 8px; left: 12px; right: 12px; } .simulation-footer { padding: 20px; gap: 18px; } .stat { width: calc(50% - 12px); } .controls-panel { padding: 20px; } .footer { font-size: 8px; } } @media (prefers-reduced-motion: reduce) { .pulse { animation: none; } }
(function () { "use strict"; const canvas = document.getElementById("particleCanvas"); const ctx = canvas.getContext("2d"); const gravitySlider = document.getElementById("gravitySlider"); const countSlider = document.getElementById("countSlider"); const trailsToggle = document.getElementById("trailsToggle"); const glowToggle = document.getElementById("glowToggle"); const gravityValue = document.getElementById("gravityValue"); const countValue = document.getElementById("countValue"); const gravityStat = document.getElementById("gravityStat"); const particleStat = document.getElementById("particleStat"); const interactionStat = document.getElementById("interactionStat"); const modeLabel = document.getElementById("modeLabel"); const palettes = { cosmic: [ "#a78bfa", "#67e8f9", "#f9a8d4", "#fbbf24", "#6ee7b7" ], ocean: [ "#38bdf8", "#22d3ee", "#60a5fa", "#0ea5e9", "#a5f3fc" ], fire: [ "#f97316", "#ef4444", "#facc15", "#fb7185", "#fdba74" ], forest: [ "#34d399", "#84cc16", "#2dd4bf", "#4ade80", "#bef264" ], mono: [ "#ffffff", "#cbd5e1", "#94a3b8", "#64748b", "#e2e8f0" ] }; let width = 0; let height = 0; let particles = []; let mode = "attract"; let palette = "cosmic"; let gravity = 0; let trails = true; let glow = true; let lastTime = 0; const pointer = { x: 0, y: 0, active: false }; function random(min, max) { return min + Math.random() * (max - min); } function randomColor() { const colors = palettes[palette]; return colors[Math.floor(Math.random() * colors.length)]; } function createParticle(x, y) { return { x: x === undefined ? random(0, width) : x, y: y === undefined ? random(0, height) : y, vx: random(-1.2, 1.2), vy: random(-1.2, 1.2), radius: random(1.3, 3.5), color: randomColor() }; } function resizeCanvas() { const rect = canvas.getBoundingClientRect(); const dpr = Math.min(window.devicePixelRatio || 1, 2); width = rect.width; height = rect.height; canvas.width = Math.round(width * dpr); canvas.height = Math.round(height * dpr); ctx.setTransform(dpr, 0, 0, dpr, 0, 0); ctx.clearRect(0, 0, width, height); } function syncParticles() { const target = Number(countSlider.value); while (particles.length < target) { particles.push(createParticle()); } if (particles.length > target) { particles.length = target; } countValue.textContent = String(target); particleStat.textContent = String(target); } function recolorParticles() { particles.forEach(function (particle) { particle.color = randomColor(); }); } function setMode(nextMode) { mode = nextMode; document.querySelectorAll(".mode-btn").forEach( function (button) { const active = button.dataset.mode === mode; button.classList.toggle("active", active); button.setAttribute("aria-pressed", String(active)); } ); interactionStat.textContent = mode.toUpperCase(); modeLabel.textContent = mode.toUpperCase() + " MODE"; } function setPalette(nextPalette) { palette = nextPalette; document.querySelectorAll(".palette-btn").forEach( function (button) { const active = button.dataset.palette === palette; button.classList.toggle("active", active); button.setAttribute("aria-pressed", String(active)); } ); recolorParticles(); } function updateGravity() { gravity = Number(gravitySlider.value); const label = gravity > 0 ? "+" + gravity : String(gravity); gravityValue.textContent = label; gravityStat.textContent = label; } function burst(x, y) { particles.forEach(function (particle) { const dx = particle.x - x; const dy = particle.y - y; const distance = Math.max(1, Math.hypot(dx, dy)); if (distance < 220) { const strength = (1 - distance / 220) * 12; particle.vx += (dx / distance) * strength; particle.vy += (dy / distance) * strength; } }); } function updatePointer(event) { const rect = canvas.getBoundingClientRect(); pointer.x = event.clientX - rect.left; pointer.y = event.clientY - rect.top; pointer.active = true; } function applyPointerForce(particle, dt) { if (!pointer.active) return; const dx = pointer.x - particle.x; const dy = pointer.y - particle.y; const distance = Math.max( 25, Math.hypot(dx, dy) ); const radius = 230; if (distance > radius) return; const falloff = (radius - distance) / radius; const force = falloff * 0.38 * dt; if (mode === "attract") { particle.vx += (dx / distance) * force; particle.vy += (dy / distance) * force; } if (mode === "repel") { particle.vx -= (dx / distance) * force; particle.vy -= (dy / distance) * force; } if (mode === "swirl") { particle.vx += (-dy / distance) * force; particle.vy += (dx / distance) * force; } } function updateParticle(particle, dt) { applyPointerForce(particle, dt); // Positive gravity pulls downward. // Negative gravity pulls upward. particle.vy += gravity * 0.0025 * dt; // Friction prevents unlimited acceleration. const friction = Math.pow(0.994, dt); particle.vx *= friction; particle.vy *= friction; // Limit speed for stable animation. const speed = Math.hypot(particle.vx, particle.vy); const maxSpeed = 12; if (speed > maxSpeed) { particle.vx = particle.vx / speed * maxSpeed; particle.vy = particle.vy / speed * maxSpeed; } particle.x += particle.vx * dt; particle.y += particle.vy * dt; // Bounce against canvas edges. if (particle.x < particle.radius) { particle.x = particle.radius; particle.vx = Math.abs(particle.vx) * 0.85; } if (particle.x > width - particle.radius) { particle.x = width - particle.radius; particle.vx = -Math.abs(particle.vx) * 0.85; } if (particle.y < particle.radius) { particle.y = particle.radius; particle.vy = Math.abs(particle.vy) * 0.85; } if (particle.y > height - particle.radius) { particle.y = height - particle.radius; particle.vy = -Math.abs(particle.vy) * 0.85; } } function drawParticle(particle) { ctx.beginPath(); ctx.fillStyle = particle.color; if (glow) { ctx.shadowBlur = 14; ctx.shadowColor = particle.color; } else { ctx.shadowBlur = 0; } ctx.arc( particle.x, particle.y, particle.radius, 0, Math.PI * 2 ); ctx.fill(); } function animate(time) { const dt = Math.min( (time - lastTime) / 16.67 || 1, 2 ); lastTime = time; // Semi-transparent clearing produces trails. if (trails) { ctx.fillStyle = "rgba(8, 13, 27, 0.24)"; ctx.fillRect(0, 0, width, height); } else { ctx.clearRect(0, 0, width, height); } particles.forEach(function (particle) { updateParticle(particle, dt); drawParticle(particle); }); ctx.shadowBlur = 0; requestAnimationFrame(animate); } function resetSimulation() { mode = "attract"; palette = "cosmic"; gravity = 0; trails = true; glow = true; gravitySlider.value = "0"; countSlider.value = "110"; trailsToggle.checked = true; glowToggle.checked = true; pointer.active = false; setMode(mode); updateGravity(); particles = []; syncParticles(); setPalette(palette); ctx.clearRect(0, 0, width, height); } // Mouse and touch interactions. canvas.addEventListener("pointermove", updatePointer); canvas.addEventListener("pointerdown", function (event) { updatePointer(event); burst(pointer.x, pointer.y); }); canvas.addEventListener("pointerleave", function () { pointer.active = false; }); canvas.addEventListener("pointerup", function (event) { if (event.pointerType !== "mouse") { pointer.active = false; } }); canvas.addEventListener("pointercancel", function () { pointer.active = false; }); // Interaction modes. document.querySelectorAll(".mode-btn").forEach( function (button) { button.addEventListener("click", function () { setMode(button.dataset.mode); }); } ); // Color palettes. document.querySelectorAll(".palette-btn").forEach( function (button) { button.addEventListener("click", function () { setPalette(button.dataset.palette); }); } ); gravitySlider.addEventListener("input", updateGravity); countSlider.addEventListener("input", syncParticles); trailsToggle.addEventListener("change", function () { trails = trailsToggle.checked; ctx.clearRect(0, 0, width, height); }); glowToggle.addEventListener("change", function () { glow = glowToggle.checked; }); document.getElementById("burstBtn").addEventListener( "click", function () { burst(width / 2, height / 2); } ); document.getElementById("resetBtn").addEventListener( "click", resetSimulation ); window.addEventListener("resize", resizeCanvas); // Initialize. resizeCanvas(); resetSimulation(); requestAnimationFrame(animate); })();
terminal
JavaScript Console
Preview
Clear
close
›
Run