Skip to content

Instantly share code, notes, and snippets.

@dbieber
Created October 14, 2025 23:52
Show Gist options
  • Select an option

  • Save dbieber/2f98dc2e0e7404f7a0fb5d4e49c7b1dc to your computer and use it in GitHub Desktop.

Select an option

Save dbieber/2f98dc2e0e7404f7a0fb5d4e49c7b1dc to your computer and use it in GitHub Desktop.
Shake to Undo - Mobile Interface Demo
<!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