Skip to content

Instantly share code, notes, and snippets.

@jasalt
Created July 19, 2026 08:52
Show Gist options
  • Select an option

  • Save jasalt/ffd0d7655a08a57f387689801c175fc2 to your computer and use it in GitHub Desktop.

Select an option

Save jasalt/ffd0d7655a08a57f387689801c175fc2 to your computer and use it in GitHub Desktop.
OSW Studio - "Make snake game where snake can be reversed" (gpt5.6-sol)
{
"project": {
"id": "526f4308-3083-401e-8684-a5f2f7ffd6f0",
"name": "reversible-snake",
"createdAt": "2026-07-18T18:46:56.855Z",
"updatedAt": "2026-07-19T08:51:21.099Z",
"settings": {
"runtime": "static"
},
"lastSavedCheckpointId": "cp_1784451075202_3",
"lastSavedAt": "2026-07-19T08:51:12.498Z",
"costTracking": {
"totalCost": 0,
"providerBreakdown": {
"opencode-go": {
"totalCost": 0,
"tokenUsage": {
"input": 12154,
"output": 199
},
"requestCount": 3,
"lastUpdated": "2026-07-18T21:47:55.766Z"
},
"openai-codex": {
"totalCost": 0,
"tokenUsage": {
"input": 57474,
"output": 7297
},
"requestCount": 4,
"lastUpdated": "2026-07-19T08:43:49.642Z"
}
},
"sessionHistory": [
{
"sessionId": "1784411257576",
"cost": 0,
"provider": "opencode-go",
"timestamp": "2026-07-18T21:47:37.612Z",
"tokenUsage": {
"input": 3912,
"output": 55
}
},
{
"sessionId": "1784411257576",
"cost": 0,
"provider": "opencode-go",
"timestamp": "2026-07-18T21:47:54.351Z",
"tokenUsage": {
"input": 4068,
"output": 74
}
},
{
"sessionId": "1784411257576",
"cost": 0,
"provider": "opencode-go",
"timestamp": "2026-07-18T21:47:55.766Z",
"tokenUsage": {
"input": 4174,
"output": 70
}
},
{
"sessionId": "1784411257576",
"cost": 0,
"provider": "openai-codex",
"timestamp": "2026-07-19T08:41:29.753Z",
"tokenUsage": {
"input": 4313,
"output": 108
}
},
{
"sessionId": "1784411257576",
"cost": 0,
"provider": "openai-codex",
"timestamp": "2026-07-19T08:41:36.047Z",
"tokenUsage": {
"input": 12188,
"output": 74
}
},
{
"sessionId": "1784411257576",
"cost": 0,
"provider": "openai-codex",
"timestamp": "2026-07-19T08:43:44.649Z",
"tokenUsage": {
"input": 17030,
"output": 7049
}
},
{
"sessionId": "1784411257576",
"cost": 0,
"provider": "openai-codex",
"timestamp": "2026-07-19T08:43:49.642Z",
"tokenUsage": {
"input": 23943,
"output": 66
}
}
]
}
},
"files": [
{
"id": "1a203663-6066-4077-b252-866ebd38cb13",
"projectId": "526f4308-3083-401e-8684-a5f2f7ffd6f0",
"path": "/PLAN.md",
"name": "PLAN.md",
"type": "text",
"content": "# Project Plan\n\n## Overview\nA polished, responsive browser Snake game with a dedicated reverse mechanic.\n\n## Pages / Views\n- `/index.html` β€” game board, score HUD, instructions, and touch controls\n\n## Tech Choices\n- CSS: Vanilla CSS\n- Icons: Text symbols\n- Fonts: Google Fonts (Lilita One + Nunito)\n- Images: None; canvas-rendered game art\n\n## Component / Module Plan\n- HUD and game canvas\n- Start/pause/reverse controls\n- Keyboard and touch input\n- Canvas game loop with local best score\n\n## Color Palette\n- Primary: #5528ff\n- Secondary: #19c7a3\n- Accent: #ffcc33\n- Neutral: #fff9e9 / #20154a\n\n## Execution Order\n1. Build semantic game shell\n2. Add responsive playful styling\n3. Implement game loop and reverse mechanic\n4. Build and verify",
"mimeType": "text/markdown",
"size": 742,
"createdAt": "2026-07-19T08:43:44.652Z",
"updatedAt": "2026-07-19T08:51:15.202Z",
"metadata": {
"isEntry": false
}
},
{
"id": "5de4ca68-5b84-4765-9679-b4746c0529c0",
"projectId": "526f4308-3083-401e-8684-a5f2f7ffd6f0",
"path": "/.PROMPT.md",
"name": ".PROMPT.md",
"type": "text",
"content": "🚨 PLATFORM CONSTRAINTS - READ THIS FIRST:\n\nThis is a STATIC WEBSITE builder - you can ONLY create client-side HTML/CSS/JS:\nβ€’ ❌ NO backend code (no Node.js, Python, PHP, Ruby, etc.)\nβ€’ ❌ NO server-side rendering (no Express, Next.js API routes, etc.)\nβ€’ ❌ NO databases or server-side storage\nβ€’ ❌ NO template engines (no Handlebars, Mustache, EJS, etc.)\nβ€’ βœ… ONLY static files that run in the browser (HTML, CSS, vanilla JS)\n\nROUTING IS AUTOMATIC:\nβ€’ Navigation works with standard HTML links: <a href=\"/about.html\">About</a>\nβ€’ Supports directory-based routing: /about/ β†’ /about/index.html\nβ€’ You can organize pages either way:\n - Direct: /about.html\n - Directory: /about/index.html (accessed as /about/ or /about)\nβ€’ DO NOT create routing logic (no History API, hash routing, or SPA routers)\nβ€’ DO NOT write JavaScript to handle page navigation\nβ€’ Create separate .html files for each page - the preview handles routing\n\nDIRECTORY INDEX RESOLUTION:\nβ€’ When a path ends with / or has no extension, the system tries:\n 1. Direct file: /about β†’ /about.html\n 2. Directory index: /about β†’ /about/index.html (fallback)\nβ€’ This allows clean URLs and organized file structures\nβ€’ Example: /products/ automatically serves /products/index.html\n\nWHAT YOU CAN BUILD:\nβ€’ Multi-page websites with .html files\nβ€’ Interactive features with vanilla JavaScript (DOM manipulation, fetch API, localStorage)\nβ€’ Responsive layouts with CSS (inline, <style> blocks, or external .css files)\nβ€’ Client-side data visualization, forms, animations, etc.\n\nES MODULES:\nYou can use <script type=\"module\"> and import/export between project files:\n import { initGame } from '/scripts/game.js';\n import { formatDate } from '/lib/utils.js';\nUse absolute paths starting with / for imports between files.\nFor third-party libraries, use CDN URLs: import confetti from 'https://esm.sh/canvas-confetti';\n\nMULTI-PAGE WEBSITES:\nCreate separate .html files for each page. Share styles and scripts across pages:\n- /index.html β€” Home page\n- /about.html β€” About page\n- /styles/style.css β€” Shared styles (link from every page)\n- /scripts/main.js β€” Shared scripts (include in every page)\n\nUse consistent navigation across pages:\n<nav>\n <a href=\"/index.html\">Home</a>\n <a href=\"/about.html\">About</a>\n <a href=\"/contact.html\">Contact</a>\n</nav>\n\nREUSABLE CONTENT:\nSince there is no template engine, the simplest reliable approach is to duplicate shared\nelements (nav, footer) directly in each page's HTML. This keeps them visible without\nJavaScript and works wherever the site is hosted.\n\nYou can also load them at runtime, but the fetch path is root-relative, so it only works\nwhen the site is served from the domain root (it breaks under a subpath):\n\n// Load shared nav into all pages\nfetch('/components/nav.html')\n .then(r => r.text())\n .then(html => document.getElementById('nav').innerHTML = html);\n\nBUILD OUTPUT:\n- The preview auto-refreshes when files change\n- Errors appear in the Terminal panel\n\nFILE CREATION GUIDELINES:\n\nPrefer editing existing files over creating new ones. Check if a file exists before creating.\n\nCreate when appropriate:\n- README.md for complex projects (3+ features/pages) β€” skip for simple single-file changes\n- Config files (package.json, tsconfig.json) only when functionality requires them\n- Component files matching request scope\n\nDo not create unless explicitly requested:\n- .gitignore, .prettierrc, .eslintrc (user preference)\n- .env files (sensitive, user creates manually)\n- LICENSE, temporary/scratch files",
"mimeType": "text/markdown",
"size": 3558,
"createdAt": "2026-07-18T18:46:56.874Z",
"updatedAt": "2026-07-19T08:51:15.277Z",
"metadata": {
"isEntry": false
}
},
{
"id": "6fe94e65-2c48-45a6-b382-5a72ec49669e",
"projectId": "526f4308-3083-401e-8684-a5f2f7ffd6f0",
"path": "/index.html",
"name": "index.html",
"type": "html",
"content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta name=\"theme-color\" content=\"#5528ff\">\n <title>FlipSnake β€” Reverse the rules</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Lilita+One&family=Nunito:wght@600;700;900&display=swap\" rel=\"stylesheet\">\n <link rel=\"stylesheet\" href=\"/styles/style.css\">\n</head>\n<body>\n <a class=\"skip-link\" href=\"#game\">Skip to game</a>\n <div class=\"blob blob-one\" aria-hidden=\"true\"></div>\n <div class=\"blob blob-two\" aria-hidden=\"true\"></div>\n\n <header class=\"site-head\">\n <a class=\"brand\" href=\"/\" aria-label=\"FlipSnake home\"><span>FLIP</span>SNAKE</a>\n <div class=\"mini-tip\"><kbd>R</kbd> reverses your snake!</div>\n </header>\n\n <main id=\"game\" class=\"game-layout\">\n <section class=\"intro\" aria-labelledby=\"game-title\">\n <p class=\"eyebrow\">Classic snake, one wild twist</p>\n <h1 id=\"game-title\">Wrong way?<br><em>Flip it.</em></h1>\n <p class=\"lede\">Eat berries, dodge the walls, and turn your tail into your head whenever things get tight.</p>\n <div class=\"instructions\">\n <div><span class=\"key-cluster\"><kbd>↑</kbd><kbd>←</kbd><kbd>↓</kbd><kbd>β†’</kbd></span><strong>Move</strong></div>\n <div><kbd class=\"wide-key\">R</kbd><strong>Reverse</strong></div>\n <div><kbd class=\"wide-key\">P</kbd><strong>Pause</strong></div>\n </div>\n </section>\n\n <section class=\"game-card\" aria-label=\"Snake game\">\n <div class=\"scorebar\">\n <div><span>Score</span><strong id=\"score\">00</strong></div>\n <div class=\"status-pill\" id=\"status\" role=\"status\" aria-live=\"polite\">Ready?</div>\n <div><span>Best</span><strong id=\"best\">00</strong></div>\n </div>\n <div class=\"board-wrap\">\n <canvas id=\"board\" width=\"528\" height=\"528\" aria-label=\"Snake game board\"></canvas>\n <div class=\"game-overlay\" id=\"overlay\">\n <div class=\"snake-face\" aria-hidden=\"true\">●‿●</div>\n <h2 id=\"overlay-title\">Ready to wiggle?</h2>\n <p id=\"overlay-copy\">Use arrow keys or the buttons below.</p>\n <button id=\"start-btn\" type=\"button\">Play now <span aria-hidden=\"true\">β†’</span></button>\n </div>\n </div>\n <div class=\"mobile-controls\" aria-label=\"Touch controls\">\n <div class=\"dpad\">\n <button data-dir=\"up\" aria-label=\"Move up\">↑</button>\n <button data-dir=\"left\" aria-label=\"Move left\">←</button>\n <button data-dir=\"down\" aria-label=\"Move down\">↓</button>\n <button data-dir=\"right\" aria-label=\"Move right\">β†’</button>\n </div>\n <button class=\"reverse-btn\" id=\"reverse-btn\" type=\"button\"><span aria-hidden=\"true\">β†­</span> Reverse</button>\n </div>\n </section>\n </main>\n\n <footer><span>Tip:</span> reverse just before a crash to make a clean getaway.</footer>\n <script src=\"/scripts/main.js\"></script>\n</body>\n</html>",
"mimeType": "text/html",
"size": 3099,
"createdAt": "2026-07-18T18:46:56.868Z",
"updatedAt": "2026-07-19T08:51:15.376Z",
"metadata": {
"isEntry": true
}
},
{
"id": "75a239fb-1e4b-4cb6-a94e-76e6ef8fde89",
"projectId": "526f4308-3083-401e-8684-a5f2f7ffd6f0",
"path": "/styles/style.css",
"name": "style.css",
"type": "css",
"content": "/*\n * DESIGN INTENT\n * Tone: candy-bright arcade playfulness\n * Fonts: Lilita One + Nunito\n * Palette: #fff9e9 #ffffff #20154a #5528ff\n * Layout signature: angled editorial intro beside a chunky game console\n * Memorable element: the tail-to-head reverse button\n */\n:root { --ink:#20154a; --purple:#5528ff; --mint:#19c7a3; --yellow:#ffcc33; --coral:#ff5f73; --paper:#fff9e9; }\n* { box-sizing:border-box; }\nhtml { min-width:320px; }\nbody { margin:0; min-height:100vh; overflow-x:hidden; background:var(--paper); color:var(--ink); font-family:\"Nunito\",sans-serif; }\nbutton, a { font:inherit; }\nbutton { min-height:44px; cursor:pointer; }\n::selection { background:var(--yellow); color:var(--ink); }\n.skip-link { position:fixed; top:-60px; left:1rem; z-index:20; padding:.7rem 1rem; border-radius:0 0 12px 12px; background:var(--ink); color:white; }\n.skip-link:focus { top:0; }\n*:focus-visible { outline:4px solid var(--yellow); outline-offset:3px; }\n.blob { position:fixed; border-radius:50%; filter:blur(2px); z-index:-1; opacity:.55; }\n.blob-one { width:21rem; height:21rem; background:#cfc4ff; top:-8rem; left:-8rem; }\n.blob-two { width:25rem; height:25rem; background:#a9f1df; right:-10rem; bottom:-10rem; }\n.site-head { max-width:1180px; margin:auto; padding:1.25rem clamp(1rem,4vw,3rem); display:flex; justify-content:space-between; align-items:center; }\n.brand { color:var(--ink); text-decoration:none; font:1.65rem \"Lilita One\",sans-serif; letter-spacing:.03em; transform:rotate(-2deg); }\n.brand span { color:var(--purple); }\n.mini-tip { display:none; font-weight:900; }\nkbd { display:inline-grid; place-items:center; min-width:2.1rem; height:2.1rem; padding:0 .45rem; border:2px solid var(--ink); border-radius:8px; background:white; box-shadow:0 3px 0 var(--ink); font:800 .8rem \"Nunito\",sans-serif; }\n.game-layout { max-width:1180px; margin:0 auto; padding:1rem clamp(1rem,4vw,3rem) 3rem; display:grid; gap:2rem; align-items:center; }\n.intro { max-width:34rem; }\n.eyebrow { display:inline-block; margin:0 0 .85rem; padding:.45rem .8rem; border-radius:999px; background:var(--yellow); font-weight:900; transform:rotate(-2deg); }\nh1 { margin:0; font:clamp(3.4rem,12vw,6.8rem)/.83 \"Lilita One\",sans-serif; letter-spacing:-.025em; }\nh1 em { color:var(--purple); font-style:normal; text-shadow:4px 5px 0 #cfc4ff; }\n.lede { max-width:31rem; margin:1.5rem 0; color:#594e78; font-size:clamp(1rem,2vw,1.15rem); line-height:1.65; font-weight:700; }\n.instructions { display:flex; flex-wrap:wrap; gap:.75rem; }\n.instructions > div { display:flex; align-items:center; gap:.6rem; padding:.55rem .7rem; border:2px solid var(--ink); border-radius:14px; background:white; box-shadow:3px 4px 0 var(--ink); }\n.instructions strong { font-size:.85rem; }\n.key-cluster { display:grid; grid-template-columns:repeat(3,1.55rem); grid-template-rows:repeat(2,1.55rem); }\n.key-cluster kbd { min-width:1.45rem; height:1.45rem; padding:0; font-size:.65rem; }\n.key-cluster kbd:first-child { grid-column:2; }\n.wide-key { min-width:2.5rem; }\n.game-card { min-width:0; width:100%; max-width:39rem; justify-self:center; padding:clamp(.7rem,2vw,1.1rem); border:3px solid var(--ink); border-radius:26px; background:white; box-shadow:8px 9px 0 var(--ink); transform:rotate(.5deg); }\n.scorebar { display:grid; grid-template-columns:1fr auto 1fr; align-items:center; gap:.5rem; padding:.2rem .35rem .85rem; text-align:center; }\n.scorebar > div:not(.status-pill) { display:flex; align-items:baseline; gap:.45rem; }\n.scorebar > div:last-child { justify-content:flex-end; }\n.scorebar span { color:#756a91; font-size:.75rem; font-weight:900; text-transform:uppercase; letter-spacing:.1em; }\n.scorebar strong { font:1.65rem \"Lilita One\",sans-serif; }\n.status-pill { padding:.35rem .7rem; border-radius:999px; background:#e6fff8; color:#087f68; font-size:.78rem; font-weight:900; }\n.board-wrap { position:relative; overflow:hidden; border:3px solid var(--ink); border-radius:18px; background:#f0ecff; line-height:0; }\ncanvas { display:block; width:100%; height:auto; aspect-ratio:1; }\n.game-overlay { position:absolute; inset:0; display:flex; flex-direction:column; justify-content:center; align-items:center; padding:1rem; background:rgba(32,21,74,.82); color:white; line-height:1.3; text-align:center; transition:.25s; }\n.game-overlay.hidden { opacity:0; visibility:hidden; transform:scale(1.08); }\n.snake-face { width:5rem; height:3.8rem; display:grid; place-items:center; border:3px solid white; border-radius:50% 50% 45% 45%; background:var(--mint); color:var(--ink); font:1.2rem \"Lilita One\"; transform:rotate(-6deg); animation:wiggle 1.5s infinite; }\n.game-overlay h2 { margin:.9rem 0 .2rem; font:clamp(1.8rem,5vw,2.8rem) \"Lilita One\"; }\n.game-overlay p { margin:.2rem 0 1rem; color:#e9e4ff; font-weight:700; }\n#start-btn { padding:.7rem 1.25rem; border:2px solid var(--ink); border-radius:999px; background:var(--yellow); color:var(--ink); box-shadow:0 5px 0 var(--ink); font-weight:900; transition:.18s; }\n#start-btn:hover { transform:translateY(-2px) scale(1.04); box-shadow:0 7px 0 var(--ink); }\n#start-btn:active { transform:translateY(4px); box-shadow:0 1px 0 var(--ink); }\n.mobile-controls { display:flex; justify-content:space-between; align-items:center; gap:1rem; padding-top:.8rem; }\n.dpad { display:grid; grid-template-columns:repeat(3,3rem); grid-template-rows:repeat(2,3rem); gap:.25rem; }\n.dpad button { border:2px solid var(--ink); border-radius:10px; background:#eeeaff; color:var(--ink); box-shadow:0 3px 0 var(--ink); font-weight:900; }\n.dpad button:first-child { grid-column:2; }\n.dpad button:nth-child(2) { grid-column:1; }\n.reverse-btn { align-self:stretch; padding:.6rem 1rem; border:2px solid var(--ink); border-radius:16px; background:var(--coral); color:white; box-shadow:0 4px 0 var(--ink); font-weight:900; }\n.reverse-btn span { display:block; font-size:1.6rem; line-height:1; }\n.dpad button:active,.reverse-btn:active { transform:translateY(3px); box-shadow:none; }\nfooter { padding:0 1rem 2rem; color:#756a91; text-align:center; font-size:.9rem; font-weight:700; }\nfooter span { color:var(--purple); font-weight:900; }\n@keyframes wiggle { 0%,100%{transform:rotate(-6deg)} 50%{transform:rotate(6deg) translateY(-4px)} }\n@media (min-width:768px) { .mini-tip{display:block}.game-layout{grid-template-columns:minmax(18rem,.8fr) minmax(24rem,1fr);gap:clamp(2rem,5vw,5rem)}.mobile-controls{padding-top:1rem} }\n@media (min-width:1024px) { .game-layout{padding-top:2rem}.game-card{max-width:40rem}.intro{transform:translateY(-1rem)} }\n@media (prefers-reduced-motion:reduce) { *,*::before,*::after{animation:none!important;transition:none!important} }\n",
"mimeType": "text/css",
"size": 6641,
"createdAt": "2026-07-18T18:46:56.872Z",
"updatedAt": "2026-07-19T08:51:15.379Z",
"metadata": {
"isEntry": false
}
},
{
"id": "a5ecf082-6e43-4280-a0ad-974a768ad967",
"projectId": "526f4308-3083-401e-8684-a5f2f7ffd6f0",
"path": "/scripts/main.js",
"name": "main.js",
"type": "js",
"content": "document.addEventListener('DOMContentLoaded', () => {\n const canvas = document.querySelector('#board');\n const ctx = canvas.getContext('2d');\n const scoreEl = document.querySelector('#score');\n const bestEl = document.querySelector('#best');\n const statusEl = document.querySelector('#status');\n const overlay = document.querySelector('#overlay');\n const overlayTitle = document.querySelector('#overlay-title');\n const overlayCopy = document.querySelector('#overlay-copy');\n const startBtn = document.querySelector('#start-btn');\n const reverseBtn = document.querySelector('#reverse-btn');\n const size = 22;\n const cell = canvas.width / size;\n let snake, food, direction, queuedDirection, score, timer;\n let state = 'ready';\n let best = Number(localStorage.getItem('flipsnake-best') || 0);\n bestEl.textContent = String(best).padStart(2, '0');\n\n function reset() {\n snake = [{x:11,y:11},{x:10,y:11},{x:9,y:11},{x:8,y:11}];\n direction = {x:1,y:0};\n queuedDirection = {...direction};\n score = 0;\n scoreEl.textContent = '00';\n placeFood();\n draw();\n }\n\n function placeFood() {\n do { food = {x:Math.floor(Math.random()*size), y:Math.floor(Math.random()*size)}; }\n while (snake?.some(part => part.x === food.x && part.y === food.y));\n }\n\n function roundedCell(x, y, color, inset = 2, radius = 7) {\n const px=x*cell+inset, py=y*cell+inset, s=cell-inset*2;\n ctx.fillStyle=color;\n ctx.beginPath();\n ctx.roundRect(px,py,s,s,radius);\n ctx.fill();\n }\n\n function draw() {\n ctx.fillStyle='#f0ecff'; ctx.fillRect(0,0,canvas.width,canvas.height);\n ctx.fillStyle='#ddd6ff';\n for(let y=0;y<size;y++) for(let x=0;x<size;x++) if((x+y)%2===0) ctx.fillRect(x*cell,y*cell,cell,cell);\n roundedCell(food.x,food.y,'#ff5f73',3,9);\n ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(food.x*cell+10,food.y*cell+9,3,0,Math.PI*2); ctx.fill();\n snake.forEach((part,i) => roundedCell(part.x,part.y,i===0?'#19c7a3':'#5528ff',2,i===0?9:7));\n const head=snake[0];\n if(head){\n const side={x:-direction.y,y:direction.x};\n [ -1, 1 ].forEach(n => {\n const ex=head.x*cell+cell/2+direction.x*5+side.x*n*5;\n const ey=head.y*cell+cell/2+direction.y*5+side.y*n*5;\n ctx.fillStyle='#20154a'; ctx.beginPath(); ctx.arc(ex,ey,2.2,0,Math.PI*2); ctx.fill();\n });\n }\n }\n\n function start() {\n clearInterval(timer);\n reset(); state='playing'; overlay.classList.add('hidden'); statusEl.textContent='Wiggling!';\n timer=setInterval(tick, Math.max(72, 135-score*2));\n }\n\n function tick() {\n direction=queuedDirection;\n const head={x:snake[0].x+direction.x,y:snake[0].y+direction.y};\n const hitWall=head.x<0||head.y<0||head.x>=size||head.y>=size;\n const hitSelf=snake.some((p,i) => i < snake.length-1 && p.x===head.x&&p.y===head.y);\n if(hitWall||hitSelf){ endGame(); return; }\n snake.unshift(head);\n if(head.x===food.x&&head.y===food.y){\n score++; scoreEl.textContent=String(score).padStart(2,'0'); placeFood();\n clearInterval(timer); timer=setInterval(tick,Math.max(72,135-score*2));\n } else snake.pop();\n draw();\n }\n\n function endGame() {\n clearInterval(timer); state='over';\n if(score>best){ best=score; localStorage.setItem('flipsnake-best',best); bestEl.textContent=String(best).padStart(2,'0'); }\n statusEl.textContent='Bonk!'; overlayTitle.textContent=score?'Nice wiggle!':'Oops β€” bonk!';\n overlayCopy.textContent=`You scored ${score}. Give it another flip!`; startBtn.innerHTML='Play again <span aria-hidden=\"true\">β†’</span>'; overlay.classList.remove('hidden');\n }\n\n function setDirection(next) {\n if(state==='ready'||state==='over') start();\n if(state!=='playing') return;\n if(next.x!==-direction.x||next.y!==-direction.y) queuedDirection=next;\n }\n\n function reverseSnake() {\n if(state!=='playing') return;\n snake.reverse();\n if(snake.length>1) direction={x:snake[0].x-snake[1].x,y:snake[0].y-snake[1].y};\n else direction={x:-direction.x,y:-direction.y};\n queuedDirection={...direction}; statusEl.textContent='Reversed!';\n canvas.animate([{transform:'scale(1)'},{transform:'scale(.96) rotate(1deg)'},{transform:'scale(1)'}],{duration:260});\n draw(); setTimeout(()=>{if(state==='playing')statusEl.textContent='Wiggling!';},650);\n }\n\n function togglePause() {\n if(state==='playing'){ clearInterval(timer); state='paused'; statusEl.textContent='Paused'; overlayTitle.textContent='Tiny break?'; overlayCopy.textContent='Press P or the button to keep wiggling.'; startBtn.textContent='Resume'; overlay.classList.remove('hidden'); }\n else if(state==='paused'){ state='playing'; statusEl.textContent='Wiggling!'; overlay.classList.add('hidden'); timer=setInterval(tick,Math.max(72,135-score*2)); }\n }\n\n const keys={ArrowUp:{x:0,y:-1},w:{x:0,y:-1},ArrowDown:{x:0,y:1},s:{x:0,y:1},ArrowLeft:{x:-1,y:0},a:{x:-1,y:0},ArrowRight:{x:1,y:0},d:{x:1,y:0}};\n document.addEventListener('keydown', event => {\n if(keys[event.key]){ event.preventDefault(); setDirection(keys[event.key]); }\n if(event.key.toLowerCase()==='r'){ event.preventDefault(); reverseSnake(); }\n if(event.key.toLowerCase()==='p'||event.key===' '){ event.preventDefault(); togglePause(); }\n });\n document.querySelectorAll('[data-dir]').forEach(button => button.addEventListener('click',()=>setDirection({up:{x:0,y:-1},down:{x:0,y:1},left:{x:-1,y:0},right:{x:1,y:0}}[button.dataset.dir])));\n reverseBtn.addEventListener('click',reverseSnake);\n startBtn.addEventListener('click',()=>state==='paused'?togglePause():start());\n reset();\n});\n",
"mimeType": "application/javascript",
"size": 5588,
"createdAt": "2026-07-18T18:46:56.873Z",
"updatedAt": "2026-07-19T08:51:15.516Z",
"metadata": {
"isEntry": false
}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment