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
<div class="game-wrap"> <div class="game-header"> <div> <span>HOLE</span> <strong id="holeNumber">1</strong> </div> <div class="logo"> MINI<span>GOLF</span> </div> <div> <span>SHOTS</span> <strong id="shots">0</strong> </div> </div> <div class="course"> <canvas id="gameCanvas"></canvas> <div id="message" class="message hidden"> <div class="flag-icon">⚑</div> <h2>NICE SHOT!</h2> <p id="resultText">Hole completed in 2 shots</p> <button id="nextButton">NEXT HOLE</button> </div> <div class="instructions"> DRAG FROM THE BALL • RELEASE TO SHOOT </div> </div> </div>
* { box-sizing: border-box; } html, body { margin: 0; min-height: 100%; } body { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 25px; font-family: Arial, Helvetica, sans-serif; background: radial-gradient( circle at 50% 20%, #26323a, #12191e 55%, #080c0f ); color: white; } /* ========================= GAME ========================= */ .game-wrap { width: min(92vw, 760px); filter: drop-shadow( 0 30px 40px rgba(0,0,0,.4) ); } /* ========================= HEADER ========================= */ .game-header { height: 68px; display: grid; grid-template-columns: 1fr 2fr 1fr; align-items: center; padding: 0 25px; background: #182127; border-radius: 20px 20px 0 0; border: 1px solid rgba(255,255,255,.08); } .game-header > div:first-child { text-align: left; } .game-header > div:last-child { text-align: right; } .game-header span { display: block; font-size: 8px; letter-spacing: 2px; color: #7f9299; } .game-header strong { display: block; margin-top: 3px; font-size: 20px; } .logo { text-align: center; font-weight: 900; letter-spacing: 3px; font-size: 15px; } .logo span { display: inline; margin-left: 4px; color: #8bd66e; font-size: inherit; letter-spacing: inherit; } /* ========================= COURSE ========================= */ .course { position: relative; width: 100%; aspect-ratio: 1.35; overflow: hidden; border-radius: 0 0 24px 24px; border: 1px solid rgba(255,255,255,.08); border-top: none; background: #68a850; } canvas { display: block; width: 100%; height: 100%; cursor: grab; touch-action: none; } canvas:active { cursor: grabbing; } /* ========================= INSTRUCTIONS ========================= */ .instructions { position: absolute; left: 50%; bottom: 13px; transform: translateX(-50%); padding: 7px 12px; border-radius: 20px; background: rgba(15,30,20,.55); color: rgba(255,255,255,.65); backdrop-filter: blur(8px); font-size: 8px; letter-spacing: 1.5px; white-space: nowrap; pointer-events: none; } /* ========================= COMPLETION SCREEN ========================= */ .message { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: min(330px, 80%); padding: 30px; text-align: center; border-radius: 22px; background: rgba(19,30,26,.93); border: 1px solid rgba(255,255,255,.12); backdrop-filter: blur(15px); box-shadow: 0 25px 60px rgba(0,0,0,.35); } .message.hidden { display: none; } .flag-icon { width: 50px; height: 50px; display: flex; align-items: center; justify-content: center; margin: 0 auto 15px; border-radius: 50%; background: rgba(139,214,110,.12); color: #9be37e; font-size: 25px; } .message h2 { margin: 0; font-size: 26px; letter-spacing: 1px; } .message p { margin: 10px 0 20px; color: #8fa29a; font-size: 12px; } .message button { padding: 12px 24px; border: 0; border-radius: 9px; background: #8bd66e; color: #172218; font-size: 10px; font-weight: bold; letter-spacing: 1px; cursor: pointer; transition: .2s; } .message button:hover { transform: translateY(-2px); background: #a7ed8b; } /* ========================= MOBILE ========================= */ @media (max-width: 550px) { body { padding: 10px; } .game-header { height: 55px; padding: 0 15px; } .course { aspect-ratio: .9; } }
const canvas = document.getElementById("gameCanvas"); const ctx = canvas.getContext("2d"); const course = document.querySelector(".course"); const shotsEl = document.getElementById("shots"); const holeNumberEl = document.getElementById("holeNumber"); const message = document.getElementById("message"); const resultText = document.getElementById("resultText"); const nextButton = document.getElementById("nextButton"); let W; let H; let scaleX = 1; let scaleY = 1; let shots = 0; let currentHole = 0; let dragging = false; let dragX = 0; let dragY = 0; let completed = false; /* ========================= BALL ========================= */ const ball = { x: 120, y: 300, vx: 0, vy: 0, radius: 10 }; /* ========================= LEVELS ========================= */ const levels = [ { ball: [110, 300], hole: [620, 180], water: { x: 280, y: 220, w: 150, h: 120 }, sand: { x: 470, y: 300, w: 130, h: 90 }, walls: [ { x: 200, y: 100, w: 20, h: 170 }, { x: 430, y: 80, w: 20, h: 150 } ] }, { ball: [100, 160], hole: [640, 350], water: { x: 300, y: 0, w: 130, h: 230 }, sand: { x: 170, y: 280, w: 150, h: 100 }, walls: [ { x: 230, y: 80, w: 20, h: 130 }, { x: 470, y: 230, w: 160, h: 20 } ] }, { ball: [100, 360], hole: [650, 100], water: { x: 250, y: 180, w: 230, h: 120 }, sand: { x: 500, y: 60, w: 100, h: 120 }, walls: [ { x: 180, y: 80, w: 20, h: 190 }, { x: 480, y: 280, w: 20, h: 130 } ] } ]; /* ========================= RESIZE ========================= */ function resize() { const rect = course.getBoundingClientRect(); canvas.width = rect.width * devicePixelRatio; canvas.height = rect.height * devicePixelRatio; ctx.setTransform( devicePixelRatio, 0, 0, devicePixelRatio, 0, 0 ); W = rect.width; H = rect.height; scaleX = W / 720; scaleY = H / 450; } resize(); window.addEventListener( "resize", resize ); /* ========================= LEVEL ========================= */ function loadLevel() { const level = levels[currentHole]; ball.x = level.ball[0]; ball.y = level.ball[1]; ball.vx = 0; ball.vy = 0; shots = 0; completed = false; shotsEl.textContent = shots; holeNumberEl.textContent = currentHole + 1; message.classList.add( "hidden" ); } loadLevel(); /* ========================= COORDINATES ========================= */ function mousePosition(e) { const rect = canvas.getBoundingClientRect(); return { x: (e.clientX - rect.left) / scaleX, y: (e.clientY - rect.top) / scaleY }; } /* ========================= INPUT ========================= */ canvas.addEventListener( "pointerdown", e => { if ( completed || Math.abs(ball.vx) > .1 || Math.abs(ball.vy) > .1 ) return; const p = mousePosition(e); const dx = p.x - ball.x; const dy = p.y - ball.y; if ( Math.sqrt( dx * dx + dy * dy ) < 35 ) { dragging = true; dragX = p.x; dragY = p.y; canvas.setPointerCapture( e.pointerId ); } } ); canvas.addEventListener( "pointermove", e => { if (!dragging) return; const p = mousePosition(e); dragX = p.x; dragY = p.y; } ); canvas.addEventListener( "pointerup", e => { if (!dragging) return; dragging = false; let dx = ball.x - dragX; let dy = ball.y - dragY; const distance = Math.sqrt( dx * dx + dy * dy ); const maxPower = 150; if ( distance > maxPower ) { dx *= maxPower / distance; dy *= maxPower / distance; } if ( distance > 8 ) { ball.vx = dx * .085; ball.vy = dy * .085; shots++; shotsEl.textContent = shots; } } ); /* ========================= RECTANGLE TEST ========================= */ function insideRect( x, y, rect ) { return ( x > rect.x && x < rect.x + rect.w && y > rect.y && y < rect.y + rect.h ); } /* ========================= WALL COLLISION ========================= */ function wallCollision(wall) { const nearestX = Math.max( wall.x, Math.min( ball.x, wall.x + wall.w ) ); const nearestY = Math.max( wall.y, Math.min( ball.y, wall.y + wall.h ) ); const dx = ball.x - nearestX; const dy = ball.y - nearestY; const distance = Math.sqrt( dx * dx + dy * dy ); if ( distance < ball.radius ) { if ( Math.abs(dx) > Math.abs(dy) ) { ball.vx *= -.82; ball.x += dx > 0 ? 3 : -3; } else { ball.vy *= -.82; ball.y += dy > 0 ? 3 : -3; } } } /* ========================= UPDATE ========================= */ function update() { if (completed) return; const level = levels[currentHole]; ball.x += ball.vx; ball.y += ball.vy; let friction = .985; /* sand */ if ( insideRect( ball.x, ball.y, level.sand ) ) { friction = .94; } ball.vx *= friction; ball.vy *= friction; if ( Math.abs(ball.vx) < .025 ) ball.vx = 0; if ( Math.abs(ball.vy) < .025 ) ball.vy = 0; /* boundaries */ if ( ball.x < ball.radius ) { ball.x = ball.radius; ball.vx *= -.8; } if ( ball.x > 720 - ball.radius ) { ball.x = 720 - ball.radius; ball.vx *= -.8; } if ( ball.y < ball.radius ) { ball.y = ball.radius; ball.vy *= -.8; } if ( ball.y > 450 - ball.radius ) { ball.y = 450 - ball.radius; ball.vy *= -.8; } level.walls.forEach( wallCollision ); /* water */ if ( insideRect( ball.x, ball.y, level.water ) ) { resetBall(); } /* hole */ const hx = level.hole[0]; const hy = level.hole[1]; const dx = ball.x - hx; const dy = ball.y - hy; const distance = Math.sqrt( dx * dx + dy * dy ); const velocity = Math.sqrt( ball.vx * ball.vx + ball.vy * ball.vy ); if ( distance < 15 && velocity < 3 ) { completed = true; ball.x = hx; ball.y = hy; ball.vx = 0; ball.vy = 0; setTimeout( showCompleted, 350 ); } } /* ========================= RESET BALL ========================= */ function resetBall() { const level = levels[currentHole]; ball.x = level.ball[0]; ball.y = level.ball[1]; ball.vx = 0; ball.vy = 0; } /* ========================= DRAW COURSE ========================= */ function drawCourse() { const level = levels[currentHole]; /* grass */ const grass = ctx.createLinearGradient( 0, 0, 0, 450 ); grass.addColorStop( 0, "#7fbd61" ); grass.addColorStop( 1, "#559444" ); ctx.fillStyle = grass; ctx.fillRect( 0, 0, 720, 450 ); /* mowing stripes */ for ( let i = 0; i < 720; i += 80 ) { ctx.fillStyle = "rgba(255,255,255,.025)"; ctx.fillRect( i, 0, 40, 450 ); } /* sand */ drawRoundedRect( level.sand.x, level.sand.y, level.sand.w, level.sand.h, 30, "#d8c181" ); /* sand texture */ ctx.fillStyle = "rgba(100,80,40,.12)"; for ( let i = 0; i < 25; i++ ) { const x = level.sand.x + Math.random() * level.sand.w; const y = level.sand.y + Math.random() * level.sand.h; ctx.beginPath(); ctx.arc( x, y, 1, 0, Math.PI * 2 ); ctx.fill(); } /* water */ drawRoundedRect( level.water.x, level.water.y, level.water.w, level.water.h, 25, "#3ba6bd" ); /* water lines */ ctx.strokeStyle = "rgba(255,255,255,.18)"; ctx.lineWidth = 2; for ( let y = level.water.y + 15; y < level.water.y + level.water.h; y += 18 ) { ctx.beginPath(); ctx.moveTo( level.water.x + 15, y ); ctx.lineTo( level.water.x + level.water.w - 15, y ); ctx.stroke(); } /* walls */ level.walls.forEach( wall => { ctx.fillStyle = "#e8e4d5"; ctx.shadowColor = "rgba(0,0,0,.18)"; ctx.shadowBlur = 8; ctx.shadowOffsetY = 5; ctx.fillRect( wall.x, wall.y, wall.w, wall.h ); ctx.shadowBlur = 0; ctx.shadowOffsetY = 0; ctx.fillStyle = "rgba(255,255,255,.45)"; ctx.fillRect( wall.x + 3, wall.y + 3, wall.w - 6, 4 ); } ); drawHole( level.hole[0], level.hole[1] ); } /* ========================= HOLE ========================= */ function drawHole(x, y) { /* hole */ ctx.beginPath(); ctx.ellipse( x, y, 15, 9, 0, 0, Math.PI * 2 ); ctx.fillStyle = "#182018"; ctx.fill(); /* pole */ ctx.strokeStyle = "#f4f4ef"; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo( x, y ); ctx.lineTo( x, y - 80 ); ctx.stroke(); /* flag */ ctx.beginPath(); ctx.moveTo( x, y - 80 ); ctx.lineTo( x + 38, y - 66 ); ctx.lineTo( x, y - 53 ); ctx.closePath(); ctx.fillStyle = "#f05d54"; ctx.fill(); } /* ========================= BALL ========================= */ function drawBall() { ctx.save(); ctx.shadowColor = "rgba(0,0,0,.35)"; ctx.shadowBlur = 8; ctx.shadowOffsetY = 5; const gradient = ctx.createRadialGradient( ball.x - 4, ball.y - 5, 1, ball.x, ball.y, ball.radius ); gradient.addColorStop( 0, "#ffffff" ); gradient.addColorStop( .7, "#f4f4f0" ); gradient.addColorStop( 1, "#c5c7c1" ); ctx.beginPath(); ctx.arc( ball.x, ball.y, ball.radius, 0, Math.PI * 2 ); ctx.fillStyle = gradient; ctx.fill(); ctx.restore(); } /* ========================= AIMING ========================= */ function drawAim() { if (!dragging) return; let dx = ball.x - dragX; let dy = ball.y - dragY; const distance = Math.sqrt( dx * dx + dy * dy ); if ( distance > 150 ) { dx *= 150 / distance; dy *= 150 / distance; } /* power line */ ctx.strokeStyle = "rgba(255,255,255,.35)"; ctx.lineWidth = 2; ctx.setLineDash( [5, 7] ); ctx.beginPath(); ctx.moveTo( ball.x, ball.y ); ctx.lineTo( ball.x + dx * 1.4, ball.y + dy * 1.4 ); ctx.stroke(); ctx.setLineDash([]); /* trajectory dots */ for ( let i = 1; i <= 5; i++ ) { const amount = i / 5; const x = ball.x + dx * amount * 1.25; const y = ball.y + dy * amount * 1.25; ctx.beginPath(); ctx.arc( x, y, 5 - i * .5, 0, Math.PI * 2 ); ctx.fillStyle = `rgba( 255, 255, 255, ${1 - amount * .7} )`; ctx.fill(); } /* drag line */ ctx.strokeStyle = "rgba(20,40,20,.35)"; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo( ball.x, ball.y ); ctx.lineTo( dragX, dragY ); ctx.stroke(); } /* ========================= ROUND RECT ========================= */ function drawRoundedRect( x, y, w, h, radius, color ) { ctx.beginPath(); ctx.roundRect( x, y, w, h, radius ); ctx.fillStyle = color; ctx.fill(); } /* ========================= DRAW ========================= */ function draw() { ctx.save(); ctx.scale( scaleX, scaleY ); ctx.clearRect( 0, 0, 720, 450 ); drawCourse(); drawAim(); drawBall(); ctx.restore(); } /* ========================= LOOP ========================= */ function loop() { update(); draw(); requestAnimationFrame( loop ); } loop(); /* ========================= COMPLETION ========================= */ function showCompleted() { resultText.textContent = "Hole completed in " + shots + ( shots === 1 ? " shot" : " shots" ); if ( currentHole === levels.length - 1 ) { nextButton.textContent = "PLAY AGAIN"; } else { nextButton.textContent = "NEXT HOLE"; } message.classList.remove( "hidden" ); } /* ========================= NEXT LEVEL ========================= */ nextButton.addEventListener( "click", () => { currentHole++; if ( currentHole >= levels.length ) { currentHole = 0; } loadLevel(); } );
terminal
JavaScript Console
Preview
Clear
close
›
Run