Created
October 14, 2025 23:52
-
-
Save dbieber/2f98dc2e0e7404f7a0fb5d4e49c7b1dc to your computer and use it in GitHub Desktop.
Shake to Undo - Mobile Interface Demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>Shake to Undo - The Rage Interface</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| -webkit-tap-highlight-color: transparent; | |
| } | |
| body { | |
| font-family: 'Arial', sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| padding: 20px; | |
| overflow-x: hidden; | |
| } | |
| .container { | |
| max-width: 600px; | |
| margin: 0 auto; | |
| background: white; | |
| border-radius: 20px; | |
| padding: 30px; | |
| box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); | |
| } | |
| h1 { | |
| color: #764ba2; | |
| margin-bottom: 10px; | |
| font-size: 2em; | |
| } | |
| .subtitle { | |
| color: #666; | |
| margin-bottom: 20px; | |
| font-size: 0.9em; | |
| } | |
| .shake-meter { | |
| width: 100%; | |
| height: 40px; | |
| background: #e9ecef; | |
| border-radius: 20px; | |
| overflow: hidden; | |
| margin: 20px 0; | |
| position: relative; | |
| } | |
| .shake-fill { | |
| height: 100%; | |
| background: linear-gradient(90deg, #28a745, #ffc107, #dc3545); | |
| width: 0%; | |
| transition: width 0.3s ease; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: white; | |
| font-weight: bold; | |
| } | |
| .canvas-area { | |
| background: #f8f9fa; | |
| border: 3px dashed #ddd; | |
| border-radius: 15px; | |
| min-height: 300px; | |
| padding: 20px; | |
| margin: 20px 0; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .drawing-item { | |
| position: absolute; | |
| font-size: 3em; | |
| cursor: grab; | |
| user-select: none; | |
| transition: transform 0.2s ease; | |
| animation: appear 0.3s ease; | |
| } | |
| @keyframes appear { | |
| from { | |
| transform: scale(0) rotate(0deg); | |
| opacity: 0; | |
| } | |
| to { | |
| transform: scale(1) rotate(0deg); | |
| opacity: 1; | |
| } | |
| } | |
| .drawing-item:active { | |
| cursor: grabbing; | |
| transform: scale(1.2); | |
| } | |
| .emoji-picker { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); | |
| gap: 10px; | |
| margin: 20px 0; | |
| } | |
| .emoji-btn { | |
| font-size: 2.5em; | |
| padding: 10px; | |
| background: white; | |
| border: 2px solid #ddd; | |
| border-radius: 10px; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| } | |
| .emoji-btn:active { | |
| transform: scale(0.9); | |
| background: #f0f0f0; | |
| } | |
| .status { | |
| text-align: center; | |
| margin: 20px 0; | |
| font-weight: bold; | |
| color: #764ba2; | |
| font-size: 1.1em; | |
| } | |
| .undo-history { | |
| background: #fff3cd; | |
| padding: 15px; | |
| border-radius: 10px; | |
| margin: 20px 0; | |
| max-height: 150px; | |
| overflow-y: auto; | |
| } | |
| .history-item { | |
| padding: 8px; | |
| margin: 5px 0; | |
| background: white; | |
| border-radius: 5px; | |
| font-size: 0.9em; | |
| } | |
| @keyframes shake-warning { | |
| 0%, 100% { transform: translateX(0); } | |
| 25% { transform: translateX(-10px); } | |
| 75% { transform: translateX(10px); } | |
| } | |
| .shaking { | |
| animation: shake-warning 0.3s ease; | |
| } | |
| .instructions { | |
| background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); | |
| color: white; | |
| padding: 20px; | |
| border-radius: 15px; | |
| margin: 20px 0; | |
| text-align: center; | |
| font-weight: bold; | |
| } | |
| .rage-mode { | |
| background: #dc3545; | |
| color: white; | |
| padding: 15px; | |
| border-radius: 10px; | |
| text-align: center; | |
| font-weight: bold; | |
| display: none; | |
| margin: 10px 0; | |
| } | |
| .rage-mode.active { | |
| display: block; | |
| animation: pulse 0.5s ease infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.7; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>π± Shake to Undo</h1> | |
| <p class="subtitle">The Most Frustrating Way to Undo Ever</p> | |
| <div class="instructions"> | |
| π¨ Add emojis to the canvas below!<br> | |
| β οΈ Made a mistake? TOO BAD!<br> | |
| π€ Shake your phone VIOLENTLY to undo! | |
| </div> | |
| <div class="rage-mode" id="rageMode"> | |
| π₯ RAGE MODE ACTIVATED π₯<br> | |
| SHAKE HARDER TO UNDO! | |
| </div> | |
| <div class="shake-meter"> | |
| <div class="shake-fill" id="shakeFill">SHAKE: 0%</div> | |
| </div> | |
| <div class="status" id="status">Tap an emoji to add it!</div> | |
| <div class="canvas-area" id="canvas"> | |
| <div style="text-align: center; color: #999; padding: 60px 0;"> | |
| π Tap emojis below to add them here!<br> | |
| π± Shake phone to undo | |
| </div> | |
| </div> | |
| <div class="emoji-picker"> | |
| <button class="emoji-btn" onclick="addEmoji('π')">π</button> | |
| <button class="emoji-btn" onclick="addEmoji('β€οΈ')">β€οΈ</button> | |
| <button class="emoji-btn" onclick="addEmoji('π')">π</button> | |
| <button class="emoji-btn" onclick="addEmoji('π₯')">π₯</button> | |
| <button class="emoji-btn" onclick="addEmoji('β')">β</button> | |
| <button class="emoji-btn" onclick="addEmoji('π')">π</button> | |
| <button class="emoji-btn" onclick="addEmoji('π¨')">π¨</button> | |
| <button class="emoji-btn" onclick="addEmoji('π')">π</button> | |
| </div> | |
| <div class="undo-history" id="history"> | |
| <strong>Undo History:</strong> (shake to undo!) | |
| </div> | |
| </div> | |
| <script> | |
| let items = []; | |
| let shakeIntensity = 0; | |
| let lastShakeTime = Date.now(); | |
| let undoThreshold = 15; | |
| let rageMode = false; | |
| const canvas = document.getElementById('canvas'); | |
| const shakeFill = document.getElementById('shakeFill'); | |
| const status = document.getElementById('status'); | |
| const history = document.getElementById('history'); | |
| const rageMode | |
| Elem = document.getElementById('rageMode'); | |
| // Request motion permission for iOS 13+ | |
| if (typeof DeviceMotionEvent !== 'undefined' && typeof DeviceMotionEvent.requestPermission === 'function') { | |
| document.body.addEventListener('click', function() { | |
| DeviceMotionEvent.requestPermission() | |
| .then(response => { | |
| if (response === 'granted') { | |
| window.addEventListener('devicemotion', handleMotion); | |
| } | |
| }); | |
| }, { once: true }); | |
| } else { | |
| window.addEventListener('devicemotion', handleMotion); | |
| } | |
| function handleMotion(event) { | |
| const acc = event.accelerationIncludingGravity; | |
| const x = Math.abs(acc.x || 0); | |
| const y = Math.abs(acc.y || 0); | |
| const z = Math.abs(acc.z || 0); | |
| const totalShake = x + y + z; | |
| if (totalShake > 30) { | |
| shakeIntensity = Math.min(100, shakeIntensity + totalShake / 5); | |
| lastShakeTime = Date.now(); | |
| updateShakeMeter(); | |
| if (shakeIntensity >= undoThreshold) { | |
| performUndo(); | |
| } | |
| } | |
| } | |
| // Decay shake intensity over time | |
| setInterval(() => { | |
| if (Date.now() - lastShakeTime > 100) { | |
| shakeIntensity = Math.max(0, shakeIntensity - 2); | |
| updateShakeMeter(); | |
| } | |
| }, 50); | |
| function updateShakeMeter() { | |
| shakeFill.style.width = shakeIntensity + '%'; | |
| shakeFill.textContent = `SHAKE: ${Math.round(shakeIntensity)}%`; | |
| if (shakeIntensity > 50) { | |
| document.body.classList.add('shaking'); | |
| setTimeout(() => document.body.classList.remove('shaking'), 300); | |
| } | |
| } | |
| function addEmoji(emoji) { | |
| if (canvas.children.length === 1) { | |
| canvas.innerHTML = ''; | |
| } | |
| const item = document.createElement('div'); | |
| item.className = 'drawing-item'; | |
| item.textContent = emoji; | |
| item.style.left = Math.random() * (canvas.offsetWidth - 60) + 'px'; | |
| item.style.top = Math.random() * (canvas.offsetHeight - 60) + 'px'; | |
| // Make draggable on mobile | |
| let isDragging = false; | |
| item.addEventListener('touchstart', (e) => { | |
| isDragging = true; | |
| item.style.zIndex = 1000; | |
| }); | |
| item.addEventListener('touchmove', (e) => { | |
| if (!isDragging) return; | |
| e.preventDefault(); | |
| const touch = e.touches[0]; | |
| const rect = canvas.getBoundingClientRect(); | |
| item.style.left = (touch.clientX - rect.left - 30) + 'px'; | |
| item.style.top = (touch.clientY - rect.top - 30) + 'px'; | |
| }); | |
| item.addEventListener('touchend', () => { | |
| isDragging = false; | |
| }); | |
| canvas.appendChild(item); | |
| items.push({ emoji, element: item }); | |
| status.textContent = `Added ${emoji}! (${items.length} items)`; | |
| addToHistory(`Added ${emoji}`); | |
| // Increase difficulty | |
| if (items.length > 5 && !rageMode) { | |
| rageMode = true; | |
| undoThreshold = 25; | |
| rageModeElem.classList.add('active'); | |
| } | |
| } | |
| function performUndo() { | |
| if (items.length === 0) { | |
| status.textContent = 'β Nothing to undo!'; | |
| return; | |
| } | |
| const removed = items.pop(); | |
| removed.element.remove(); | |
| status.textContent = `β Undid ${removed.emoji}! Keep shaking to undo more!`; | |
| addToHistory(`π Undid ${removed.emoji}`); | |
| shakeIntensity = 0; | |
| updateShakeMeter(); | |
| if (items.length <= 5 && rageMode) { | |
| rageMode = false; | |
| undoThreshold = 15; | |
| rageModeElem.classList.remove('active'); | |
| } | |
| } | |
| function addToHistory(text) { | |
| const item = document.createElement('div'); | |
| item.className = 'history-item'; | |
| item.textContent = `${new Date().toLocaleTimeString()}: ${text}`; | |
| history.appendChild(item); | |
| history.scrollTop = history.scrollHeight; | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment