Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
- <title>Traffic Blitz [Hustle Mode]</title>
- <style>
- :root{
- --bg:#0b0f14;--fg:#e8f1ff;--muted:#93a0b8;--acc:#4ade80;--acc2:#60a5fa;--warn:#facc15;--danger:#fb7185;--panel:#0f1722;--glass:rgba(255,255,255,.06);
- }
- html,body{height:100%;}
- *{box-sizing:border-box}
- body{
- margin:0; background:radial-gradient(1200px 800px at 70% -10%,#122036,transparent),
- radial-gradient(1200px 800px at -20% 120%,#191f2c,transparent),var(--bg);
- color:var(--fg);
- font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji";
- overflow:hidden;
- }
- #wrap{position:relative; width:100vw; height:100vh;}
- canvas{position:absolute; inset:0; width:100%; height:100%; image-rendering: crisp-edges; image-rendering: pixelated;}
- .ui{position:absolute; inset:0; pointer-events:none;}
- .topbar{display:flex; gap:.75rem; align-items:center; justify-content:space-between; padding:10px 14px;}
- .pill{background:var(--glass); border:1px solid rgba(255,255,255,.08); backdrop-filter: blur(6px); box-shadow:0 8px 24px rgba(0,0,0,.25) inset, 0 2px 6px rgba(0,0,0,.2);
- border-radius:999px; padding:6px 12px; font-weight:700; letter-spacing:.3px;}
- .btn{pointer-events:auto; cursor:pointer; user-select:none; transition:transform .08s ease, filter .15s ease;}
- .btn:active{transform:translateY(1px) scale(.99)}
- .corner{position:absolute; bottom:14px; left:50%; transform:translateX(-50%); display:flex; gap:10px; flex-wrap:wrap; align-items:center; justify-content:center}
- .cta{background:linear-gradient(180deg,var(--acc2),#2563eb); border:0; color:white; padding:12px 18px; border-radius:14px; font-weight:800; letter-spacing:.4px;
- box-shadow:0 10px 30px rgba(37,99,235,.35);}
- .ghost{background:var(--glass); color:var(--fg); border:1px solid rgba(255,255,255,.08);}
- .center{position:absolute; inset:0; display:grid; place-items:center;}
- .panel{pointer-events:auto; width:min(720px,92vw); background:linear-gradient(180deg, rgba(255,255,255,.07), rgba(255,255,255,.03)); border:1px solid rgba(255,255,255,.08);
- border-radius:18px; padding:20px; box-shadow:0 24px 60px rgba(0,0,0,.45);}
- .title{font-size:clamp(28px,4vw,44px); font-weight:900; letter-spacing:.5px; margin:4px 0 8px; background:linear-gradient(90deg,#93c5fd,#a7f3d0,#facc15);
- -webkit-background-clip:text; background-clip:text; color:transparent;}
- .subtitle{opacity:.8; margin-bottom:14px}
- .row{display:flex; gap:10px; align-items:center; flex-wrap:wrap}
- .kbd{font-weight:800; border:1px solid rgba(255,255,255,.14); background:rgba(255,255,255,.06); padding:3px 8px; border-radius:8px;}
- .list{display:grid; grid-template-columns: 1fr 1fr; gap:8px 16px;}
- @media (max-width:640px){.list{grid-template-columns: 1fr}}
- .fine{font-size:12px; opacity:.8}
- .meter{height:10px; background:rgba(255,255,255,.08); border-radius:999px; overflow:hidden; border:1px solid rgba(255,255,255,.1)}
- .meter>span{display:block; height:100%; background:linear-gradient(90deg,#22d3ee,#60a5fa,#34d399); width:0%}
- </style>
- </head>
- <body>
- <div id="wrap">
- <canvas id="game"></canvas>
- <div class="ui">
- <div class="topbar">
- <div class="pill" id="scorePill">Score 0</div>
- <div class="pill">x<span id="mult">1.0</span> Mult</div>
- <div class="pill">Level <span id="lvl">1</span></div>
- <div class="pill">Best <span id="best">0</span></div>
- <div class="pill" id="hustlePill" title="Hustle Meter (Space / Hold)">
- Hustle
- <div class="meter" style="width:120px; margin-top:6px"><span id="hustleBar"></span></div>
- </div>
- <button class="pill btn ghost" id="pauseBtn" aria-label="Pause (P)">⏸︎</button>
- </div>
- <div class="corner">
- <button class="btn cta" id="playBtn">▶ Play / Toggle Lights (Click or Tap)</button>
- <button class="btn ghost" id="howBtn">How to Play</button>
- </div>
- <div class="center" id="menu">
- <div class="panel">
- <div class="title">Traffic Blitz <span class="fine">[Hustle Mode]</span></div>
- <div class="subtitle">One-button chaos: run the intersection, keep the flow, chase the combo. No accidents allowed.</div>
- <div class="list" style="margin:12px 0 16px">
- <div>• <b>Click / Tap</b> — toggle lights (N/S ↔ E/W)</div>
- <div>• <span class="kbd">Space</span> — hold to Hustle (faster spawn, bigger <b>multiplier</b>)</div>
- <div>• <span class="kbd">P</span> — pause / resume</div>
- <div>• <span class="kbd">M</span> — mute</div>
- </div>
- <div class="row" style="margin:8px 0 16px">
- <div class="pill">Objective: <b>Survive</b> & score big with clean streaks.</div>
- <div class="pill">Combo builds when no one waits too long.</div>
- </div>
- <div class="row" style="justify-content:space-between; align-items:center">
- <button class="btn cta" id="startBtn">Start Game</button>
- <div class="fine">Autosaves Best Score</div>
- </div>
- </div>
- </div>
- <div class="center" id="help" style="display:none">
- <div class="panel">
- <div class="title">How to Play</div>
- <p>Cars stream in from four directions toward a single intersection. You control the lights: either <b>N/S is green</b> and <b>E/W is red</b>, or vice versa. Tap/click anywhere to toggle.
- Let cars pass to score. If cars stack up and wait too long, you’ll lose combo juice. If they collide inside the box—it’s game over.</p>
- <ul>
- <li><b>Hustle Mode:</b> Hold <span class="kbd">Space</span> to temporarily spike spawn rate & speed. You’ll earn a bigger multiplier while hustling, but it drains the meter.</li>
- <li><b>Level Ups:</b> Every 100 points increases pace, introduces vans & speedy cars, and shortens patience a bit.</li>
- <li><b>Pro Tip:</b> Flip lights in rhythm right before cars arrive at the line. Keep both queues flowing.</li>
- </ul>
- <div class="row" style="justify-content:space-between; align-items:center">
- <button class="btn ghost" id="backBtn">Back</button>
- <button class="btn cta" id="playFromHelp">Play Now</button>
- </div>
- </div>
- </div>
- <div class="center" id="gameover" style="display:none">
- <div class="panel">
- <div class="title">Crash! 💥</div>
- <div style="margin:6px 0 10px">Final Score: <b id="finalScore">0</b> · Best: <b id="finalBest">0</b></div>
- <div style="margin:6px 0 12px">Max Multiplier: <b id="finalMult">1.0x</b> · Level Reached: <b id="finalLvl">1</b></div>
- <div class="row" style="justify-content:space-between; align-items:center">
- <button class="btn ghost" id="retryBtn">Retry</button>
- <button class="btn cta" id="menuBtn">Main Menu</button>
- </div>
- </div>
- </div>
- </div>
- </div>
- <script>
- /* ==========================
- Traffic Blitz [Hustle Mode]
- by Pyroflame Games (single-file build)
- FIX: ignore collisions for cars traveling along the same axis (vertical vs horizontal)
- ========================== */
- const cvs = document.getElementById('game');
- const ctx = cvs.getContext('2d');
- // UI refs
- const $ = (id)=>document.getElementById(id);
- const scorePill=$('scorePill'), bestEl=$('best'), multEl=$('mult'), lvlEl=$('lvl');
- const hustleBar=$('hustleBar');
- const pauseBtn=$('pauseBtn'), playBtn=$('playBtn');
- const menu=$('menu'), help=$('help'), gameover=$('gameover');
- const startBtn=$('startBtn'), retryBtn=$('retryBtn'), menuBtn=$('menuBtn');
- const howBtn=$('howBtn'), backBtn=$('backBtn'), playFromHelp=$('playFromHelp');
- const finalScore=$('finalScore'), finalBest=$('finalBest'), finalMult=$('finalMult'), finalLvl=$('finalLvl');
- // State
- const STATE={MENU:0, PLAY:1, PAUSED:2, OVER:3};
- let state=STATE.MENU;
- // DPR scaling
- const S = { w:0, h:0, dpr:1 };
- function resize(){
- S.dpr = Math.min(2, window.devicePixelRatio||1);
- S.w = cvs.clientWidth; S.h = cvs.clientHeight;
- cvs.width = (S.w*S.dpr)|0; cvs.height=(S.h*S.dpr)|0;
- ctx.setTransform(S.dpr,0,0,S.dpr,0,0);
- }
- addEventListener('resize', resize, {passive:true}); resize();
- // RNG
- const rand = (a,b)=>a+Math.random()*(b-a);
- const rint = (a,b)=> (Math.random()*(b-a+1)|0)+a;
- // Audio (lightweight, oscillator-based)
- const Audio = (function(){
- const ac = new (window.AudioContext||window.webkitAudioContext||function(){return {resume:()=>{}, currentTime:0, createOscillator:()=>({}), createGain:()=>({})}})();
- let muted = false; function toggle(){ muted=!muted; return muted; }
- function beep(freq=880, dur=0.06, type='sine', vol=0.04){
- if(muted || !ac.createOscillator) return;
- const o=ac.createOscillator(), g=ac.createGain();
- o.type=type; o.frequency.value=freq; g.gain.value=vol; o.connect(g); g.connect(ac.destination);
- o.start(); g.gain.exponentialRampToValueAtTime(0.0001, ac.currentTime+dur); o.stop(ac.currentTime+dur);
- }
- function honk(){ beep(220, .09, 'square', .05); }
- function crash(){ beep(120, .25, 'sawtooth', .12); setTimeout(()=>beep(60,.35,'triangle',.1),40); }
- return {beep,honk,crash,toggle, get muted(){return muted;}, context:ac};
- })();
- // Game objects
- let score=0, best=Number(localStorage.getItem('tb_best')||0), level=1, multiplier=1, maxMult=1;
- bestEl.textContent=best;
- const world = {
- lightNS:true, // true => NS green, EW red
- toggle(){ this.lightNS=!this.lightNS; Audio.beep(this.lightNS?880:660, .05, 'triangle', .045); },
- lanes:[ // 0=N,1=E,2=S,3=W
- {dir:0, x:0, y:-1}, {dir:1, x:1, y:0}, {dir:2, x:0, y:1}, {dir:3, x:-1, y:0}
- ],
- spawnCooldown:0,
- baseSpawn: 1.2, // seconds
- hustle:0, // 0..1
- hustleHeld:false,
- };
- const cars=[];
- function carTemplate(dir){
- const speedType = Math.random();
- const base = speedType<.1? 110 : speedType<.8? 140 : 180; // vans, normal, speedy (px/s)
- const len = speedType<.1? 36 : speedType<.8? 28 : 22;
- const wid = 14;
- const col = speedType<.1? '#eab308' : speedType<.8? '#38bdf8' : '#f472b6';
- // positions relative to intersection center
- const spawnDist = Math.max(S.w,S.h);
- let x=0,y=0,vx=0,vy=0; const lane=dir;
- if(dir===0){ y = -spawnDist; x= rint(-18,18); vy= base; }
- if(dir===2){ y = spawnDist; x= rint(-18,18); vy= -base; }
- if(dir===1){ x = spawnDist; y= rint(-18,18); vx= -base; }
- if(dir===3){ x = -spawnDist; y= rint(-18,18); vx= base; }
- return {x,y,vx,vy,w:wid,h:len,col,dir:lane, wait:0, passed:false};
- }
- function spawnCar(){
- const dir = rint(0,3);
- cars.push(carTemplate(dir));
- }
- function reset(){
- score=0; multiplier=1; maxMult=1; level=1; world.lightNS=true; world.spawnCooldown=0; world.hustle=0.35; // start with a bit of meter
- cars.length=0;
- }
- function aabb(a,b){ return Math.abs(a.x-b.x) < (a.w+b.w)/2 && Math.abs(a.y-b.y) < (a.h+b.h)/2; }
- // Input
- let mouseDown=false;
- window.addEventListener('pointerdown', (e)=>{
- mouseDown=true; if(state===STATE.MENU){ startGame(); return; }
- if(state===STATE.PLAY){ world.toggle(); }
- if(state===STATE.OVER){ startGame(); }
- });
- window.addEventListener('pointerup', ()=> mouseDown=false);
- window.addEventListener('keydown', (e)=>{
- if(e.key===' '){ world.hustleHeld=true; e.preventDefault(); }
- if(e.key==='p'||e.key==='P'){ togglePause(); }
- if(e.key==='m'||e.key==='M'){ const m=Audio.toggle(); toast(m?"Muted":"Sound On"); }
- });
- window.addEventListener('keyup', (e)=>{ if(e.key===' '){ world.hustleHeld=false; }});
- // Buttons
- startBtn.onclick=()=>startGame();
- retryBtn.onclick=()=>startGame();
- menuBtn.onclick=()=>showMenu();
- playBtn.onclick=()=>{ if(state===STATE.PLAY){ world.toggle(); } else startGame(); };
- howBtn.onclick=()=>{ help.style.display='grid'; menu.style.display='none'; };
- backBtn.onclick=()=>{ help.style.display='none'; menu.style.display='grid'; };
- playFromHelp.onclick=()=>{ help.style.display='none'; startGame(); };
- pauseBtn.onclick=()=>togglePause();
- function togglePause(){
- if(state!==STATE.PLAY && state!==STATE.PAUSED) return;
- state = state===STATE.PLAY? STATE.PAUSED : STATE.PLAY;
- toast(state===STATE.PAUSED? 'Paused' : 'Resumed');
- }
- function showMenu(){
- state=STATE.MENU; menu.style.display='grid'; help.style.display='none'; gameover.style.display='none';
- }
- function startGame(){
- reset();
- state=STATE.PLAY; menu.style.display='none'; help.style.display='none'; gameover.style.display='none';
- Audio.context.resume && Audio.context.resume();
- }
- function endGame(){
- state=STATE.OVER; finalScore.textContent=score; finalBest.textContent=best; finalMult.textContent=maxMult.toFixed(1)+'x'; finalLvl.textContent=level;
- gameover.style.display='grid';
- }
- // HUD
- function updateHUD(){
- scorePill.textContent = `Score ${score}`;
- multEl.textContent = multiplier.toFixed(1);
- lvlEl.textContent = level;
- hustleBar.style.width = `${(world.hustle*100)|0}%`;
- bestEl.textContent = best;
- }
- // Toasts
- let toastT=0, toastText='';
- function toast(t){ toastText=t; toastT=1.6; }
- // Loop
- let last=performance.now();
- function loop(now){
- requestAnimationFrame(loop);
- const dt = Math.min(0.033, (now-last)/1000); last=now;
- if(state===STATE.PAUSED){ draw(dt,true); return; }
- if(state===STATE.PLAY){ update(dt); }
- draw(dt,false);
- }
- requestAnimationFrame(loop);
- // Update
- function update(dt){
- // Hustle meter
- const hustleDrain = world.hustleHeld? .45 : -.25; // drain if held, recharge if not
- world.hustle = Math.max(0, Math.min(1, world.hustle + hustleDrain*dt));
- const hustleBoost = 1 + world.hustle*1.2 + (world.hustleHeld? .4:0);
- // Spawning
- const spawnBase = Math.max(.45, world.baseSpawn - (level-1)*0.08);
- world.spawnCooldown -= dt * hustleBoost;
- if(world.spawnCooldown<=0){
- world.spawnCooldown = spawnBase * rand(.75,1.25);
- spawnCar();
- }
- // Move cars & light logic
- const lightNS = world.lightNS;
- const interHalf = 44; // intersection half-size
- const stopLine = interHalf+20;
- for(let i=cars.length-1;i>=0;i--){
- const c = cars[i];
- // Decide if should stop at red
- let red = false;
- if(c.dir===0||c.dir===2){ // N/S
- red = !lightNS;
- const willEnter = Math.abs(c.y + Math.sign(c.vy)*c.h/2) < stopLine;
- if(red && ((c.dir===0 && c.y < -stopLine) || (c.dir===2 && c.y > stopLine))){
- // Far from line: keep moving
- } else if(red && willEnter){ // apply braking
- c.vstop = true;
- } else { c.vstop=false; }
- } else { // E/W
- red = lightNS;
- const willEnter = Math.abs(c.x + Math.sign(c.vx)*c.w/2) < stopLine;
- if(red && ((c.dir===3 && c.x < -stopLine) || (c.dir===1 && c.x > stopLine))){
- } else if(red && willEnter){ c.vstop=true; } else { c.vstop=false; }
- }
- // patience / wait
- if(c.vstop){ c.wait += dt; if(c.wait>.9) { if((i&7)===0) Audio.honk(); } }
- else { if(c.wait>0){ c.wait=Math.max(0, c.wait - dt*1.5); } }
- // speed
- const spdScale = 1 + (level-1)*.08 + (world.hustleHeld? .1:0);
- const maxV = Math.hypot(c.vx,c.vy) * spdScale;
- const v = c.vstop? 0 : maxV;
- const nx = c.x + (c.vx? Math.sign(c.vx)*v*dt : 0);
- const ny = c.y + (c.vy? Math.sign(c.vy)*v*dt : 0);
- c.x=nx; c.y=ny;
- // Passed center for scoring
- if(!c.passed && Math.abs(c.x)<interHalf && Math.abs(c.y)<interHalf){
- c.passed=true;
- }
- if(c.passed && (Math.abs(c.x)>interHalf+40 || Math.abs(c.y)>interHalf+40)){
- // exited intersection safely
- score += Math.ceil(1*multiplier);
- // combo influenced by how little they waited
- multiplier = Math.min(9.9, multiplier + (c.wait<.15? .15 : c.wait<.35? .08 : .03));
- maxMult = Math.max(maxMult, multiplier);
- cars.splice(i,1);
- if(score>0 && score%100===0){ level++; toast('Level Up!'); Audio.beep(1200,.09,'triangle',.08); }
- }
- // remove if offscreen long
- if(Math.abs(c.x)>Math.max(S.w,S.h)+200 || Math.abs(c.y)>Math.max(S.w,S.h)+200){ cars.splice(i,1); }
- }
- // Combo decay if queues are long (someone is waiting too long)
- const worstWait = cars.reduce((m,c)=>Math.max(m,c.wait),0);
- const decay = worstWait>.8? .55 : worstWait>.5? .3 : .12;
- multiplier = Math.max(1, multiplier - decay*dt + (world.hustleHeld? .18*dt:0));
- // Collisions inside intersection
- const active = cars.filter(c=> Math.abs(c.x) < interHalf && Math.abs(c.y) < interHalf);
- for(let i=0;i<active.length;i++){
- for(let j=i+1;j<active.length;j++){
- const A = active[i], B = active[j];
- // IMPORTANT FIX:
- // Ignore collisions between cars that travel along the same axis (both vertical: dir 0 or 2, or both horizontal: dir 1 or 3).
- // This lets N<>S vehicles pass together and E<>W vehicles pass together without falsely colliding.
- if( (A.dir % 2) === (B.dir % 2) ) continue;
- if(aabb(A, B)){
- // crash
- Audio.crash();
- best = Math.max(best, score); localStorage.setItem('tb_best', best);
- endGame(); return;
- }
- }
- }
- updateHUD();
- }
- // Draw
- function draw(dt, paused){
- ctx.clearRect(0,0,S.w,S.h);
- ctx.save();
- // transform origin to center
- ctx.translate(S.w/2, S.h/2);
- // Road background
- drawRoad();
- drawLights();
- // Cars
- for(const c of cars){ drawCar(c); }
- // Toast
- if(toastT>0){
- toastT -= dt; const t = Math.max(0, Math.min(1, toastT/1.6));
- ctx.save(); ctx.translate(0, -S.h*0.34); ctx.globalAlpha = Math.pow(t, .9);
- roundedText(toastText, 0,0, 16, '#0b1220', 'white'); ctx.restore();
- }
- // Paused overlay
- if(paused){
- ctx.save(); ctx.globalAlpha=.9; ctx.fillStyle='rgba(11,15,20,.55)'; ctx.fillRect(-S.w,S.h,-S.w*2,-S.h*2); ctx.restore();
- roundedText('Paused',0,0,22,'#0b1220','white');
- }
- ctx.restore();
- }
- function drawRoad(){
- const w=S.w, h=S.h; const interSize=88; // full size
- // asphalt
- ctx.fillStyle = '#0f172a';
- ctx.fillRect(-w, -interSize/2-120, w*2, interSize+240); // horizontal band
- ctx.fillRect(-interSize/2-120, -h, interSize+240, h*2); // vertical band
- // lane markings
- ctx.strokeStyle='rgba(255,255,255,.25)'; ctx.lineWidth=2; ctx.setLineDash([10,10]);
- ctx.beginPath();
- ctx.moveTo(-w,0); ctx.lineTo(w,0); // center horiz
- ctx.moveTo(0,-h); ctx.lineTo(0,h); // center vert
- ctx.stroke(); ctx.setLineDash([]);
- // intersection box
- ctx.strokeStyle='rgba(250,204,21,.6)'; ctx.lineWidth=3; roundedRect(-44,-44,88,88,6,true);
- // sidewalks
- ctx.fillStyle='#1f2937';
- roundedRect(-w,-h,w*2, h*2, 0, true);
- ctx.save(); ctx.globalCompositeOperation='destination-out';
- roundedRect(-w, -interSize/2-120, w*2, interSize+240, 0, true);
- roundedRect(-interSize/2-120, -h, interSize+240, h*2, 0, true);
- ctx.restore();
- // glow
- const g = ctx.createRadialGradient(0,0,10, 0,0,220); g.addColorStop(0,'rgba(99,102,241,.18)'); g.addColorStop(1,'transparent');
- ctx.fillStyle=g; ctx.beginPath(); ctx.arc(0,0,220,0,Math.PI*2); ctx.fill();
- }
- function drawLights(){
- const green = world.lightNS? 'rgba(74,222,128,.95)':'rgba(239,68,68,.9)';
- const red = world.lightNS? 'rgba(239,68,68,.9)':'rgba(74,222,128,.95)';
- // NS heads
- lightPuck(0,-72, green);
- lightPuck(0, 72, green);
- // EW heads
- lightPuck(-72,0, red);
- lightPuck(72,0, red);
- }
- function lightPuck(x,y,color){
- ctx.save(); ctx.translate(x,y);
- ctx.shadowColor=color; ctx.shadowBlur=18; ctx.fillStyle=color; roundedRect(-14,-14,28,28,8,true);
- ctx.restore();
- }
- function drawCar(c){
- ctx.save(); ctx.translate(c.x, c.y);
- ctx.fillStyle=c.col; roundedRect(-c.w/2, -c.h/2, c.w, c.h, 3, true);
- // windshield
- ctx.fillStyle='rgba(255,255,255,.25)'; if(c.vx){ roundedRect(-c.w/2+2, -c.h/2+2, c.w-4, 5, 2, true); } else { roundedRect(-c.w/2+2, -c.h/2+2, c.w-4, 5, 2, true); }
- // taillight glow
- ctx.fillStyle='rgba(248,113,113,.5)'; if(c.vx>0) roundedRect(-c.w/2, -c.h/2, 3, c.h, 1, true); if(c.vx<0) roundedRect(c.w/2-3, -c.h/2, 3, c.h, 1, true);
- if(c.vy>0) roundedRect(-c.w/2, -c.h/2, c.w, 3, 1, true); if(c.vy<0) roundedRect(-c.w/2, c.h/2-3, c.w, 3, 1, true);
- // impatience bar
- if(c.wait>0){ ctx.fillStyle='rgba(250,204,21,.9)'; const w = Math.min(1, c.wait/1.2)* (c.w); ctx.fillRect(-c.w/2, -c.h/2-6, w, 2); }
- ctx.restore();
- }
- // Helpers
- function roundedRect(x,y,w,h,r,fill){ ctx.beginPath(); rrPath(x,y,w,h,r); fill?ctx.fill():ctx.stroke(); }
- function rrPath(x,y,w,h,r){ const rr = Math.min(r, Math.abs(w/2), Math.abs(h/2));
- ctx.moveTo(x+rr,y); ctx.arcTo(x+w,y, x+w,y+h, rr); ctx.arcTo(x+w,y+h, x,y+h, rr); ctx.arcTo(x,y+h, x,y, rr); ctx.arcTo(x,y, x+w,y, rr); ctx.closePath(); }
- function roundedText(t, x,y, pad, bg='#000', fg='#fff'){
- ctx.save(); ctx.font='700 18px system-ui, -apple-system, Segoe UI, Roboto, Arial'; ctx.textAlign='center'; ctx.textBaseline='middle';
- const metrics=ctx.measureText(t); const tw=metrics.width; const th=28; const ww=tw+pad*2;
- ctx.fillStyle=bg; ctx.globalAlpha=.9; roundedRect(x-ww/2,y-th/2,ww,th,12,true);
- ctx.globalAlpha=1; ctx.fillStyle=fg; ctx.fillText(t,x,y+1);
- ctx.restore();
- }
- // Kick off
- toast('Tap to toggle lights • Hold Space to Hustle');
- </script>
- </body>
- </html>
Advertisement
Add Comment
Please, Sign In to add comment