Files
miles-web/games.html
T

1050 lines
44 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>miles' game corner</title>
<meta name="description" content="Browser games and physics simulations — play them right here.">
<link rel="canonical" href="https://milesallen.site/games.html">
<meta property="og:title" content="miles' game corner">
<meta property="og:description" content="Browser games and physics simulations — play them right here.">
<meta property="og:image" content="https://milesallen.site/og.png">
<meta property="og:url" content="https://milesallen.site/games.html">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Space+Mono:wght@400;700&family=Inter:wght@400;500;600;800&display=swap" rel="stylesheet">
<link rel="icon" type="image/png" href="favicon.png">
<link rel="stylesheet" href="projects/style.css">
<style>
:root{--games-bg:#0a0a0f;}
body{background:var(--games-bg);}
nav{position:fixed;top:0;left:0;right:0;z-index:100;background:rgba(10,10,15,0.95);padding:14px 24px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid rgba(255,255,255,0.06);}
.nav-logo{font-family:var(--font-pixel);font-size:10px;color:var(--text);text-decoration:none;display:flex;align-items:center;gap:8px;}
.nav-logo .logo-dot{width:10px;height:10px;background:var(--accent);display:inline-block;}
.nav-links{display:flex;gap:18px;}
.nav-links a{font-family:var(--font-mono);font-size:11px;color:var(--muted);text-decoration:none;text-transform:uppercase;letter-spacing:.04em;}
.nav-links a:hover{color:var(--accent);}
.scroll-content{position:relative;z-index:1;padding-top:60px;}
.hero{min-height:60vh;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:80px 5vw 40px;position:relative;overflow:hidden;}
.hero h1{font-family:var(--font-pixel);font-size:clamp(24px,5vw,48px);color:var(--text);text-shadow:4px 4px 0 var(--accent);z-index:2;line-height:1.3;}
.hero .sub{font-family:var(--font-mono);font-size:12px;color:var(--muted);margin-top:12px;z-index:2;max-width:500px;}
.hero .emoji{font-size:60px;opacity:.3;margin-top:30px;z-index:2;}
.section-header{text-align:center;padding:60px 5vw 30px;}
.section-header .tag{font-family:var(--font-pixel);font-size:10px;color:var(--accent);letter-spacing:.12em;text-transform:uppercase;}
.section-header h2{font-family:var(--font-pixel);font-size:clamp(18px,3vw,32px);color:var(--text);margin-top:8px;text-transform:uppercase;}
.section-header p{font-family:var(--font-mono);font-size:12px;color:#999;max-width:500px;margin:10px auto 0;line-height:1.7;}
.games-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(380px,1fr));gap:28px;max-width:1100px;margin:0 auto;padding:0 5vw 60px;}
@media(max-width:480px){.games-grid{grid-template-columns:1fr;}}
.game-card{border:2px solid rgba(255,255,255,0.1);background:rgba(255,255,255,0.02);overflow:hidden;transition:border-color .3s,transform .3s;}
.game-card:hover{border-color:var(--accent);transform:translateY(-4px);}
.game-card .game-head{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid rgba(255,255,255,0.06);}
.game-card .game-head h3{font-family:var(--font-pixel);font-size:11px;color:var(--text);text-transform:uppercase;}
.game-card .game-head .badge{font-family:var(--font-mono);font-size:9px;color:var(--accent);border:1px solid var(--accent);padding:3px 8px;text-transform:uppercase;letter-spacing:.06em;}
.game-card .game-head .badge.physics{color:var(--tape);border-color:var(--tape);}
.game-card canvas{display:block;width:100%;background:#050508;cursor:crosshair;}
.game-card .game-foot{padding:12px 20px;display:flex;justify-content:space-between;align-items:center;border-top:1px solid rgba(255,255,255,0.06);}
.game-card .game-foot .controls{font-family:var(--font-mono);font-size:10px;color:var(--muted);}
.game-card .game-foot .controls kbd{background:rgba(255,255,255,0.08);padding:2px 6px;border:1px solid rgba(255,255,255,0.15);margin:0 2px;font-size:9px;}
.game-card .game-foot button{font-family:var(--font-pixel);font-size:8px;color:var(--accent);background:transparent;border:1px solid var(--accent);padding:6px 14px;cursor:pointer;text-transform:uppercase;transition:all .2s;}
.game-card .game-foot button:hover{background:var(--accent);color:#000;}
.footer{text-align:center;padding:40px 5vw;border-top:1px solid rgba(255,255,255,0.04);}
.footer .ft{font-family:var(--font-mono);font-size:10px;color:var(--muted);line-height:1.8;}
/* paper fold */
.paper-area{padding:28px 20px;text-align:center;display:flex;flex-direction:column;align-items:center;gap:16px;min-height:320px;justify-content:center;}
.paper-counter{font-family:var(--font-pixel);font-size:clamp(28px,6vw,40px);color:var(--text);text-shadow:2px 2px 0 var(--accent);}
.paper-counter span{color:var(--accent);}
.paper-visual{position:relative;width:200px;height:120px;display:flex;align-items:center;justify-content:center;}
.paper-sheet{width:180px;background:linear-gradient(180deg,#f5f2ed 0%,#e8e2d8 100%);border:1px solid rgba(255,255,255,0.3);border-radius:2px;transition:height .4s cubic-bezier(.34,1.56,.64,1),box-shadow .4s;position:relative;overflow:hidden;}
.paper-sheet::before{content:'';position:absolute;top:0;left:0;right:0;bottom:0;background:repeating-linear-gradient(0deg,transparent,transparent 7px,rgba(0,0,0,0.04) 7px,rgba(0,0,0,0.04) 8px);pointer-events:none;}
.paper-sheet::after{content:'';position:absolute;top:0;left:40px;bottom:0;width:1px;background:rgba(220,80,80,0.3);}
.paper-shadow{position:absolute;bottom:-8px;left:50%;transform:translateX(-50%);width:160px;height:12px;background:radial-gradient(ellipse,rgba(0,0,0,0.25),transparent 70%);transition:width .4s,opacity .4s;}
.paper-thickness{font-family:var(--font-mono);font-size:clamp(14px,2.5vw,18px);color:var(--muted);transition:color .3s;}
.paper-fact{font-family:var(--font-mono);font-size:11px;color:var(--muted);line-height:1.7;max-width:300px;min-height:40px;opacity:.7;transition:opacity .3s;}
.paper-btns{display:flex;gap:12px;margin-top:4px;}
.paper-btn{font-family:var(--font-pixel);font-size:10px;padding:10px 24px;border:2px solid;cursor:pointer;text-transform:uppercase;letter-spacing:.06em;transition:all .2s;background:transparent;}
.paper-btn.fold{color:var(--accent);border-color:var(--accent);}
.paper-btn.fold:hover:not(:disabled){background:var(--accent);color:#000;box-shadow:0 0 20px rgba(255,61,154,0.3);}
.paper-btn.unfold{color:var(--muted);border-color:rgba(255,255,255,0.15);}
.paper-btn.unfold:hover:not(:disabled){color:var(--text);border-color:var(--text);}
.paper-btn:disabled{opacity:.25;cursor:default;}
.paper-area.fold-anim .paper-sheet{animation:paperFoldAnim .5s ease-in-out;}
@keyframes paperFoldAnim{0%{transform:rotateX(0)}50%{transform:rotateX(-80deg) scaleY(.3)}100%{transform:rotateX(0)}}
.paper-wide{grid-column:1/-1;max-width:600px;justify-self:center;width:100%;}
/* clean theme overrides */
body.clean{--bg:#18181e;--text:#e4e0eb;--muted:#7b7890;--accent:#b8a9ff;}
body.clean::before,body.clean::after{display:none!important;}
body.clean nav{background:rgba(34,32,42,0.9);backdrop-filter:blur(8px);border-color:#333045;}
body.clean .nav-logo{font-size:13px;font-weight:600;font-family:'Inter',sans-serif;}
body.clean .nav-links a{font-family:'Inter',sans-serif;font-size:13px;font-weight:500;text-transform:none;letter-spacing:0;color:var(--muted);}
body.clean .nav-links a:hover{color:var(--text);}
body.clean .scroll-content{padding-top:56px;}
body.clean .hero{min-height:auto;padding:80px 24px;}
body.clean .hero h1{font-family:'Inter',sans-serif;font-weight:700;font-size:clamp(28px,5vw,42px);color:var(--text);text-shadow:none;}
body.clean .hero .sub{font-family:'Inter',sans-serif;font-size:14px;color:var(--muted);}
body.clean .section-header .tag{font-family:'Inter',sans-serif;font-size:10px;font-weight:600;letter-spacing:0.04em;color:var(--accent);}
body.clean .section-header h2{font-family:'Inter',sans-serif;font-weight:600;font-size:clamp(18px,3vw,24px);color:var(--text);text-transform:none;}
body.clean .section-header p{font-family:'Inter',sans-serif;color:var(--muted);}
body.clean .game-card{background:#22202a;border-color:#333045;border-radius:12px;}
body.clean .game-card:hover{border-color:var(--accent);transform:none;}
body.clean .game-card .game-head h3{font-family:'Inter',sans-serif;font-size:14px;font-weight:600;}
body.clean .game-card .game-head .badge{font-family:'Inter',sans-serif;font-size:10px;font-weight:600;border-radius:6px;}
body.clean .game-card .game-foot .controls{font-family:'Inter',sans-serif;font-size:11px;}
body.clean .game-card .game-foot button{font-family:'Inter',sans-serif;font-size:12px;font-weight:600;border-radius:8px;text-transform:none;}
body.clean .footer .ft{font-family:'Inter',sans-serif;font-size:11px;color:var(--muted);}
/* maximalist overrides */
body.maximalist nav{background:var(--bg);backdrop-filter:none;border-bottom:6px solid var(--black);padding:14px 24px;}
body.maximalist .scroll-content{padding-top:56px;}
body.maximalist .hero{min-height:auto;padding:90px 24px 60px;background:var(--tape)!important;}
body.maximalist .hero h1{font-family:var(--shout);font-size:clamp(30px,6vw,64px);color:var(--black);text-shadow:none;text-transform:uppercase;line-height:.95;}
body.maximalist .hero .sub{font-family:var(--font-mono);font-weight:700;font-size:14px;color:var(--black);margin-top:16px;}
body.maximalist .section-header{padding:50px 24px 24px;}
body.maximalist .section-header .tag{font-family:var(--font-pixel);font-size:13px;color:var(--tape);letter-spacing:2px;}
body.maximalist .section-header h2{font-family:var(--shout);font-size:clamp(24px,5vw,48px);color:var(--white);text-transform:uppercase;text-shadow:none;letter-spacing:0;}
body.maximalist .section-header p{font-family:var(--font-mono);font-size:14px;color:var(--muted);line-height:1.7;}
body.maximalist .game-card{background:var(--white)!important;border:5px solid var(--black)!important;border-radius:0!important;box-shadow:10px 10px 0 var(--pink)!important;}
body.maximalist .game-card:nth-child(even){box-shadow:10px 10px 0 var(--blue)!important;}
body.maximalist .game-card:hover{transform:translate(-3px,-3px);box-shadow:14px 14px 0 var(--tape)!important;}
body.maximalist .game-card .game-head{border-color:var(--black);}
body.maximalist .game-card .game-head h3{font-family:var(--font-pixel);font-size:12px;color:var(--black);letter-spacing:1px;}
body.maximalist .game-card .game-head .badge{font-family:var(--font-pixel);font-size:9px;color:var(--black);background:var(--tape);border:2px solid var(--black);border-radius:0;}
body.maximalist .game-card .game-head .badge.physics{background:var(--accent);color:var(--white);}
body.maximalist .game-card .game-foot{border-color:var(--black);}
body.maximalist .game-card .game-foot .controls{font-family:var(--font-mono);font-size:11px;color:var(--muted);}
body.maximalist .game-card .game-foot button{font-family:var(--font-pixel);font-size:10px;color:var(--black);background:var(--tape);border:3px solid var(--black);border-radius:0;padding:8px 16px;box-shadow:4px 4px 0 var(--black);}
body.maximalist .game-card .game-foot button:hover{transform:translate(-2px,-2px);box-shadow:6px 6px 0 var(--black);}
body.maximalist .footer{border-color:var(--white);padding:40px 24px;}
body.maximalist .footer .ft{font-family:var(--font-pixel);font-size:12px;color:var(--muted);letter-spacing:1px;}
/* synthwave overrides */
body.synthwave .hero h1{font-family:var(--shout);font-size:clamp(30px,5vw,56px);text-shadow:0 0 20px rgba(255,46,151,0.5),0 0 60px rgba(0,229,255,0.2);animation:none;line-height:1.1;}
body.synthwave .section-header .tag{color:var(--blue);}
body.synthwave .section-header h2{font-family:var(--shout);font-size:clamp(22px,4vw,40px);text-shadow:0 0 18px rgba(255,46,151,0.4);}
body.synthwave .section-header p{color:var(--muted);}
body.synthwave .game-card{border-color:var(--accent);background:rgba(255,255,255,0.03);}
body.synthwave .game-card:hover{border-color:var(--blue);box-shadow:0 0 25px rgba(255,46,151,0.15);}
body.synthwave .game-card .game-head .badge{color:var(--blue);border-color:var(--blue);}
body.synthwave .game-card .game-head .badge.physics{color:var(--accent);border-color:var(--accent);}
body.synthwave .footer .ft{color:var(--muted);}
/* phosphor overrides */
body.phosphor .hero h1{font-family:var(--shout);font-size:clamp(28px,5vw,52px);text-shadow:0 0 8px var(--text);animation:none;line-height:1.1;}
body.phosphor .section-header h2{font-family:var(--shout);font-size:clamp(20px,4vw,36px);text-shadow:0 0 6px var(--text);}
body.phosphor .game-card{border-color:rgba(57,255,108,0.35);}
body.phosphor .game-card:hover{border-color:var(--accent);box-shadow:0 0 20px rgba(57,255,108,0.15);}
body.phosphor .game-card .game-head .badge{color:var(--accent);border-color:var(--accent);}
body.phosphor .footer .ft{color:var(--muted);}
/* chrome overrides */
body.chrome .hero h1{font-family:var(--shout);font-size:clamp(32px,6vw,60px);text-shadow:8px 8px 0 rgba(255,255,255,0.15);animation:none;line-height:1.1;}
body.chrome .section-header h2{font-family:var(--shout);font-size:clamp(24px,4vw,44px);text-shadow:4px 4px 0 rgba(255,255,255,0.15);}
body.chrome .section-header p{color:var(--muted);}
body.chrome .game-card{border-color:var(--text);background:var(--bg);}
body.chrome .game-card:hover{background:var(--text);color:var(--bg);}
body.chrome .game-card:hover .game-head h3{color:var(--bg);}
body.chrome .game-card .game-head .badge{color:var(--text);border-color:var(--text);}
body.chrome .footer .ft{color:var(--muted);}
/* paper overrides */
body.paper .hero h1{font-family:var(--shout);font-size:clamp(30px,5vw,54px);color:var(--text);text-shadow:none;animation:none;line-height:1.1;}
body.paper .hero .sub{color:var(--muted);}
body.paper .section-header .tag{color:var(--accent);}
body.paper .section-header h2{font-family:var(--shout);font-size:clamp(24px,4vw,40px);color:var(--text);text-shadow:none;}
body.paper .section-header p{color:var(--muted);}
body.paper .game-card{border:1px solid #d9d2c2;background:#fff;border-radius:14px 10px 16px 11px/11px 16px 10px 14px;box-shadow:2px 3px 0 rgba(43,38,32,0.06),5px 8px 18px rgba(43,38,32,0.07);}
body.paper .game-card:hover{transform:translateY(-2px) rotate(-0.5deg);box-shadow:4px 6px 0 rgba(43,38,32,0.08),8px 12px 24px rgba(43,38,32,0.1);}
body.paper .game-card .game-head h3{color:var(--text);}
body.paper .game-card .game-head .badge{color:var(--accent);border-color:var(--accent);border-radius:6px;}
body.paper .game-card .game-foot button{border-radius:8px 5px 9px 6px/6px 9px 5px 8px;border-color:#d9d2c2;color:var(--muted);}
body.paper .game-card .game-foot button:hover{border-color:var(--accent);color:var(--accent);background:#fff;}
body.paper .footer{border-color:#d9d2c2;}
body.paper .footer .ft{color:var(--muted);}
</style>
</head>
<body>
<div class="cursor-dot" id="cursorDot"></div>
<div class="cursor-ring" id="cursorRing"></div>
<div class="progress-bar" id="progressBar"></div>
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"></button>
<div class="shortcut-modal" id="shortcutModal">
<div class="shortcut-card">
<h3>Keyboard Shortcuts</h3>
<div class="shortcut-row"><span>Go Home</span><kbd>H</kbd></div>
<div class="shortcut-row"><span>Toggle theme</span><kbd>T</kbd></div>
<div class="shortcut-row"><span>Show shortcuts</span><kbd>?</kbd></div>
<div class="close-hint">Press Esc or ? to close</div>
</div>
</div>
<div class="flash" id="flash"></div>
<div class="toast-stack" id="toastStack"></div>
<nav class="mobile-nav">
<a href="index.html">Home</a>
<a href="projects/fedl.html">FEDL</a>
<a href="projects/omniemu.html">OmniEmu</a>
<a href="https://github.com/mileswolfallen2" target="_blank">GitHub</a>
</nav>
<nav>
<a href="index.html" class="nav-logo"><span class="logo-dot"></span> milesallen</a>
<div class="nav-links">
<a href="index.html#about">About</a>
<a href="index.html#skills">Skills</a>
<a href="index.html#projects">Projects</a>
<a href="index.html#contact">Contact</a>
</div>
</nav>
<div class="scroll-content">
<section class="hero" id="hero">
<h1>GAME CORNER</h1>
<p class="sub">Browser games &amp; physics sims — type <strong style="color:var(--accent)">games</strong> on the home page to get here</p>
<div class="emoji">🕹️</div>
</section>
<!-- WEB GAMES -->
<div class="section-header">
<div class="tag">▸ Web Games ◂</div>
<h2>Play These</h2>
<p>Classic arcade games rebuilt in vanilla JS. Click to focus, then play.</p>
</div>
<div class="games-grid">
<!-- SNAKE -->
<div class="game-card" id="snakeCard">
<div class="game-head">
<h3>🐍 Snake</h3>
<span class="badge">Arcade</span>
</div>
<canvas id="snakeCanvas" width="400" height="300"></canvas>
<div class="game-foot">
<span class="controls"><kbd></kbd><kbd></kbd><kbd></kbd><kbd></kbd> Move</span>
<button onclick="snakeReset()">Restart</button>
</div>
</div>
<!-- BREAKOUT -->
<div class="game-card" id="breakoutCard">
<div class="game-head">
<h3>🧱 Breakout</h3>
<span class="badge">Arcade</span>
</div>
<canvas id="breakoutCanvas" width="400" height="300"></canvas>
<div class="game-foot">
<span class="controls"><kbd></kbd><kbd></kbd> Move paddle</span>
<button onclick="breakoutReset()">Restart</button>
</div>
</div>
<!-- PONG -->
<div class="game-card" id="pongCard">
<div class="game-head">
<h3>🏓 Pong</h3>
<span class="badge">Arcade</span>
</div>
<canvas id="pongCanvas" width="400" height="300"></canvas>
<div class="game-foot">
<span class="controls"><kbd>W</kbd><kbd>S</kbd> Left · <kbd></kbd><kbd></kbd> Right</span>
<button onclick="pongReset()">Restart</button>
</div>
</div>
</div>
<!-- PHYSICS GAMES -->
<div class="section-header">
<div class="tag">▸ Physics ◂</div>
<h2>Physics Playground</h2>
<p>Click and drag to interact. Real(ish) physics in your browser.</p>
</div>
<div class="games-grid">
<!-- GRAVITY SIM -->
<div class="game-card" id="gravityCard">
<div class="game-head">
<h3>🪐 Gravity Well</h3>
<span class="badge physics">Physics</span>
</div>
<canvas id="gravityCanvas" width="400" height="300"></canvas>
<div class="game-foot">
<span class="controls">Click to place mass · Drag to fling</span>
<button onclick="gravityReset()">Clear</button>
</div>
</div>
<!-- PARTICLE PHYSICS -->
<div class="game-card" id="particleCard">
<div class="game-head">
<h3>⚛️ Particles</h3>
<span class="badge physics">Physics</span>
</div>
<canvas id="particleCanvas" width="400" height="300"></canvas>
<div class="game-foot">
<span class="controls">Click to explode · Watch them bounce</span>
<button onclick="particleReset()">Clear</button>
</div>
</div>
<!-- ROPE / CHAIN -->
<div class="game-card" id="ropeCard">
<div class="game-head">
<h3>🔗 Pendulum Chain</h3>
<span class="badge physics">Physics</span>
</div>
<canvas id="ropeCanvas" width="400" height="300"></canvas>
<div class="game-foot">
<span class="controls">Click and drag a node</span>
<button onclick="ropeReset()">Reset</button>
</div>
</div>
<!-- PAPER FOLD -->
<div class="game-card paper-wide" id="paperCard">
<div class="game-head">
<h3>📄 Paper</h3>
<span class="badge physics">Physics</span>
</div>
<div class="paper-area" id="paperArea">
<div class="paper-counter"><span id="paperFolds">0</span> folds</div>
<div class="paper-visual" id="paperVisual">
<div class="paper-sheet" id="paperSheet"></div>
<div class="paper-shadow" id="paperShadow"></div>
</div>
<div class="paper-thickness" id="paperThickness">0.1mm</div>
<div class="paper-fact" id="paperFact">Your paper is now 0.1mm tall.<br>You have a piece of paper. It is 0.1mm thick.</div>
<div class="paper-btns">
<button class="paper-btn unfold" id="paperUnfold" onclick="paperUnfold()" disabled>Unfold</button>
<button class="paper-btn fold" id="paperFoldBtn" onclick="paperFold()">Fold</button>
</div>
</div>
<div class="game-foot">
<span class="controls">Fold it 42 times to reach the moon</span>
<button onclick="paperReset()">Reset</button>
</div>
</div>
</div>
<footer>
<div class="ft">Game Corner · Miles · 2026<br>made with canvas + vanilla JS</div>
</footer>
</div>
<script src="projects/script.js"></script>
<script>
/* ═══════════════════════════════════════════
SNAKE
═══════════════════════════════════════════ */
(function(){
const cvs = document.getElementById('snakeCanvas');
if(!cvs) return;
const ctx = cvs.getContext('2d');
const W = 400, H = 300, GRID = 20;
const COLS = W/GRID, ROWS = H/GRID;
let snake, dir, nextDir, food, score, alive, timer;
function init(){
snake = [{x:10,y:10},{x:9,y:10},{x:8,y:10}];
dir = {x:1,y:0};
nextDir = {x:1,y:0};
score = 0;
alive = true;
placeFood();
clearInterval(timer);
timer = setInterval(tick, 120);
}
function placeFood(){
let tries = 0;
do {
food = {x:Math.floor(Math.random()*COLS), y:Math.floor(Math.random()*ROWS)};
tries++;
} while(snake.some(s => s.x===food.x && s.y===food.y) && tries < 200);
}
function tick(){
if(!alive) return;
dir = nextDir;
const head = {x:snake[0].x+dir.x, y:snake[0].y+dir.y};
if(head.x<0||head.x>=COLS||head.y<0||head.y>=ROWS||snake.some(s=>s.x===head.x&&s.y===head.y)){
alive = false;
clearInterval(timer);
return;
}
snake.unshift(head);
if(head.x===food.x&&head.y===food.y){
score++;
placeFood();
} else {
snake.pop();
}
draw();
}
function draw(){
ctx.fillStyle='#050508';
ctx.fillRect(0,0,W,H);
// grid
ctx.strokeStyle='rgba(255,255,255,0.03)';
for(let x=0;x<W;x+=GRID){ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,H);ctx.stroke();}
for(let y=0;y<H;y+=GRID){ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(W,y);ctx.stroke();}
// food
ctx.fillStyle='#FF3D9A';
ctx.shadowColor='#FF3D9A';
ctx.shadowBlur=10;
ctx.fillRect(food.x*GRID+2, food.y*GRID+2, GRID-4, GRID-4);
ctx.shadowBlur=0;
// snake
snake.forEach((s,i)=>{
const bright = 1 - (i/snake.length)*0.6;
ctx.fillStyle = `rgba(57,255,20,${bright})`;
ctx.shadowColor='#39FF14';
ctx.shadowBlur=i===0?12:4;
ctx.fillRect(s.x*GRID+1, s.y*GRID+1, GRID-2, GRID-2);
});
ctx.shadowBlur=0;
// score
ctx.fillStyle='rgba(255,255,255,0.5)';
ctx.font='10px "Press Start 2P"';
ctx.fillText('SCORE: '+score, 10, 18);
if(!alive){
ctx.fillStyle='rgba(0,0,0,0.6)';
ctx.fillRect(0,0,W,H);
ctx.fillStyle='#FF3D9A';
ctx.font='16px "Press Start 2P"';
ctx.textAlign='center';
ctx.fillText('GAME OVER',W/2,H/2-10);
ctx.fillStyle='rgba(255,255,255,0.5)';
ctx.font='10px "Press Start 2P"';
ctx.fillText('SCORE: '+score,W/2,H/2+16);
ctx.textAlign='left';
}
}
window.snakeReset = function(){
init();
draw();
};
document.addEventListener('keydown', function(e){
const k = e.key;
if(k==='ArrowUp'&&dir.y!==1) nextDir={x:0,y:-1};
else if(k==='ArrowDown'&&dir.y!==-1) nextDir={x:0,y:1};
else if(k==='ArrowLeft'&&dir.x!==1) nextDir={x:-1,y:0};
else if(k==='ArrowRight'&&dir.x!==-1) nextDir={x:1,y:0};
});
init();
draw();
})();
/* ═══════════════════════════════════════════
BREAKOUT
═══════════════════════════════════════════ */
(function(){
const cvs = document.getElementById('breakoutCanvas');
if(!cvs) return;
const ctx = cvs.getContext('2d');
const W=400, H=300;
let paddle, ball, bricks, score, alive, timer;
const BRICK_ROWS=4, BRICK_COLS=8, BRICK_W=W/BRICK_COLS-2, BRICK_H=16;
const COLORS=['#FF3D9A','#FFC93C','#39FF14','#00E5FF'];
function init(){
paddle={x:W/2-30,y:H-20,w:60,h:10};
ball={x:W/2,y:H-30,dx:2.5,dy:-2.5,r:5};
bricks=[];
score=0;
alive=true;
for(let r=0;r<BRICK_ROWS;r++)
for(let c=0;c<BRICK_COLS;c++)
bricks.push({x:c*(BRICK_W+2)+1,y:r*(BRICK_H+4)+30,w:BRICK_W,h:BRICK_H,color:COLORS[r],alive:true});
clearInterval(timer);
timer=setInterval(tick,16);
}
function tick(){
if(!alive) return;
ball.x+=ball.dx;
ball.y+=ball.dy;
if(ball.x-ball.r<0||ball.x+ball.r>W){ball.dx*=-1;ball.x=Math.max(ball.r,Math.min(W-ball.r,ball.x));}
if(ball.y-ball.r<0){ball.dy*=-1;ball.y=ball.r;}
if(ball.y+ball.r>H){alive=false;clearInterval(timer);draw();return;}
if(ball.dy>0&&ball.y+ball.r>=paddle.y&&ball.x>=paddle.x&&ball.x<=paddle.x+paddle.w){
ball.dy=-Math.abs(ball.dy);
ball.dx+=((ball.x-(paddle.x+paddle.w/2))/paddle.w)*1.5;
}
bricks.forEach(b=>{
if(!b.alive) return;
if(ball.x+ball.r>b.x&&ball.x-ball.r<b.x+b.w&&ball.y+ball.r>b.y&&ball.y-ball.r<b.y+b.h){
b.alive=false;
ball.dy*=-1;
score+=10;
}
});
if(bricks.every(b=>!b.alive)){
alive=false;clearInterval(timer);
}
draw();
}
function draw(){
ctx.fillStyle='#050508';
ctx.fillRect(0,0,W,H);
// bricks
bricks.forEach(b=>{
if(!b.alive) return;
ctx.fillStyle=b.color;
ctx.shadowColor=b.color;
ctx.shadowBlur=6;
ctx.fillRect(b.x,b.y,b.w,b.h);
});
ctx.shadowBlur=0;
// paddle
ctx.fillStyle='#fff';
ctx.fillRect(paddle.x,paddle.y,paddle.w,paddle.h);
// ball
ctx.fillStyle='#FFC93C';
ctx.shadowColor='#FFC93C';
ctx.shadowBlur=10;
ctx.beginPath();
ctx.arc(ball.x,ball.y,ball.r,0,Math.PI*2);
ctx.fill();
ctx.shadowBlur=0;
// score
ctx.fillStyle='rgba(255,255,255,0.5)';
ctx.font='10px "Press Start 2P"';
ctx.fillText('SCORE: '+score,10,18);
if(!alive){
ctx.fillStyle='rgba(0,0,0,0.5)';
ctx.fillRect(0,0,W,H);
ctx.fillStyle=bricks.every(b=>!b.alive)?'#39FF14':'#FF3D9A';
ctx.font='14px "Press Start 2P"';
ctx.textAlign='center';
ctx.fillText(bricks.every(b=>!b.alive)?'YOU WIN!':'GAME OVER',W/2,H/2);
ctx.textAlign='left';
}
}
window.breakoutReset=function(){init();draw();};
cvs.addEventListener('mousemove',function(e){
const r=cvs.getBoundingClientRect();
paddle.x=((e.clientX-r.left)/r.width)*W-paddle.w/2;
paddle.x=Math.max(0,Math.min(W-paddle.w,paddle.x));
});
cvs.addEventListener('touchmove',function(e){
e.preventDefault();
const r=cvs.getBoundingClientRect();
paddle.x=((e.touches[0].clientX-r.left)/r.width)*W-paddle.w/2;
paddle.x=Math.max(0,Math.min(W-paddle.w,paddle.x));
},{passive:false});
init();
draw();
})();
/* ═══════════════════════════════════════════
PONG
═══════════════════════════════════════════ */
(function(){
const cvs=document.getElementById('pongCanvas');
if(!cvs) return;
const ctx=cvs.getContext('2d');
const W=400,H=300;
let p1,p2,ball,s1,s2,alive,timer;
const PH=60,PW=10;
function init(){
p1={x:10,y:H/2-PH/2};
p2={x:W-PW-10,y:H/2-PH/2};
ball={x:W/2,y:H/2,dx:3*(Math.random()>0.5?1:-1),dy:2*(Math.random()>0.5?1:-1),r:5};
s1=0;s2=0;alive=true;
clearInterval(timer);
timer=setInterval(tick,16);
}
function tick(){
if(!alive) return;
ball.x+=ball.dx;ball.y+=ball.dy;
if(ball.y-ball.r<0||ball.y+ball.r>H) ball.dy*=-1;
// paddle collisions
if(ball.dx<0&&ball.x-ball.r<=p1.x+PW&&ball.y>=p1.y&&ball.y<=p1.y+PH){
ball.dx=Math.abs(ball.dx)*1.02;ball.x=p1.x+PW+ball.r;
ball.dy+=(ball.y-(p1.y+PH/2))*0.15;
}
if(ball.dx>0&&ball.x+ball.r>=p2.x&&ball.y>=p2.y&&ball.y<=p2.y+PH){
ball.dx=-Math.abs(ball.dx)*1.02;ball.x=p2.x-ball.r;
ball.dy+=(ball.y-(p2.y+PH/2))*0.15;
}
// scoring
if(ball.x<0){s2++;resetBall();}
if(ball.x>W){s1++;resetBall();}
// AI for left paddle
const target=ball.y-PH/2;
p1.y+=(target-p1.y)*0.06;
p1.y=Math.max(0,Math.min(H-PH,p1.y));
draw();
}
function resetBall(){
ball.x=W/2;ball.y=H/2;
ball.dx=3*(Math.random()>0.5?1:-1);
ball.dy=2*(Math.random()>0.5?1:-1);
}
function draw(){
ctx.fillStyle='#050508';
ctx.fillRect(0,0,W,H);
// center line
ctx.strokeStyle='rgba(255,255,255,0.1)';
ctx.setLineDash([4,4]);
ctx.beginPath();ctx.moveTo(W/2,0);ctx.lineTo(W/2,H);ctx.stroke();
ctx.setLineDash([]);
// paddles
ctx.fillStyle='#FF3D9A';
ctx.shadowColor='#FF3D9A';ctx.shadowBlur=8;
ctx.fillRect(p1.x,p1.y,PW,PH);
ctx.fillStyle='#00E5FF';
ctx.shadowColor='#00E5FF';
ctx.fillRect(p2.x,p2.y,PW,PH);
ctx.shadowBlur=0;
// ball
ctx.fillStyle='#FFC93C';
ctx.shadowColor='#FFC93C';ctx.shadowBlur=10;
ctx.beginPath();ctx.arc(ball.x,ball.y,ball.r,0,Math.PI*2);ctx.fill();
ctx.shadowBlur=0;
// score
ctx.fillStyle='rgba(255,255,255,0.4)';
ctx.font='20px "Press Start 2P"';
ctx.textAlign='center';
ctx.fillText(s1,W/4,35);
ctx.fillText(s2,W*3/4,35);
ctx.textAlign='left';
}
window.pongReset=function(){init();draw();};
document.addEventListener('keydown',function(e){
if(e.key==='ArrowUp') p2.y=Math.max(0,p2.y-20);
if(e.key==='ArrowDown') p2.y=Math.min(H-PH,p2.y+20);
});
init();
draw();
})();
/* ═══════════════════════════════════════════
GRAVITY WELL
═══════════════════════════════════════════ */
(function(){
const cvs=document.getElementById('gravityCanvas');
if(!cvs) return;
const ctx=cvs.getContext('2d');
const W=400,H=300;
let bodies=[], dragging=null, dragStart=null, timer;
function createBodies(){
bodies=[];
for(let i=0;i<60;i++){
const angle=Math.random()*Math.PI*2;
const dist=30+Math.random()*100;
const speed=0.3+Math.random()*0.5;
bodies.push({
x:W/2+Math.cos(angle)*dist,
y:H/2+Math.sin(angle)*dist,
vx:Math.cos(angle+Math.PI/2)*speed,
vy:Math.sin(angle+Math.PI/2)*speed,
mass:1+Math.random()*2,
hue:Math.random()*60+100
});
}
// central mass
bodies.push({x:W/2,y:H/2,vx:0,vy:0,mass:50,hue:50,star:true});
}
function tick(){
const G=0.5;
bodies.forEach(b=>{
if(b.star||b===dragging) return;
bodies.forEach(o=>{
if(o===b) return;
const dx=o.x-b.x, dy=o.y-b.y;
const dist=Math.sqrt(dx*dx+dy*dy)+5;
const force=G*o.mass*b.mass/(dist*dist);
b.vx+=dx/dist*force*0.01;
b.vy+=dy/dist*force*0.01;
});
b.x+=b.vx;b.y+=b.vy;
// wrap
if(b.x<-10) b.x=W+10;
if(b.x>W+10) b.x=-10;
if(b.y<-10) b.y=H+10;
if(b.y>H+10) b.y=-10;
});
draw();
}
function draw(){
ctx.fillStyle='rgba(5,5,8,0.25)';
ctx.fillRect(0,0,W,H);
bodies.forEach(b=>{
ctx.fillStyle=b.star?`hsl(45,90%,65%)`:`hsl(${b.hue},80%,60%)`;
ctx.shadowColor=ctx.fillStyle;
ctx.shadowBlur=b.star?20:6;
ctx.beginPath();
ctx.arc(b.x,b.y,b.star?8:b.mass*1.5,0,Math.PI*2);
ctx.fill();
});
// drag line
if(dragging&&dragStart){
ctx.strokeStyle='rgba(255,61,154,0.4)';
ctx.lineWidth=1;
ctx.beginPath();
ctx.moveTo(dragStart.x,dragStart.y);
ctx.lineTo(dragging.x,dragging.y);
ctx.stroke();
ctx.lineWidth=1;
}
ctx.shadowBlur=0;
}
window.gravityReset=function(){createBodies();};
cvs.addEventListener('mousedown',function(e){
const r=cvs.getBoundingClientRect();
const mx=((e.clientX-r.left)/r.width)*W;
const my=((e.clientY-r.top)/r.height)*H;
dragStart={x:mx,y:my};
dragging={x:mx,y:my,vx:0,vy:0,mass:2,hue:330,star:false};
});
cvs.addEventListener('mousemove',function(e){
if(!dragging) return;
const r=cvs.getBoundingClientRect();
dragging.x=((e.clientX-r.left)/r.width)*W;
dragging.y=((e.clientY-r.top)/r.height)*H;
});
cvs.addEventListener('mouseup',function(){
if(dragging&&dragStart){
dragging.vx=(dragStart.x-dragging.x)*0.05;
dragging.vy=(dragStart.y-dragging.y)*0.05;
bodies.push(dragging);
}
dragging=null;dragStart=null;
});
createBodies();
timer=setInterval(tick,16);
})();
/* ═══════════════════════════════════════════
PARTICLE BOUNCE
═══════════════════════════════════════════ */
(function(){
const cvs=document.getElementById('particleCanvas');
if(!cvs) return;
const ctx=cvs.getContext('2d');
const W=400,H=300;
let particles=[];
function spawnBurst(x,y,n){
for(let i=0;i<n;i++){
const angle=Math.random()*Math.PI*2;
const speed=1+Math.random()*4;
particles.push({
x:x,y:y,
vx:Math.cos(angle)*speed,
vy:Math.sin(angle)*speed,
r:2+Math.random()*3,
life:1,
decay:0.005+Math.random()*0.01,
hue:Math.random()*360
});
}
}
function tick(){
ctx.fillStyle='rgba(5,5,8,0.15)';
ctx.fillRect(0,0,W,H);
particles.forEach(p=>{
p.vy+=0.04; // gravity
p.x+=p.vx;p.y+=p.vy;
// bounce
if(p.x-p.r<0){p.x=p.r;p.vx*=-0.8;}
if(p.x+p.r>W){p.x=W-p.r;p.vx*=-0.8;}
if(p.y+p.r>H){p.y=H-p.r;p.vy*=-0.7;p.vx*=0.95;}
if(p.y-p.r<0){p.y=p.r;p.vy*=-0.8;}
p.life-=p.decay;
ctx.fillStyle=`hsla(${p.hue},80%,60%,${p.life})`;
ctx.shadowColor=`hsl(${p.hue},80%,60%)`;
ctx.shadowBlur=6;
ctx.beginPath();
ctx.arc(p.x,p.y,p.r*p.life,0,Math.PI*2);
ctx.fill();
});
ctx.shadowBlur=0;
particles=particles.filter(p=>p.life>0);
// ambient particles
if(Math.random()<0.05){
spawnBurst(Math.random()*W,Math.random()*H,1);
}
}
window.particleReset=function(){particles=[];};
cvs.addEventListener('click',function(e){
const r=cvs.getBoundingClientRect();
const mx=((e.clientX-r.left)/r.width)*W;
const my=((e.clientY-r.top)/r.height)*H;
spawnBurst(mx,my,30);
});
setInterval(tick,16);
spawnBurst(W/2,H/2,20);
})();
/* ═══════════════════════════════════════════
PENDULUM CHAIN
═══════════════════════════════════════════ */
(function(){
const cvs=document.getElementById('ropeCanvas');
if(!cvs) return;
const ctx=cvs.getContext('2d');
const W=400,H=300;
const N=15, SPACING=14;
let nodes=[], dragNode=null;
function init(){
nodes=[];
for(let i=0;i<N;i++){
nodes.push({
x:W/2+i*SPACING,
y:40+Math.abs(i)*4,
ox:W/2+i*SPACING,
oy:40+Math.abs(i)*4,
pinned:i===0
});
}
}
function tick(){
const GRAVITY=0.5;
const ITERATIONS=5;
// verlet integration
nodes.forEach(n=>{
if(n.pinned||n===dragNode) return;
const vx=(n.x-n.ox)*0.99;
const vy=(n.y-n.oy)*0.99;
n.ox=n.x;n.oy=n.y;
n.x+=vx;n.y+=vy+GRAVITY;
});
// constraints
for(let iter=0;iter<ITERATIONS;iter++){
for(let i=0;i<nodes.length-1;i++){
const a=nodes[i],b=nodes[i+1];
const dx=b.x-a.x, dy=b.y-a.y;
const dist=Math.sqrt(dx*dx+dy*dy);
const diff=(dist-SPACING)/dist*0.5;
if(!a.pinned&&a!==dragNode){a.x+=dx*diff;a.y+=dy*diff;}
if(!b.pinned&&b!==dragNode){b.x-=dx*diff;b.y-=dy*diff;}
}
// pin first node
nodes[0].x=nodes[0].ox;nodes[0].y=nodes[0].oy;
}
// bounds
nodes.forEach(n=>{
if(n.pinned) return;
if(n.y>H-5) n.y=H-5;
if(n.x<5) n.x=5;
if(n.x>W-5) n.x=W-5;
});
draw();
}
function draw(){
ctx.fillStyle='rgba(5,5,8,0.3)';
ctx.fillRect(0,0,W,H);
// chain links
ctx.strokeStyle='rgba(255,255,255,0.15)';
ctx.lineWidth=1;
ctx.beginPath();
nodes.forEach((n,i)=>{
if(i===0) ctx.moveTo(n.x,n.y); else ctx.lineTo(n.x,n.y);
});
ctx.stroke();
// nodes
nodes.forEach((n,i)=>{
const t=i/(nodes.length-1);
const hue=330-t*180;
ctx.fillStyle=`hsl(${hue},80%,60%)`;
ctx.shadowColor=ctx.fillStyle;
ctx.shadowBlur=n.pinned?12:6;
ctx.beginPath();
ctx.arc(n.x,n.y,n.pinned?5:3.5,0,Math.PI*2);
ctx.fill();
});
ctx.shadowBlur=0;
// anchor
ctx.fillStyle='rgba(255,255,255,0.3)';
ctx.fillRect(W/2-20,35,40,3);
}
window.ropeReset=function(){init();};
function getNode(mx,my){
for(let i=nodes.length-1;i>=0;i--){
const dx=nodes[i].x-mx,dy=nodes[i].y-my;
if(Math.sqrt(dx*dx+dy*dy)<15) return nodes[i];
}
return null;
}
cvs.addEventListener('mousedown',function(e){
const r=cvs.getBoundingClientRect();
const mx=((e.clientX-r.left)/r.width)*W;
const my=((e.clientY-r.top)/r.height)*H;
dragNode=getNode(mx,my);
});
cvs.addEventListener('mousemove',function(e){
if(!dragNode) return;
const r=cvs.getBoundingClientRect();
dragNode.x=((e.clientX-r.left)/r.width)*W;
dragNode.y=((e.clientY-r.top)/r.height)*H;
dragNode.ox=dragNode.x;dragNode.oy=dragNode.y;
});
cvs.addEventListener('mouseup',function(){dragNode=null;});
cvs.addEventListener('mouseleave',function(){dragNode=null;});
init();
setInterval(tick,16);
})();
/* ═══════════════════════════════════════════
PAPER FOLD
═══════════════════════════════════════════ */
(function(){
let folds=0;
const BASE_THICKNESS=0.1; // mm
const MAX_FOLDS=42;
const facts=[
[0,"Your paper is now 0.1mm tall.","You have a piece of paper. It is 0.1mm thick."],
[1,"Your paper is now 0.2mm tall.","Still basically nothing."],
[2,"Your paper is now 0.4mm tall.","You can barely feel the difference."],
[3,"Your paper is now 0.8mm tall.","About the thickness of a credit card."],
[4,"Your paper is now 1.6mm tall.","Getting into cardboard territory."],
[5,"Your paper is now 3.2mm tall.","Thicker than a typical smartphone."],
[6,"Your paper is now 6.4mm tall.","About the width of your pinky finger."],
[7,"Your paper is now 1.3cm tall.","Now we're talking. A thick stack."],
[8,"Your paper is now 2.6cm tall.","Thicker than a baseball."],
[9,"Your paper is now 5.1cm tall.","About 2 inches. Respectable."],
[10,"Your paper is now 10.2cm tall.","Your paper is now taller than a can of soda."],
[11,"Your paper is now 20.5cm tall.","About the height of a ruler."],
[12,"Your paper is now 41.0cm tall.","You could use it as a table."],
[13,"Your paper is now 81.9cm tall.","About waist height on an adult."],
[14,"Your paper is now 1.6m tall.","Taller than most people."],
[15,"Your paper is now 3.3m tall.","It's a small wall now."],
[16,"Your paper is now 6.6m tall.","About 2 stories high."],
[17,"Your paper is now 13.1m tall.","Taller than the Statue of Liberty's torch."],
[18,"Your paper is now 26.2m tall.","About 8 stories. A small building."],
[19,"Your paper is now 52.4m tall.","Taller than the Tower of Pisa."],
[20,"Your paper is now 104.9m tall.","That's a skyscraper."],
[21,"Your paper is now 209.7m tall.","About the height of Big Ben."],
[22,"Your paper is now 419.4m tall.","Taller than the Empire State Building."],
[23,"Your paper is now 838.9m tall.","Almost a kilometer."],
[24,"Your paper is now 1.7km tall.","You can see it from space. Sort of."],
[25,"Your paper is now 3.4km tall.","Higher than most commercial flights."],
[26,"Your paper is now 6.7km tall.","You're in the clouds now."],
[27,"Your paper is now 13.4km tall.","Above the cruising altitude of a jumbo jet."],
[28,"Your paper is now 26.8km tall.","You've entered the stratosphere."],
[29,"Your paper is now 53.7km tall.","The sky is getting very dark."],
[30,"Your paper is now 107.4km tall.","You've crossed the Kármán line. This is space."],
[31,"Your paper is now 214.7km tall.","Low Earth orbit territory."],
[32,"Your paper is now 429.5km tall.","Higher than the International Space Station."],
[33,"Your paper is now 859.0km tall.","Deep into orbit."],
[34,"Your paper is now 1,717.9km tall.","You can see entire countries."],
[35,"Your paper is now 3,435.9km tall.","Halfway to the Moon? Not even close."],
[36,"Your paper is now 6,871.7km tall.","That's about the radius of Earth."],
[37,"Your paper is now 13,743.4km tall.","Your paper is now bigger than Earth."],
[38,"Your paper is now 27,486.9km tall.","About the radius of Earth's orbit around itself... no wait."],
[39,"Your paper is now 54,973.7km tall.","Getting out there."],
[40,"Your paper is now 109,947.5km tall.","About a third of the way to the Moon."],
[41,"Your paper is now 219,895.0km tall.","Two thirds of the way to the Moon."],
[42,"Your paper is now 439,790.0km tall.","The Moon is 384,400km away. You passed it."]
];
function formatThickness(mm){
if(mm<1) return mm.toFixed(1)+'mm';
if(mm<1000) return mm.toFixed(1)+'mm';
if(mm<1000000) return (mm/1000).toFixed(1)+'cm';
if(mm<1000000000) return (mm/1000000).toFixed(2)+'m';
if(mm<1000000000000) return (mm/1000000000).toFixed(2)+'km';
return (mm/1000000000000).toFixed(2)+' thousand km';
}
function update(){
const thickness=BASE_THICKNESS*Math.pow(2,folds);
const fact=facts[Math.min(folds,facts.length-1)];
document.getElementById('paperFolds').textContent=folds;
document.getElementById('paperThickness').textContent=formatThickness(thickness);
document.getElementById('paperFact').innerHTML=fact[1]+'<br>'+fact[2];
document.getElementById('paperUnfold').disabled=folds===0;
document.getElementById('paperFoldBtn').disabled=folds>=MAX_FOLDS;
// visual height: map folds to pixel height
const maxH=260;
const h=Math.min(maxH, 4+Math.pow(folds,1.8)*1.5);
const sheet=document.getElementById('paperSheet');
const shadow=document.getElementById('paperShadow');
sheet.style.height=h+'px';
const sw=Math.max(40,160-folds*2);
shadow.style.width=sw+'px';
shadow.style.opacity=Math.min(0.5,0.1+folds*0.02);
// color shift as it gets thick
if(folds>30){
sheet.style.background='linear-gradient(180deg,#c8e6ff 0%,#89b4f0 100%)';
} else if(folds>20){
sheet.style.background='linear-gradient(180deg,#e8e0d0 0%,#d4c8b0 100%)';
} else {
sheet.style.background='linear-gradient(180deg,#f5f2ed 0%,#e8e2d8 100%)';
}
}
window.paperFold=function(){
if(folds>=MAX_FOLDS) return;
folds++;
const area=document.getElementById('paperArea');
area.classList.remove('fold-anim');
void area.offsetWidth;
area.classList.add('fold-anim');
update();
};
window.paperUnfold=function(){
if(folds<=0) return;
folds--;
update();
};
window.paperReset=function(){
folds=0;
update();
};
update();
})();
/* ═══════════════════════════════════════════
GLOBAL: HOME KEY
═══════════════════════════════════════════ */
document.addEventListener('keydown',function(e){
if(e.key==='h'||e.key==='H') window.location.href='index.html';
});
</script>
</body>
</html>