Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Retry - The Persistence Engine</title> | |
| <script src="https://unpkg.com/@phosphor-icons/web"></script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;600;700&display=swap'); | |
| :root { | |
| --primary: #6366f1; | |
| --primary-glow: rgba(99, 102, 241, 0.5); | |
| --success: #10b981; | |
| --success-glow: rgba(16, 185, 129, 0.5); | |
| --danger: #ef4444; | |
| --danger-glow: rgba(239, 68, 68, 0.5); | |
| --warning: #f59e0b; | |
| --bg: #0f172a; | |
| --surface: rgba(30, 41, 59, 0.7); | |
| --text: #f8fafc; | |
| --text-muted: #94a3b8; | |
| --border: rgba(148, 163, 184, 0.2); | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Space Grotesk', sans-serif; | |
| background: var(--bg); | |
| color: var(--text); | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| background-image: | |
| radial-gradient(circle at 20% 50%, rgba(99, 102, 241, 0.15) 0%, transparent 50%), | |
| radial-gradient(circle at 80% 80%, rgba(16, 185, 129, 0.1) 0%, transparent 50%); | |
| } | |
| /* Animated background grid */ | |
| .bg-grid { | |
| position: fixed; | |
| inset: 0; | |
| background-image: | |
| linear-gradient(rgba(99, 102, 241, 0.03) 1px, transparent 1px), | |
| linear-gradient(90deg, rgba(99, 102, 241, 0.03) 1px, transparent 1px); | |
| background-size: 50px 50px; | |
| pointer-events: none; | |
| z-index: -1; | |
| } | |
| header { | |
| padding: 1.5rem; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| border-bottom: 1px solid var(--border); | |
| background: rgba(15, 23, 42, 0.8); | |
| backdrop-filter: blur(12px); | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| } | |
| .logo { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| letter-spacing: -0.02em; | |
| } | |
| .logo i { | |
| color: var(--primary); | |
| animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| } | |
| .anycoder-link { | |
| color: var(--text-muted); | |
| text-decoration: none; | |
| font-size: 0.875rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| padding: 0.5rem 1rem; | |
| border-radius: 9999px; | |
| border: 1px solid var(--border); | |
| transition: all 0.3s ease; | |
| background: var(--surface); | |
| backdrop-filter: blur(8px); | |
| } | |
| .anycoder-link:hover { | |
| border-color: var(--primary); | |
| color: var(--primary); | |
| transform: translateY(-2px); | |
| box-shadow: 0 0 20px var(--primary-glow); | |
| } | |
| main { | |
| max-width: 800px; | |
| margin: 0 auto; | |
| padding: 2rem 1rem; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 2rem; | |
| } | |
| .game-container { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 24px; | |
| padding: 2rem; | |
| backdrop-filter: blur(16px); | |
| box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .game-container::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| height: 1px; | |
| background: linear-gradient(90deg, transparent, var(--primary), transparent); | |
| animation: scan 3s linear infinite; | |
| } | |
| @keyframes scan { | |
| 0% { transform: translateX(-100%); } | |
| 100% { transform: translateX(100%); } | |
| } | |
| .stats-bar { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); | |
| gap: 1rem; | |
| margin-bottom: 2rem; | |
| } | |
| .stat-card { | |
| background: rgba(15, 23, 42, 0.6); | |
| padding: 1rem; | |
| border-radius: 16px; | |
| border: 1px solid var(--border); | |
| text-align: center; | |
| transition: transform 0.2s; | |
| } | |
| .stat-card:hover { | |
| transform: translateY(-2px); | |
| border-color: var(--primary); | |
| } | |
| .stat-label { | |
| font-size: 0.75rem; | |
| text-transform: uppercase; | |
| letter-spacing: 0.1em; | |
| color: var(--text-muted); | |
| margin-bottom: 0.5rem; | |
| } | |
| .stat-value { | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| color: var(--text); | |
| } | |
| .stat-value.success { color: var(--success); } | |
| .stat-value.danger { color: var(--danger); } | |
| .stat-value.warning { color: var(--warning); } | |
| .game-area { | |
| position: relative; | |
| height: 200px; | |
| background: rgba(15, 23, 42, 0.8); | |
| border-radius: 16px; | |
| border: 2px solid var(--border); | |
| overflow: hidden; | |
| margin-bottom: 2rem; | |
| cursor: pointer; | |
| user-select: none; | |
| touch-action: manipulation; | |
| } | |
| .track { | |
| position: absolute; | |
| top: 50%; | |
| left: 0; | |
| right: 0; | |
| height: 4px; | |
| background: var(--border); | |
| transform: translateY(-50%); | |
| } | |
| .target-zone { | |
| position: absolute; | |
| top: 50%; | |
| height: 60px; | |
| transform: translateY(-50%); | |
| background: linear-gradient(90deg, transparent, var(--success-glow), transparent); | |
| border-left: 2px solid var(--success); | |
| border-right: 2px solid var(--success); | |
| border-radius: 4px; | |
| transition: all 0.3s ease; | |
| } | |
| .mover { | |
| position: absolute; | |
| top: 50%; | |
| width: 40px; | |
| height: 40px; | |
| background: var(--primary); | |
| border-radius: 50%; | |
| transform: translate(-50%, -50%); | |
| box-shadow: 0 0 30px var(--primary-glow); | |
| transition: left 0.1s linear; | |
| z-index: 10; | |
| } | |
| .mover.perfect { | |
| background: var(--success); | |
| box-shadow: 0 0 40px var(--success-glow); | |
| } | |
| .mover.fail { | |
| background: var(--danger); | |
| box-shadow: 0 0 40px var(--danger-glow); | |
| } | |
| .trail { | |
| position: absolute; | |
| top: 50%; | |
| height: 2px; | |
| background: linear-gradient(90deg, transparent, var(--primary-glow)); | |
| transform: translateY(-50%); | |
| opacity: 0.5; | |
| pointer-events: none; | |
| } | |
| .controls { | |
| display: flex; | |
| gap: 1rem; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| } | |
| button { | |
| font-family: inherit; | |
| font-size: 1rem; | |
| font-weight: 600; | |
| padding: 1rem 2rem; | |
| border: none; | |
| border-radius: 12px; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| button::before { | |
| content: ''; | |
| position: absolute; | |
| inset: 0; | |
| background: linear-gradient(45deg, transparent, rgba(255,255,255,0.1), transparent); | |
| transform: translateX(-100%); | |
| transition: transform 0.6s; | |
| } | |
| button:hover::before { | |
| transform: translateX(100%); | |
| } | |
| .btn-primary { | |
| background: var(--primary); | |
| color: white; | |
| box-shadow: 0 4px 20px var(--primary-glow); | |
| } | |
| .btn-primary:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 30px var(--primary-glow); | |
| } | |
| .btn-primary:active { | |
| transform: translateY(0); | |
| } | |
| .btn-secondary { | |
| background: transparent; | |
| color: var(--text); | |
| border: 2px solid var(--border); | |
| } | |
| .btn-secondary:hover { | |
| border-color: var(--primary); | |
| color: var(--primary); | |
| } | |
| .history-section { | |
| margin-top: 2rem; | |
| } | |
| .section-title { | |
| font-size: 1.25rem; | |
| margin-bottom: 1rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .retry-history { | |
| display: flex; | |
| gap: 0.5rem; | |
| flex-wrap: wrap; | |
| max-height: 150px; | |
| overflow-y: auto; | |
| padding: 1rem; | |
| background: rgba(15, 23, 42, 0.4); | |
| border-radius: 12px; | |
| border: 1px solid var(--border); | |
| } | |
| .retry-dot { | |
| width: 32px; | |
| height: 32px; | |
| border-radius: 8px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 0.875rem; | |
| font-weight: 700; | |
| animation: popIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); | |
| } | |
| @keyframes popIn { | |
| 0% { transform: scale(0); opacity: 0; } | |
| 100% { transform: scale(1); opacity: 1; } | |
| } | |
| .retry-dot.success { | |
| background: rgba(16, 185, 129, 0.2); | |
| color: var(--success); | |
| border: 1px solid var(--success); | |
| } | |
| .retry-dot.fail { | |
| background: rgba(239, 68, 68, 0.2); | |
| color: var(--danger); | |
| border: 1px solid var(--danger); | |
| } | |
| .message { | |
| text-align: center; | |
| padding: 1rem; | |
| border-radius: 12px; | |
| margin-bottom: 1rem; | |
| font-weight: 600; | |
| opacity: 0; | |
| transform: translateY(-10px); | |
| transition: all 0.3s ease; | |
| } | |
| .message.show { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| .message.success { | |
| background: rgba(16, 185, 129, 0.1); | |
| color: var(--success); | |
| border: 1px solid var(--success); | |
| } | |
| .message.fail { | |
| background: rgba(239, 68, 68, 0.1); | |
| color: var(--danger); | |
| border: 1px solid var(--danger); | |
| } | |
| .progress-bar { | |
| width: 100%; | |
| height: 4px; | |
| background: var(--border); | |
| border-radius: 2px; | |
| margin-top: 2rem; | |
| overflow: hidden; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: linear-gradient(90deg, var(--primary), var(--success)); | |
| transition: width 0.5s ease; | |
| box-shadow: 0 0 10px var(--success-glow); | |
| } | |
| .level-indicator { | |
| text-align: center; | |
| margin-top: 0.5rem; | |
| font-size: 0.875rem; | |
| color: var(--text-muted); | |
| } | |
| @media (max-width: 640px) { | |
| .game-container { | |
| padding: 1rem; | |
| } | |
| .stats-bar { | |
| grid-template-columns: repeat(2, 1fr); | |
| } | |
| .mover { | |
| width: 30px; | |
| height: 30px; | |
| } | |
| } | |
| .particle { | |
| position: absolute; | |
| pointer-events: none; | |
| opacity: 0; | |
| animation: particle 1s ease-out forwards; | |
| } | |
| @keyframes particle { | |
| 0% { | |
| transform: translate(0, 0) scale(1); | |
| opacity: 1; | |
| } | |
| 100% { | |
| transform: translate(var(--tx), var(--ty)) scale(0); | |
| opacity: 0; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="bg-grid"></div> | |
| <header> | |
| <div class="logo"> | |
| <i class="ph ph-arrow-counter-clockwise"></i> | |
| <span>RETRY</span> | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link"> | |
| <i class="ph ph-code"></i> | |
| Built with anycoder | |
| </a> | |
| </header> | |
| <main> | |
| <div class="game-container"> | |
| <div class="stats-bar"> | |
| <div class="stat-card"> | |
| <div class="stat-label">Level</div> | |
| <div class="stat-value" id="level">1</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-label">Retries</div> | |
| <div class="stat-value warning" id="retries">0</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-label">Success Rate</div> | |
| <div class="stat-value" id="successRate">100%</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-label">Streak</div> | |
| <div class="stat-value success" id="streak">0</div> | |
| </div> | |
| </div> | |
| <div id="message" class="message"></div> | |
| <div class="game-area" id="gameArea"> | |
| <div class="track"></div> | |
| <div class="target-zone" id="targetZone"></div> | |
| <div class="mover" id="mover"></div> | |
| </div> | |
| <div class="controls"> | |
| <button class="btn-primary" id="actionBtn" onclick="game.attempt()"> | |
| <i class="ph ph-hand-tap"></i> | |
| Stop | |
| </button> | |
| <button class="btn-secondary" onclick="game.reset()"> | |
| <i class="ph ph-arrow-counter-clockwise"></i> | |
| Reset Level | |
| </button> | |
| </div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="progressFill" style="width: 0%"></div> | |
| </div> | |
| <div class="level-indicator">Progress to Next Level</div> | |
| </div> | |
| <div class="history-section"> | |
| <h2 class="section-title"> | |
| <i class="ph ph-clock-counter-clockwise"></i> | |
| Retry History | |
| </h2> | |
| <div class="retry-history" id="retryHistory"> | |
| <span style="color: var(--text-muted); font-size: 0.875rem;">No attempts yet. Start playing to see your retry pattern.</span> | |
| </div> | |
| </div> | |
| </main> | |
| <script> | |
| class RetryGame { | |
| constructor() { | |
| this.level = 1; | |
| this.retries = 0; | |
| this.successes = 0; | |
| this.attempts = []; | |
| this.streak = 0; | |
| this.isPlaying = false; | |
| this.moverPosition = 0; | |
| this.moverDirection = 1; | |
| this.speed = 2; | |
| this.animationId = null; | |
| this.elements = { | |
| mover: document.getElementById('mover'), | |
| targetZone: document.getElementById('targetZone'), | |
| gameArea: document.getElementById('gameArea'), | |
| level: document.getElementById('level'), | |
| retries: document.getElementById('retries'), | |
| successRate: document.getElementById('successRate'), | |
| streak: document.getElementById('streak'), | |
| message: document.getElementById('message'), | |
| actionBtn: document.getElementById('actionBtn'), | |
| retryHistory: document.getElementById('retryHistory'), | |
| progressFill: document.getElementById('progressFill') | |
| }; | |
| this.init(); | |
| } | |
| init() { | |
| this.setupLevel(); | |
| this.startMoving(); | |
| // Touch support | |
| this.elements.gameArea.addEventListener('touchstart', (e) => { | |
| e.preventDefault(); | |
| this.attempt(); | |
| }); | |
| // Keyboard support | |
| document.addEventListener('keydown', (e) => { | |
| if (e.code === 'Space' || e.code === 'Enter') { | |
| e.preventDefault(); | |
| this.attempt(); | |
| } | |
| }); | |
| } | |
| setupLevel() { | |
| const areaWidth = this.elements.gameArea.offsetWidth; | |
| const targetWidth = Math.max(60, 200 - (this.level * 10)); | |
| const targetLeft = Math.random() * (areaWidth - targetWidth - 40) + 20; | |
| this.elements.targetZone.style.width = targetWidth + 'px'; | |
| this.elements.targetZone.style.left = targetLeft + 'px'; | |
| this.speed = Math.min(8, 2 + (this.level * 0.5)); | |
| this.moverPosition = 0; | |
| this.moverDirection = 1; | |
| } | |
| startMoving() { | |
| this.isPlaying = true; | |
| this.elements.mover.classList.remove('perfect', 'fail'); | |
| this.animate(); | |
| } | |
| animate() { | |
| if (!this.isPlaying) return; | |
| const areaWidth = this.elements.gameArea.offsetWidth; | |
| this.moverPosition += this.speed * this.moverDirection; | |
| if (this.moverPosition >= areaWidth || this.moverPosition <= 0) { | |
| this.moverDirection *= -1; | |
| this.moverPosition = Math.max(0, Math.min(areaWidth, this.moverPosition)); | |
| } | |
| this.elements.mover.style.left = this.moverPosition + 'px'; | |
| this.animationId = requestAnimationFrame(() => this.animate()); | |
| } | |
| attempt() { | |
| if (!this.isPlaying) { | |
| this.startMoving(); | |
| return; | |
| } | |
| this.isPlaying = false; | |
| cancelAnimationFrame(this.animationId); | |
| const targetRect = this.elements.targetZone.getBoundingClientRect(); | |
| const moverRect = this.elements.mover.getBoundingClientRect(); | |
| const gameRect = this.elements.gameArea.getBoundingClientRect(); | |
| const targetStart = targetRect.left - gameRect.left; | |
| const targetEnd = targetStart + targetRect.width; | |
| const moverCenter = moverRect.left - gameRect.left + (moverRect.width / 2); | |
| const isSuccess = moverCenter >= targetStart && moverCenter <= targetEnd; | |
| this.retries++; | |
| this.attempts.push(isSuccess); | |
| if (isSuccess) { | |
| this.handleSuccess(); | |
| } else { | |
| this.handleFail(); | |
| } | |
| this.updateStats(); | |
| this.addToHistory(isSuccess); | |
| } | |
| handleSuccess() { | |
| this.successes++; | |
| this.streak++; | |
| this.elements.mover.classList.add('perfect'); | |
| this.showMessage('Perfect! Level Complete', 'success'); | |
| this.createParticles(); | |
| const progress = (this.streak % 3) * 33.33; | |
| this.elements.progressFill.style.width = progress + '%'; | |
| if (this.streak % 3 === 0) { | |
| setTimeout(() => { | |
| this.level++; | |
| this.showMessage(`Level ${this.level} Unlocked!`, 'success'); | |
| this.elements.progressFill.style.width = '0%'; | |
| this.setupLevel(); | |
| this.startMoving(); | |
| }, 1000); | |
| } else { | |
| setTimeout(() => { | |
| this.setupLevel(); | |
| this.startMoving(); | |
| }, 1000); | |
| } | |
| } | |
| handleFail() { | |
| this.streak = 0; | |
| this.elements.mover.classList.add('fail'); | |
| this.showMessage('Miss! Retry?', 'fail'); | |
| this.elements.progressFill.style.width = '0%'; | |
| setTimeout(() => { | |
| this.startMoving(); | |
| }, 800); | |
| } | |
| showMessage(text, type) { | |
| const msg = this.elements.message; | |
| msg.textContent = text; | |
| msg.className = `message ${type} show`; | |
| setTimeout(() => { | |
| msg.classList.remove('show'); | |
| }, 2000); | |
| } | |
| createParticles() { | |
| const rect = this.elements.mover.getBoundingClientRect(); | |
| const colors = ['#6366f1', '#10b981', '#f59e0b']; | |
| for (let i = 0; i < 8; i++) { | |
| const particle = document.createElement('div'); | |
| particle.className = 'particle'; | |
| particle.style.left = rect.left + rect.width/2 + 'px'; | |
| particle.style.top = rect.top + rect.height/2 + 'px'; | |
| particle.style.width = '8px'; | |
| particle.style.height = '8px'; | |
| particle.style.background = colors[Math.floor(Math.random() * colors.length)]; | |
| particle.style.borderRadius = '50%'; | |
| particle.style.setProperty('--tx', (Math.random() - 0.5) * 100 + 'px'); | |
| particle.style.setProperty('--ty', (Math.random() - 0.5) * 100 + 'px'); | |
| document.body.appendChild(particle); | |
| setTimeout(() => particle.remove(), 1000); | |
| } | |
| } | |
| addToHistory(success) { | |
| if (this.attempts.length === 1) { | |
| this.elements.retryHistory.innerHTML = ''; | |
| } | |
| const dot = document.createElement('div'); | |
| dot.className = `retry-dot ${success ? 'success' : 'fail'}`; | |
| dot.innerHTML = success ? | |
| '<i class="ph ph-check"></i>' : | |
| '<i class="ph ph-x"></i>'; | |
| dot.title = `Attempt #${this.attempts.length}: ${success ? 'Success' : 'Fail'}`; | |
| this.elements.retryHistory.appendChild(dot); | |
| this.elements.retryHistory.scrollTop = this.elements.retryHistory.scrollHeight; | |
| } | |
| updateStats() { | |
| this.elements.level.textContent = this.level; | |
| this.elements.retries.textContent = this.retries; | |
| this.elements.streak.textContent = this.streak; | |
| const rate = this.attempts.length > 0 | |
| ? Math.round((this.successes / this.attempts.length) * 100) | |
| : 100; | |
| this.elements.successRate.textContent = rate + '%'; | |
| } | |
| reset() { | |
| this.isPlaying = false; | |
| cancelAnimationFrame(this.animationId); | |
| this.streak = 0; | |
| this.elements.progressFill.style.width = '0%'; | |
| this.setupLevel(); | |
| this.startMoving(); | |
| this.showMessage('Level Reset', 'fail'); | |
| } | |
| } | |
| // Initialize game when DOM is ready | |
| let game; | |
| document.addEventListener('DOMContentLoaded', () => { | |
| game = new RetryGame(); | |
| }); | |
| // Prevent zoom on double tap for mobile | |
| let lastTouchEnd = 0; | |
| document.addEventListener('touchend', (e) => { | |
| const now = Date.now(); | |
| if (now - lastTouchEnd <= 300) { | |
| e.preventDefault(); | |
| } | |
| lastTouchEnd = now; | |
| }, false); | |
| </script> | |
| </body> | |
| </html> |