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
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <div id="app"></div> <div class="title"> <div class="eyebrow">3D FABRIC STUDY</div> <h1>FLOWING FLAG</h1> <p>Fixed pole edge · Dynamic cloth</p> </div> <div class="controls"> <div class="control-group"> <span>WIND</span> <input id="windSlider" type="range" min="0" max="100" value="55" > <strong id="windValue">55</strong> </div> <div class="control-group"> <span>FOLD</span> <input id="foldSlider" type="range" min="0" max="100" value="60" > <strong id="foldValue">60</strong> </div> </div> <div class="status"> <span class="indicator"></span> LIVE CLOTH </div>
* { box-sizing: border-box; } html, body { width: 100%; height: 100%; margin: 0; } body { overflow: hidden; background: radial-gradient( circle at 50% 43%, #373431 0%, #181613 44%, #050505 100% ); color: #fff; font-family: Arial, Helvetica, sans-serif; } /* ========================================== THREE.JS ========================================== */ #app { position: fixed; inset: 0; } #app canvas { display: block; width: 100%; height: 100%; cursor: grab; } #app canvas:active { cursor: grabbing; } /* ========================================== TITLE ========================================== */ .title { position: fixed; left: 30px; top: 28px; z-index: 10; pointer-events: none; } .eyebrow { margin-bottom: 8px; font-size: 8px; letter-spacing: 3px; color: rgba(255,255,255,0.42); } h1 { margin: 0; font-size: 42px; line-height: 1; letter-spacing: -2px; } .title p { margin: 9px 0 0; font-size: 10px; color: rgba(255,255,255,0.4); } /* ========================================== CONTROLS ========================================== */ .controls { position: fixed; top: 28px; right: 28px; z-index: 10; width: 205px; padding: 16px 16px 4px; border: 1px solid rgba(255,255,255,0.13); border-radius: 12px; background: rgba(0,0,0,0.56); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); } .control-group { position: relative; margin-bottom: 15px; } .control-group span { font-size: 8px; letter-spacing: 1.5px; color: rgba(255,255,255,0.46); } .control-group strong { float: right; font-size: 9px; font-weight: 500; color: rgba(255,255,255,0.55); } .control-group input { display: block; width: 100%; height: 4px; margin-top: 9px; appearance: none; -webkit-appearance: none; outline: none; border-radius: 10px; background: rgba(255,255,255,0.18); } .control-group input::-webkit-slider-thumb { appearance: none; -webkit-appearance: none; width: 14px; height: 14px; border: 0; border-radius: 50%; background: white; cursor: pointer; } .control-group input::-moz-range-thumb { width: 14px; height: 14px; border: 0; border-radius: 50%; background: white; cursor: pointer; } /* ========================================== STATUS ========================================== */ .status { position: fixed; left: 30px; bottom: 28px; z-index: 10; display: flex; align-items: center; gap: 8px; padding: 8px 12px; border: 1px solid rgba(255,255,255,0.11); border-radius: 20px; background: rgba(0,0,0,0.48); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); font-size: 8px; letter-spacing: 1.6px; color: rgba(255,255,255,0.4); pointer-events: none; } .indicator { width: 6px; height: 6px; border-radius: 50%; background: #72d596; box-shadow: 0 0 10px rgba(114,213,150,0.9); animation: pulse 1.4s ease-in-out infinite; } @keyframes pulse { 0% { opacity: 0.35; } 50% { opacity: 1; } 100% { opacity: 0.35; } } /* ========================================== MOBILE ========================================== */ @media (max-width: 600px) { .title { left: 18px; top: 18px; } h1 { font-size: 30px; } .controls { right: 18px; top: 18px; width: 155px; } .status { left: 18px; bottom: 18px; } }
/* ========================================================= FIXED-EDGE 3D FLOWING FLAG Liveweave + Three.js r128 ========================================================= */ /* ===================================================== CHECK THREE.JS ===================================================== */ if (typeof THREE === "undefined") { document.getElementById("app").innerHTML = "<div style=\"" + "position:fixed;" + "inset:0;" + "display:flex;" + "align-items:center;" + "justify-content:center;" + "color:white;" + "font-family:Arial;" + "\">" + "Three.js failed to load." + "</div>"; throw new Error( "Three.js was not loaded." ); } /* ===================================================== CONTAINER ===================================================== */ const app = document.getElementById("app"); /* ===================================================== SCENE ===================================================== */ const scene = new THREE.Scene(); /* ===================================================== CAMERA ===================================================== */ const camera = new THREE.PerspectiveCamera( 36, window.innerWidth / window.innerHeight, 0.1, 100 ); camera.position.set( 0, 1.0, 9.5 ); camera.lookAt( 0, 1.0, 0 ); /* ===================================================== RENDERER ===================================================== */ const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.setPixelRatio( Math.min( window.devicePixelRatio, 2 ) ); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; app.appendChild( renderer.domElement ); /* ===================================================== LIGHTING ===================================================== */ const ambient = new THREE.HemisphereLight( 0xffffff, 0x18120e, 2.2 ); scene.add( ambient ); const keyLight = new THREE.DirectionalLight( 0xffffff, 3.8 ); keyLight.position.set( 5, 7, 7 ); keyLight.castShadow = true; scene.add( keyLight ); const warmLight = new THREE.PointLight( 0xff7048, 8, 18 ); warmLight.position.set( -4, 3, 3 ); scene.add( warmLight ); /* ===================================================== FLAG DIMENSIONS ===================================================== */ const FLAG_WIDTH = 6.8; const FLAG_HEIGHT = 3.9; /* Higher subdivisions = smoother cloth. */ const SEGMENTS_X = 80; const SEGMENTS_Y = 36; /* ===================================================== FLAG ART ===================================================== */ const canvas = document.createElement("canvas"); canvas.width = 1200; canvas.height = 690; const ctx = canvas.getContext("2d"); /* Base */ ctx.fillStyle = "#d96a45"; ctx.fillRect( 0, 0, 1200, 690 ); /* Cream geometric panel */ ctx.fillStyle = "#eee7d8"; ctx.beginPath(); ctx.moveTo( 310, 0 ); ctx.lineTo( 1200, 0 ); ctx.lineTo( 1200, 410 ); ctx.lineTo( 760, 690 ); ctx.lineTo( 310, 690 ); ctx.closePath(); ctx.fill(); /* Dark diagonal stripe */ ctx.fillStyle = "#292521"; ctx.beginPath(); ctx.moveTo( 0, 490 ); ctx.lineTo( 1200, 135 ); ctx.lineTo( 1200, 220 ); ctx.lineTo( 0, 575 ); ctx.closePath(); ctx.fill(); /* Circular emblem */ ctx.strokeStyle = "#292521"; ctx.lineWidth = 15; ctx.beginPath(); ctx.arc( 205, 345, 102, 0, Math.PI * 2 ); ctx.stroke(); /* Inner emblem */ ctx.lineWidth = 8; ctx.beginPath(); ctx.arc( 205, 345, 38, 0, Math.PI * 2 ); ctx.stroke(); /* Accent square */ ctx.fillStyle = "#d96a45"; ctx.fillRect( 169, 309, 72, 72 ); /* Fabric texture */ ctx.globalAlpha = 0.045; for ( let i = 0; i < 6500; i++ ) { const x = Math.random() * 1200; const y = Math.random() * 690; ctx.fillStyle = "#ffffff"; ctx.fillRect( x, y, 1, 1 ); } ctx.globalAlpha = 1; /* ===================================================== TEXTURE ===================================================== */ const texture = new THREE.Texture( canvas ); texture.needsUpdate = true; /* ===================================================== FLAG GEOMETRY ===================================================== */ const geometry = new THREE.PlaneGeometry( FLAG_WIDTH, FLAG_HEIGHT, SEGMENTS_X, SEGMENTS_Y ); /* Save the original flat geometry. */ const original = []; const positions = geometry.attributes.position; for ( let i = 0; i < positions.count; i++ ) { original.push({ x: positions.getX(i), y: positions.getY(i) }); } /* ===================================================== MATERIAL ===================================================== */ const material = new THREE.MeshStandardMaterial({ map: texture, side: THREE.DoubleSide, roughness: 0.86, metalness: 0 }); /* ===================================================== FLAG ===================================================== */ const flag = new THREE.Mesh( geometry, material ); /* CRITICAL: The flag remains centered. Therefore: left edge = -FLAG_WIDTH / 2 */ flag.position.set( 0, 1.45, 0 ); flag.castShadow = true; flag.receiveShadow = true; scene.add( flag ); /* ===================================================== POLE ===================================================== */ const poleX = -FLAG_WIDTH / 2; const poleHeight = 6.2; const poleMaterial = new THREE.MeshStandardMaterial({ color: 0x767676, metalness: 0.92, roughness: 0.22 }); const pole = new THREE.Mesh( new THREE.CylinderGeometry( 0.075, 0.10, poleHeight, 24 ), poleMaterial ); pole.position.set( poleX, 0, 0 ); pole.castShadow = true; scene.add( pole ); /* ===================================================== POLE TOP ===================================================== */ const poleTop = new THREE.Mesh( new THREE.SphereGeometry( 0.16, 24, 24 ), new THREE.MeshStandardMaterial({ color: 0xd4a64e, metalness: 0.92, roughness: 0.18 }) ); poleTop.position.set( poleX, poleHeight / 2, 0 ); scene.add( poleTop ); /* ===================================================== BASE ===================================================== */ const base = new THREE.Mesh( new THREE.CylinderGeometry( 0.48, 0.64, 0.46, 32 ), new THREE.MeshStandardMaterial({ color: 0x262626, metalness: 0.75, roughness: 0.3 }) ); base.position.set( poleX, -3.0, 0 ); base.castShadow = true; scene.add( base ); /* ===================================================== GROUND ===================================================== */ const ground = new THREE.Mesh( new THREE.CircleGeometry( 8.5, 64 ), new THREE.MeshStandardMaterial({ color: 0x151515, roughness: 0.95 }) ); ground.rotation.x = -Math.PI / 2; ground.position.y = -2.77; ground.receiveShadow = true; scene.add( ground ); /* ===================================================== CONTROLS ===================================================== */ const windSlider = document.getElementById( "windSlider" ); const foldSlider = document.getElementById( "foldSlider" ); const windValue = document.getElementById( "windValue" ); const foldValue = document.getElementById( "foldValue" ); let wind = 0.55; let fold = 0.60; windSlider.addEventListener( "input", function() { wind = Number(this.value) / 100; windValue.textContent = this.value; } ); foldSlider.addEventListener( "input", function() { fold = Number(this.value) / 100; foldValue.textContent = this.value; } ); /* ===================================================== CLOTH SIMULATION ===================================================== */ let time = 0; function updateFlag() { const position = geometry.attributes.position; for ( let i = 0; i < position.count; i++ ) { const p = original[i]; const x = p.x; const y = p.y; /* Normalize the horizontal position: 0 = pole 1 = free edge */ const progress = ( x + FLAG_WIDTH / 2 ) / FLAG_WIDTH; /* ----------------------------------- PINNED AREA ----------------------------------- The first 3 columns remain COMPLETELY stationary. This is the important fix. */ const pinWidth = 0.08; if ( progress <= pinWidth ) { position.setXYZ( i, x, y, 0 ); continue; } /* Remap movement after the pinned section. */ const clothProgress = ( progress - pinWidth ) / ( 1 - pinWidth ); /* Ease the movement so it starts gradually. */ const influence = Math.pow( clothProgress, 1.35 ); /* ===================================== PRIMARY WAVE ===================================== */ const primaryWave = Math.sin( time * ( 2.0 + wind * 4.5 ) - clothProgress * 8.5 ); /* ===================================== SECONDARY FOLD ===================================== */ const secondaryWave = Math.sin( time * 5.0 - clothProgress * 20 + y * 2.4 ); /* ===================================== FABRIC DEPTH ===================================== */ const primary = primaryWave * influence * ( 0.10 + wind * 0.62 ); const folds = secondaryWave * influence * fold * 0.12; /* ONLY Z CHANGES. X stays exactly the original position. Y stays exactly the original position. Therefore the pole-side edge remains a perfectly straight vertical edge. */ position.setXYZ( i, x, y, primary + folds ); } position.needsUpdate = true; geometry.computeVertexNormals(); } /* ===================================================== CAMERA ===================================================== */ let angle = 0.10; let height = 0.05; let distance = 9.5; let dragging = false; let lastX = 0; let lastY = 0; /* ===================================================== POINTER DOWN ===================================================== */ renderer.domElement.addEventListener( "pointerdown", function(event) { dragging = true; lastX = event.clientX; lastY = event.clientY; renderer.domElement.setPointerCapture( event.pointerId ); } ); /* ===================================================== POINTER MOVE ===================================================== */ renderer.domElement.addEventListener( "pointermove", function(event) { if (!dragging) { return; } const dx = event.clientX - lastX; const dy = event.clientY - lastY; angle += dx * 0.006; height += dy * 0.004; height = Math.max( -0.35, Math.min( 0.45, height ) ); lastX = event.clientX; lastY = event.clientY; } ); /* ===================================================== POINTER UP ===================================================== */ renderer.domElement.addEventListener( "pointerup", function(event) { dragging = false; renderer.domElement.releasePointerCapture( event.pointerId ); } ); /* ===================================================== ZOOM ===================================================== */ renderer.domElement.addEventListener( "wheel", function(event) { distance += event.deltaY * 0.006; distance = Math.max( 5.8, Math.min( 14, distance ) ); }, { passive: true } ); /* ===================================================== CAMERA UPDATE ===================================================== */ function updateCamera() { camera.position.x = Math.sin(angle) * distance; camera.position.z = Math.cos(angle) * distance; camera.position.y = 1.0 + height * 5; camera.lookAt( 0, 1.0, 0 ); } /* ===================================================== ANIMATION ===================================================== */ function animate() { requestAnimationFrame( animate ); time += 0.016; /* Cloth */ updateFlag(); /* Moving light */ warmLight.position.x = -4 + Math.sin( time * 0.35 ) * 2.5; warmLight.position.z = 2 + Math.cos( time * 0.35 ) * 2; /* Camera */ updateCamera(); /* Render */ renderer.render( scene, camera ); } /* ===================================================== RESIZE ===================================================== */ window.addEventListener( "resize", function() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); } ); /* ===================================================== START ===================================================== */ animate();
terminal
JavaScript Console
Preview
Clear
close
›
Run