Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Ticket Triage Turbo</title> | |
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap" rel="stylesheet"> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
:root { | |
--primary: #3a86ff; | |
--danger: #ef476f; | |
--success: #06d6a0; | |
--warning: #ffd166; | |
--dark: #1c2541; | |
--light: #f8f9fa; | |
} | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
font-family: 'Poppins', sans-serif; | |
} | |
body { | |
background-color: #f0f2f5; | |
min-height: 100vh; | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
padding: 20px; | |
} | |
header { | |
text-align: center; | |
margin-bottom: 30px; | |
} | |
h1 { | |
color: var(--dark); | |
margin-bottom: 10px; | |
font-size: 2.5rem; | |
} | |
.score-display { | |
display: flex; | |
justify-content: center; | |
gap: 30px; | |
margin-bottom: 20px; | |
} | |
.score-box { | |
background: white; | |
padding: 15px 25px; | |
border-radius: 10px; | |
box-shadow: 0 4px 6px rgba(0,0,0,0.1); | |
display: flex; | |
align-items: center; | |
gap: 10px; | |
font-weight: 600; | |
} | |
.score-box .value { | |
font-size: 1.5rem; | |
} | |
#score { | |
color: var(--primary); | |
} | |
#streak { | |
color: var(--success); | |
} | |
.game-container { | |
width: 100%; | |
max-width: 800px; | |
background: white; | |
border-radius: 15px; | |
box-shadow: 0 10px 20px rgba(0,0,0,0.1); | |
padding: 30px; | |
margin-bottom: 30px; | |
position: relative; | |
overflow: hidden; | |
} | |
.ticket-display { | |
background: var(--light); | |
border-radius: 10px; | |
padding: 20px; | |
min-height: 200px; | |
margin-bottom: 30px; | |
border: 2px dashed #dee2e6; | |
position: relative; | |
} | |
.ticket-content { | |
margin-bottom: 15px; | |
} | |
.ticket-meta { | |
display: flex; | |
justify-content: space-between; | |
color: #6c757d; | |
font-size: 0.9rem; | |
} | |
.tag { | |
display: inline-block; | |
padding: 3px 8px; | |
border-radius: 5px; | |
font-size: 0.8rem; | |
font-weight: 600; | |
margin-right: 8px; | |
} | |
.critical-tag { | |
background-color: var(--danger); | |
color: white; | |
} | |
.technical-tag { | |
background-color: var(--primary); | |
color: white; | |
} | |
.billing-tag { | |
background-color: var(--success); | |
color: white; | |
} | |
.feature-tag { | |
background-color: var(--warning); | |
color: var(--dark); | |
} | |
.actions { | |
display: flex; | |
justify-content: center; | |
gap: 20px; | |
margin-bottom: 30px; | |
} | |
.action-btn { | |
padding: 15px 25px; | |
border-radius: 8px; | |
border: none; | |
font-size: 1rem; | |
font-weight: 600; | |
cursor: pointer; | |
transition: all 0.2s; | |
display: flex; | |
align-items: center; | |
gap: 10px; | |
box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
} | |
.action-btn:active { | |
transform: translateY(3px); | |
} | |
.human-btn { | |
background-color: var(--primary); | |
color: white; | |
} | |
.ai-btn { | |
background-color: var(--success); | |
color: white; | |
} | |
.explanation { | |
text-align: center; | |
color: #6c757d; | |
margin-bottom: 20px; | |
min-height: 30px; | |
font-style: italic; | |
} | |
.emoji-feedback { | |
font-size: 3rem; | |
text-align: center; | |
height: 60px; | |
margin-bottom: 20px; | |
animation: fadeIn 0.5s; | |
} | |
@keyframes fadeIn { | |
from { opacity: 0; } | |
to { opacity: 1; } | |
} | |
.next-ticket { | |
text-align: center; | |
} | |
.next-btn { | |
padding: 10px 20px; | |
background-color: var(--dark); | |
color: white; | |
border: none; | |
border-radius: 5px; | |
cursor: pointer; | |
transition: all 0.2s; | |
} | |
.next-btn:hover { | |
background-color: #2c3b6b; | |
} | |
.progress-container { | |
width: 100%; | |
height: 10px; | |
background-color: #e9ecef; | |
border-radius: 5px; | |
margin-bottom: 30px; | |
} | |
.progress-bar { | |
height: 100%; | |
border-radius: 5px; | |
background-color: var(--primary); | |
width: 0; | |
transition: width 0.3s ease; | |
} | |
.timer { | |
font-size: 1.2rem; | |
color: var(--dark); | |
margin-bottom: 20px; | |
text-align: center; | |
} | |
.ticket-countdown { | |
position: absolute; | |
top: 10px; | |
right: 10px; | |
width: 30px; | |
height: 30px; | |
border-radius: 50%; | |
background-color: var(--primary); | |
color: white; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
font-weight: bold; | |
} | |
.ticket-badge { | |
position: absolute; | |
top: 10px; | |
left: 10px; | |
background-color: var(--primary); | |
color: white; | |
padding: 3px 8px; | |
border-radius: 5px; | |
font-size: 0.8rem; | |
} | |
.game-over { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: rgba(255, 255, 255, 0.9); | |
display: flex; | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
z-index: 10; | |
opacity: 0; | |
pointer-events: none; | |
transition: opacity 0.3s; | |
} | |
.game-over.show { | |
opacity: 1; | |
pointer-events: all; | |
} | |
.game-over h2 { | |
font-size: 2rem; | |
margin-bottom: 20px; | |
color: var(--dark); | |
} | |
.final-score { | |
font-size: 1.5rem; | |
margin-bottom: 30px; | |
color: var(--primary); | |
} | |
.restart-btn { | |
padding: 15px 30px; | |
background-color: var(--primary); | |
color: white; | |
border: none; | |
border-radius: 8px; | |
font-size: 1rem; | |
font-weight: 600; | |
cursor: pointer; | |
transition: all 0.2s; | |
} | |
.restart-btn:hover { | |
background-color: #2a74e5; | |
} | |
.difficulty-selector { | |
margin-bottom: 20px; | |
} | |
.difficulty-btn { | |
padding: 8px 15px; | |
margin: 0 5px; | |
background-color: #e9ecef; | |
border: none; | |
border-radius: 5px; | |
cursor: pointer; | |
transition: all 0.2s; | |
} | |
.difficulty-btn.active { | |
background-color: var(--primary); | |
color: white; | |
} | |
.combo-meter { | |
width: 100%; | |
height: 6px; | |
background-color: #e9ecef; | |
border-radius: 3px; | |
margin-bottom: 10px; | |
overflow: hidden; | |
} | |
.combo-fill { | |
height: 100%; | |
background: linear-gradient(to right, var(--warning), var(--danger)); | |
width: 0; | |
transition: width 0.3s; | |
} | |
.combo-display { | |
text-align: center; | |
color: #6c757d; | |
font-size: 0.9rem; | |
margin-bottom: 20px; | |
} | |
.highlight { | |
animation: highlight 1s; | |
} | |
@keyframes highlight { | |
0% { background-color: initial; } | |
50% { background-color: rgba(255, 215, 0, 0.3); } | |
100% { background-color: initial; } | |
} | |
.floating-points { | |
position: absolute; | |
font-weight: bold; | |
color: var(--success); | |
animation: floatUp 1s forwards; | |
opacity: 0; | |
pointer-events: none; | |
} | |
@keyframes floatUp { | |
0% { transform: translateY(0); opacity: 1; } | |
100% { transform: translateY(-100px); opacity: 0; } | |
} | |
</style> | |
</head> | |
<body> | |
<header> | |
<h1>Ticket Triage Turbo</h1> | |
<p>Route support tickets to humans or AI as fast as possible!</p> | |
</header> | |
<div class="score-display"> | |
<div class="score-box"> | |
<i class="fas fa-star"></i> | |
<span>Score: </span> | |
<span class="value" id="score">0</span> | |
</div> | |
<div class="score-box"> | |
<i class="fas fa-bolt"></i> | |
<span>Streak: </span> | |
<span class="value" id="streak">0x</span> | |
</div> | |
</div> | |
<div class="difficulty-selector"> | |
<button class="difficulty-btn active" data-difficulty="easy">Easy</button> | |
<button class="difficulty-btn" data-difficulty="medium">Medium</button> | |
<button class="difficulty-btn" data-difficulty="hard">Hard</button> | |
</div> | |
<div class="combo-meter"> | |
<div class="combo-fill" id="combo-fill"></div> | |
</div> | |
<div class="combo-display" id="combo-display">No combo</div> | |
<div class="timer">Time: <span id="time">30</span>s</div> | |
<div class="game-container" id="game-container"> | |
<div class="game-over" id="game-over"> | |
<h2>Game Over!</h2> | |
<div class="final-score" id="final-score">Score: 0</div> | |
<button class="restart-btn" id="restart-btn">Play Again</button> | |
</div> | |
<div class="progress-container"> | |
<div class="progress-bar" id="progress-bar"></div> | |
</div> | |
<div class="ticket-display" id="ticket-display"> | |
<div class="ticket-badge" id="ticket-badge">Ticket #1</div> | |
<div class="ticket-countdown" id="ticket-countdown">5</div> | |
<div class="ticket-content" id="ticket-content"> | |
<p>Hi, I can't seem to log in to my account. I've tried resetting my password but the email never arrives.</p> | |
</div> | |
<div class="ticket-meta"> | |
<div class="tags" id="tags"> | |
<span class="tag critical-tag">Critical</span> | |
<span class="tag technical-tag">Technical</span> | |
</div> | |
<div class="customer" id="customer"> | |
<i class="fas fa-user"></i> John D. | |
</div> | |
</div> | |
</div> | |
<div class="emoji-feedback" id="emoji-feedback"></div> | |
<div class="explanation" id="explanation">Decide whether to route this ticket to a human or AI support agent</div> | |
<div class="actions"> | |
<button class="action-btn human-btn" id="human-btn"> | |
<i class="fas fa-user-headset"></i> Human Agent | |
</button> | |
<button class="action-btn ai-btn" id="ai-btn"> | |
<i class="fas fa-robot"></i> AI Assistant | |
</button> | |
</div> | |
<div class="next-ticket"> | |
<button class="next-btn" id="next-btn" disabled> | |
Next Ticket <i class="fas fa-arrow-right"></i> | |
</button> | |
</div> | |
</div> | |
<audio id="correct-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-correct-answer-tone-2870.mp3" preload="auto"></audio> | |
<audio id="wrong-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-wrong-answer-fail-notification-946.mp3" preload="auto"></audio> | |
<audio id="combo-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-achievement-bell-600.mp3" preload="auto"></audio> | |
<script> | |
// Game state | |
const state = { | |
score: 0, | |
streak: 0, | |
maxStreak: 0, | |
timeLeft: 30, | |
gameRunning: true, | |
combo: 0, | |
difficulty: 'easy', | |
ticketsProcessed: 0 | |
}; | |
// Ticket categories and their correct routing | |
const ticketTypes = { | |
technical: { | |
name: "Technical", | |
aiSuccessRate: 0.3, // 30% chance AI can handle it | |
color: "#3a86ff" | |
}, | |
billing: { | |
name: "Billing", | |
aiSuccessRate: 0.6, | |
color: "#06d6a0" | |
}, | |
feature: { | |
name: "Feature Request", | |
aiSuccessRate: 0.8, | |
color: "#ffd166" | |
}, | |
critical: { | |
name: "Critical", | |
aiSuccessRate: 0.1, | |
color: "#ef476f" | |
} | |
}; | |
// Current ticket | |
let currentTicket = null; | |
let ticketTimer = null; | |
let gameTimer = null; | |
let countdownInterval = null; | |
// Elements | |
const elements = { | |
gameContainer: document.getElementById('game-container'), | |
ticketDisplay: document.getElementById('ticket-display'), | |
ticketContent: document.getElementById('ticket-content'), | |
ticketBadge: document.getElementById('ticket-badge'), | |
ticketCountdown: document.getElementById('ticket-countdown'), | |
scoreDisplay: document.getElementById('score'), | |
streakDisplay: document.getElementById('streak'), | |
humanBtn: document.getElementById('human-btn'), | |
aiBtn: document.getElementById('ai-btn'), | |
nextBtn: document.getElementById('next-btn'), | |
explanation: document.getElementById('explanation'), | |
emojiFeedback: document.getElementById('emoji-feedback'), | |
tagsContainer: document.getElementById('tags'), | |
customerDisplay: document.getElementById('customer'), | |
gameOverScreen: document.getElementById('game-over'), | |
finalScore: document.getElementById('final-score'), | |
restartBtn: document.getElementById('restart-btn'), | |
progressBar: document.getElementById('progress-bar'), | |
timeDisplay: document.getElementById('time'), | |
comboFill: document.getElementById('combo-fill'), | |
comboDisplay: document.getElementById('combo-display') | |
}; | |
// Sounds | |
const sounds = { | |
correct: document.getElementById('correct-sound'), | |
wrong: document.getElementById('wrong-sound'), | |
combo: document.getElementById('combo-sound') | |
}; | |
// Utilities | |
function randomItem(arr) { | |
return arr[Math.floor(Math.random() * arr.length)]; | |
} | |
function randomBetween(min, max) { | |
return Math.floor(Math.random() * (max - min + 1) + min); | |
} | |
function shuffleArray(array) { | |
const newArray = [...array]; | |
for (let i = newArray.length - 1; i > 0; i--) { | |
const j = Math.floor(Math.random() * (i + 1)); | |
[newArray[i], newArray[j]] = [newArray[j], newArray[i]]; | |
} | |
return newArray; | |
} | |
// Customers | |
const customers = [ | |
"John D.", "Sarah K.", "Mike T.", "Lisa P.", "David M.", | |
"Emma R.", "James L.", "Olivia B.", "Robert G.", "Sophia N." | |
]; | |
// Ticket templates | |
const ticketTemplates = { | |
technical: [ | |
"I can't log in to my account. The password reset email isn't arriving.", | |
"The app crashes every time I try to open the settings page.", | |
"I'm getting a 404 error when trying to access my dashboard.", | |
"The download process gets stuck at 99% and never completes.", | |
"After the latest update, all my settings were reset to default." | |
], | |
billing: [ | |
"I was charged twice this month but only received one invoice.", | |
"Can you explain the extra $5 charge on my last bill?", | |
"I want to upgrade my plan but keep my current payment method.", | |
"The system says my credit card expired but it's still valid.", | |
"I canceled my subscription last week but was still charged." | |
], | |
feature: [ | |
"I'd love a dark mode option for the mobile app.", | |
"Could you add keyboard shortcuts for common actions?", | |
"Would it be possible to export reports in CSV format?", | |
"I wish there was a way to customize the dashboard layout.", | |
"Can you implement a toggle to hide all images for text-only browsing?" | |
], | |
critical: [ | |
"Our entire company account was suddenly deactivated without warning!", | |
"All our customer data has disappeared from the system!", | |
"We're getting reports from multiple users about unauthorized access.", | |
"The payment system is charging all customers double!", | |
"A critical security vulnerability was just reported in your API." | |
] | |
}; | |
// Customer complaints | |
const complaints = { | |
aiWrong: [ | |
"The AI gave me completely irrelevant answers!", | |
"The bot just sent me in circles without solving anything.", | |
"Automated responses didn't address my issue at all.", | |
"I wasted 30 minutes with the AI and still need help!", | |
"The AI kept misunderstanding my very clear problem." | |
], | |
humanWrong: [ | |
"I waited 2 hours for a human when the AI could have answered!", | |
"Why does every simple question need a human response?", | |
"A human agent just gave me the same answer as the AI would have.", | |
"This was a basic question that didn't need human involvement.", | |
"I needed instant help but was stuck in queue unnecessarily." | |
], | |
aiSuccess: [ | |
"The AI solved my problem instantly - great experience!", | |
"No waiting, fast answer - exactly what I needed.", | |
"Simple problem, simple solution - perfect for AI.", | |
"The bot understood my question immediately and helped.", | |
"Quick response that actually fixed my issue - very efficient." | |
], | |
humanSuccess: [ | |
"The human agent was patient and solved my complex problem.", | |
"I really appreciated the personal touch for this sensitive issue.", | |
"After the AI didn't help, the human fixed everything quickly.", | |
"The agent took time to understand all the nuances of my case.", | |
"For this level of service, waiting was definitely worth it." | |
] | |
}; | |
// Timer logic | |
function startGameTimer() { | |
elements.timeDisplay.textContent = state.timeLeft; | |
gameTimer = setInterval(() => { | |
state.timeLeft--; | |
elements.timeDisplay.textContent = state.timeLeft; | |
elements.progressBar.style.width = `${(state.timeLeft / 30) * 100}%`; | |
if (state.timeLeft <= 0) { | |
endGame(); | |
} | |
}, 1000); | |
} | |
// Ticket countdown | |
function startTicketTimer() { | |
let secondsLeft = getTicketTimeLimit(); | |
elements.ticketCountdown.textContent = secondsLeft; | |
if (countdownInterval) clearInterval(countdownInterval); | |
countdownInterval = setInterval(() => { | |
secondsLeft--; | |
elements.ticketCountdown.textContent = secondsLeft; | |
if (secondsLeft <= 0) { | |
clearInterval(countdownInterval); | |
handleTimeout(); | |
} | |
}, 1000); | |
} | |
function getTicketTimeLimit() { | |
switch (state.difficulty) { | |
case 'easy': return 7; | |
case 'medium': return 5; | |
case 'hard': return 3; | |
default: return 5; | |
} | |
} | |
function handleTimeout() { | |
state.streak = 0; | |
updateStreakDisplay(); | |
resetCombo(); | |
elements.emojiFeedback.textContent = "😴"; | |
elements.explanation.textContent = "Too slow! Customer was frustrated by the long wait."; | |
sounds.wrong.play(); | |
elements.humanBtn.disabled = true; | |
elements.aiBtn.disabled = true; | |
elements.nextBtn.disabled = false; | |
} | |
// Ticket generation | |
function generateRandomTicket() { | |
state.ticketsProcessed++; | |
clearInterval(countdownInterval); | |
// Determine which ticket types to include based on difficulty | |
let availableTypes = Object.keys(ticketTypes); | |
if (state.difficulty === 'easy') { | |
// Remove critical tickets in easy mode | |
availableTypes = availableTypes.filter(type => type !== 'critical'); | |
} | |
const typeKey = randomItem(availableTypes); | |
const type = ticketTypes[typeKey]; | |
const templates = ticketTemplates[typeKey]; | |
// Select random content | |
const content = randomItem(templates); | |
const customer = randomItem(customers); | |
// Create list of tags (always includes the main type) | |
const tags = [typeKey]; | |
// Add a second tag 50% of the time in medium/hard modes | |
if (Math.random() > 0.5 && state.difficulty !== 'easy') { | |
const otherTypes = availableTypes.filter(t => t !== typeKey); | |
if (otherTypes.length > 0) { | |
tags.push(randomItem(otherTypes)); | |
} | |
} | |
// Create ticket object | |
currentTicket = { | |
id: state.ticketsProcessed, | |
type: typeKey, | |
content: content, | |
customer: customer, | |
tags: shuffleArray(tags), | |
correctChoice: Math.random() < type.aiSuccessRate ? 'ai' : 'human' | |
}; | |
// Update UI | |
renderTicket(currentTicket); | |
startTicketTimer(); | |
// Reset buttons | |
elements.humanBtn.disabled = false; | |
elements.aiBtn.disabled = false; | |
elements.nextBtn.disabled = true; | |
// Clear feedback | |
elements.emojiFeedback.textContent = ""; | |
elements.explanation.textContent = "Decide whether to route this ticket to a human or AI support agent"; | |
} | |
function renderTicket(ticket) { | |
elements.ticketBadge.textContent = `Ticket #${ticket.id}`; | |
elements.ticketContent.innerHTML = `<p>${ticket.content}</p>`; | |
elements.customerDisplay.innerHTML = `<i class="fas fa-user"></i> ${ticket.customer}`; | |
// Clear existing tags | |
elements.tagsContainer.innerHTML = ''; | |
// Add new tags | |
ticket.tags.forEach(tag => { | |
const span = document.createElement('span'); | |
span.className = `tag ${tag}-tag`; | |
span.textContent = ticketTypes[tag].name; | |
span.style.backgroundColor = ticketTypes[tag].color; | |
elements.tagsContainer.appendChild(span); | |
}); | |
} | |
// Game actions | |
function routeToHuman() { | |
clearInterval(countdownInterval); | |
evaluateChoice('human'); | |
} | |
function routeToAI() { | |
clearInterval(countdownInterval); | |
evaluateChoice('ai'); | |
} | |
function evaluateChoice(choice) { | |
elements.humanBtn.disabled = true; | |
elements.aiBtn.disabled = true; | |
elements.nextBtn.disabled = false; | |
const wasCorrect = choice === currentTicket.correctChoice; | |
if (wasCorrect) { | |
handleCorrectChoice(); | |
} else { | |
handleWrongChoice(choice); | |
} | |
// Show feedback | |
const feedback = generateFeedback(choice, wasCorrect); | |
elements.emojiFeedback.textContent = feedback.emoji; | |
elements.explanation.textContent = feedback.text; | |
// Update combo | |
if (wasCorrect) { | |
incrementCombo(); | |
} else { | |
resetCombo(); | |
} | |
// Update streak | |
if (wasCorrect) { | |
state.streak++; | |
if (state.streak > state.maxStreak) { | |
state.maxStreak = state.streak; | |
} | |
} else { | |
state.streak = 0; | |
} | |
updateStreakDisplay(); | |
// Add floating points for correct answer | |
if (wasCorrect) { | |
addFloatingPoints(); | |
} | |
} | |
function handleCorrectChoice() { | |
let pointsEarned = 100; | |
// Add streak bonus | |
if (state.streak > 0) { | |
pointsEarned += state.streak * 10; | |
} | |
// Add combo bonus | |
if (state.combo > 2) { | |
pointsEarned += Math.pow(state.combo, 2) * 5; | |
} | |
// Difficulty bonus | |
if (state.difficulty === 'hard') pointsEarned *= 1.5; | |
else if (state.difficulty === 'medium') pointsEarned *= 1.2; | |
pointsEarned = Math.round(pointsEarned); | |
state.score += pointsEarned; | |
updateScoreDisplay(); | |
sounds.correct.play(); | |
} | |
function handleWrongChoice(choice) { | |
if (choice === 'ai' && currentTicket.correctChoice === 'human') { | |
sounds.wrong.play(); | |
} else if (choice === 'human' && currentTicket.correctChoice === 'ai') { | |
sounds.wrong.play(); | |
} | |
} | |
function generateFeedback(choice, wasCorrect) { | |
const ticketType = currentTicket.type; | |
if (wasCorrect) { | |
if (choice === 'ai') { | |
const msg = randomItem(complaints.aiSuccess); | |
return { emoji: "🤖✅", text: `Correct! AI handled this ${ticketType} issue well: "${msg}"` }; | |
} else { | |
const msg = randomItem(complaints.humanSuccess); | |
return { emoji: "👩💼✅", text: `Correct! Human was right for this ${ticketType} issue: "${msg}"` }; | |
} | |
} else { | |
if (choice === 'ai') { | |
const msg = randomItem(complaints.aiWrong); | |
return { emoji: "🤖❌", text: `Wrong! AI failed this ${ticketType} issue: "${msg}"` }; | |
} else { | |
const msg = randomItem(complaints.humanWrong); | |
return { emoji: "👩💼❌", text: `Wrong! Human was wrong for this ${ticketType} issue: "${msg}"` }; | |
} | |
} | |
} | |
// Combo system | |
function incrementCombo() { | |
state.combo++; | |
updateComboDisplay(); | |
// Play combo sound every 3 correct answers | |
if (state.combo >= 3 && state.combo % 3 === 0) { | |
sounds.combo.play(); | |
} | |
} | |
function resetCombo() { | |
state.combo = 0; | |
updateComboDisplay(); | |
} | |
function updateComboDisplay() { | |
const percent = Math.min(100, (state.combo / 10) * 100); | |
elements.comboFill.style.width = `${percent}%`; | |
if (state.combo === 0) { | |
elements.comboDisplay.textContent = "No combo"; | |
elements.comboDisplay.style.color = "#6c757d"; | |
} else if (state.combo < 3) { | |
elements.comboDisplay.textContent = `${state.combo}x combo`; | |
elements.comboDisplay.style.color = "#6c757d"; | |
} else if (state.combo < 5) { | |
elements.comboDisplay.textContent = `${state.combo}x combo!`; | |
elements.comboDisplay.style.color = "#ffd166"; | |
} else { | |
elements.comboDisplay.textContent = `${state.combo}x COMBO!! 🔥`; | |
elements.comboDisplay.style.color = "#ef476f"; | |
} | |
} | |
// Score display | |
function updateScoreDisplay() { | |
elements.scoreDisplay.textContent = state.score; | |
elements.scoreDisplay.classList.add('highlight'); | |
setTimeout(() => { | |
elements.scoreDisplay.classList.remove('highlight'); | |
}, 1000); | |
} | |
function updateStreakDisplay() { | |
elements.streakDisplay.textContent = `${state.streak > 0 ? state.streak : 0}x`; | |
// Add visual effect for streaks | |
if (state.streak >= 5) { | |
elements.streakDisplay.style.color = "#ffd166"; | |
} else if (state.streak >= 3) { | |
elements.streakDisplay.style.color = "#06d6a0"; | |
} else { | |
elements.streakDisplay.style.color = "#3a86ff"; | |
} | |
} | |
function addFloatingPoints() { | |
let points = 100; | |
if (state.streak > 0) points += state.streak * 10; | |
if (state.combo > 2) points += Math.pow(state.combo, 2) * 5; | |
if (state.difficulty === 'hard') points *= 1.5; | |
else if (state.difficulty === 'medium') points *= 1.2; | |
points = Math.round(points); | |
const floating = document.createElement('div'); | |
floating.className = 'floating-points'; | |
floating.textContent = `+${points}`; | |
// Position near the score display | |
floating.style.left = `${elements.scoreDisplay.getBoundingClientRect().left - 30}px`; | |
floating.style.top = `${elements.scoreDisplay.getBoundingClientRect().top}px`; | |
document.body.appendChild(floating); | |
// Remove after animation | |
setTimeout(() => { | |
floating.remove(); | |
}, 1000); | |
} | |
// Game flow | |
function startGame() { | |
state.score = 0; | |
state.streak = 0; | |
state.maxStreak = 0; | |
state.timeLeft = 30; | |
state.gameRunning = true; | |
state.combo = 0; | |
state.ticketsProcessed = 0; | |
updateScoreDisplay(); | |
updateStreakDisplay(); | |
updateComboDisplay(); | |
elements.gameOverScreen.classList.remove('show'); | |
elements.progressBar.style.width = "100%"; | |
startGameTimer(); | |
generateRandomTicket(); | |
} | |
function endGame() { | |
clearInterval(gameTimer); | |
clearInterval(countdownInterval); | |
state.gameRunning = false; | |
elements.finalScore.textContent = `Final Score: ${state.score} (Max Streak: ${state.maxStreak}x)`; | |
elements.gameOverScreen.classList.add('show'); | |
} | |
// Event listeners | |
elements.humanBtn.addEventListener('click', routeToHuman); | |
elements.aiBtn.addEventListener('click', routeToAI); | |
elements.nextBtn.addEventListener('click', generateRandomTicket); | |
elements.restartBtn.addEventListener('click', startGame); | |
document.querySelectorAll('.difficulty-btn').forEach(btn => { | |
btn.addEventListener('click', function() { | |
document.querySelectorAll('.difficulty-btn').forEach(b => b.classList.remove('active')); | |
this.classList.add('active'); | |
state.difficulty = this.dataset.difficulty; | |
}); | |
}); | |
// Start the game | |
startGame(); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body> | |
</html> |