程式碼 · World Quest: Retro 8-Bit Nationality Clash
CC BY 4.0 · 請標明 @joe · v1
AI 編輯提示起頭
Edit this classroom game for interactive whiteboards. Keep large touch targets and whole-class play. Tracking, logins, personal-data collection, and remote API calls are prohibited. Preserve its single-HTML structure. Preserve the educational goal. Original creator: @joe on ClassBoard Games (CC BY 4.0).
world-quest-retro-8-bit-nationality-clash.html · 56.9 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Where Are They From? - 8-Bit Educational Game</title>
<!-- Pixel Font -->
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
<style>
:root {
--bg-color: #202028;
--text-color: #ffffff;
--accent-green: #4caf50;
--accent-red: #f44336;
--accent-blue: #2196f3;
--accent-gold: #ffd700;
--pixel-font: 'Press Start 2P', cursive;
}
* {
box-sizing: border-box;
user-select: none;
}
body {
background-color: #000;
color: var(--text-color);
font-family: var(--pixel-font);
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
width: 100vw;
overflow: hidden;
}
#game-container {
width: 95vw;
height: 90vh;
background-color: var(--bg-color);
border: 4px solid #fff;
position: relative;
display: flex;
flex-direction: column;
box-shadow: 0 0 25px rgba(0,0,0,0.8);
overflow: hidden;
}
#scoreboard {
display: none;
flex-direction: row;
justify-content: space-between;
padding: 1vh 2vw;
background: #000;
border-bottom: 4px solid #fff;
z-index: 100;
height: 9vh;
align-items: center;
flex-shrink: 0;
}
.team-score {
font-size: 2vh;
color: #888;
padding: 8px 14px;
border: 2px solid transparent;
transition: all 0.3s;
position: relative;
}
.team-score.active-turn {
color: #fff;
border: 2px solid #ffff00;
background: #2a2a35;
transform: scale(1.08);
box-shadow: 0 0 12px #ffff00;
}
.turn-indicator {
font-size: 1.5vh;
color: #ffff00;
animation: blink 1s infinite;
}
@keyframes blink { 50% { opacity: 0.4; } }
.screen {
display: none;
flex-direction: column;
align-items: center;
justify-content: flex-start;
width: 100%;
height: 100%;
padding: 2vh 2vw;
box-sizing: border-box;
flex-grow: 1;
text-align: center;
overflow-y: auto;
position: relative;
}
.screen.active {
display: flex;
}
h1 {
font-size: 3.5vh;
margin: 1.5vh 0;
color: #ffff00;
text-shadow: 3px 3px #000;
line-height: 1.4;
}
h2 {
font-size: 2.8vh;
color: #00ffcc;
margin: 1vh 0 2vh 0;
flex-shrink: 0;
text-shadow: 3px 3px #000;
}
.huge-text {
font-size: 4vh;
color: #fff;
margin: 3vh 0;
text-shadow: 4px 4px #000;
line-height: 1.5;
max-width: 85%;
}
button {
background-color: #333;
color: #fff;
font-family: var(--pixel-font);
font-size: 2vh;
border: 4px solid #fff;
padding: 1.5vh 3vw;
margin: 0.8vh;
cursor: pointer;
box-shadow: 0.6vh 0.6vh 0px #000;
transition: transform 0.1s, background-color 0.1s;
}
button:active {
transform: translate(3px, 3px);
box-shadow: 2px 2px 0px #000;
}
button:hover {
background-color: #555;
}
button.correct {
background-color: var(--accent-green) !important;
border-color: #b9f6ca !important;
}
button.wrong {
background-color: var(--accent-red) !important;
border-color: #ff8a80 !important;
}
#start-btn {
padding: 2vh 10vw;
margin-top: 5vh;
font-size: 2.6vh;
}
#start-game-btn {
background-color: #2196f3;
margin-top: 3vh;
animation: pulse 1.8s infinite;
}
canvas.flag {
image-rendering: pixelated;
border: 4px solid #fff;
margin: 8px;
box-shadow: 4px 4px 0 #000;
}
.pulsing {
animation: pulse 0.6s ease-in-out;
}
@keyframes pulse {
0% { transform: scale(1); filter: brightness(1); }
50% { transform: scale(1.1); filter: brightness(1.25); }
100% { transform: scale(1); filter: brightness(1); }
}
/* Revision Screen Components */
.hub-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
gap: 12px;
width: 90%;
margin-bottom: 15px;
background: #3a3a4a;
padding: 16px;
border-radius: 8px;
border: 2px solid #555;
}
.hub-flag-container {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
transition: transform 0.2s;
}
.hub-flag-container:hover {
transform: scale(1.12);
z-index: 10;
}
#revision-display {
min-height: 16vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: #222;
width: 80%;
border: 4px double #555;
margin-bottom: 2vh;
padding: 2vh;
gap: 1.5vh;
}
.rev-row {
display: flex;
align-items: center;
gap: 15px;
width: 100%;
justify-content: center;
}
.rev-label {
color: #aaa;
font-size: 1.8vh;
text-align: right;
width: 140px;
}
.rev-val {
font-size: 2.6vh;
text-align: left;
flex-grow: 1;
}
#revision-country { color: #fff; }
#revision-nat { color: #ffff00; }
.person-icon {
width: 3.5vh;
height: 3.5vh;
image-rendering: pixelated;
}
/* Level 1: Spelling */
.spelling-word {
font-size: 4.5vh;
letter-spacing: 8px;
margin: 1.5vh 0;
color: #fff;
}
.keyboard {
display: flex;
flex-wrap: wrap;
justify-content: center;
max-width: 85%;
gap: 8px;
}
.key-btn {
padding: 1.2vh 1.2vw;
min-width: 44px;
font-size: 1.8vh;
}
/* Level 2: Choice */
.portrait {
width: 14vh;
height: 14vh;
image-rendering: pixelated;
background-color: #ddd;
border: 4px solid #fff;
margin: 8px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
box-shadow: 4px 4px 0 #000;
}
.portrait canvas { width: 100%; height: 100%; }
.question-box {
display: flex;
align-items: center;
justify-content: center;
gap: 30px;
margin-bottom: 2vh;
}
.choice-btn {
width: 70%;
margin: 1vh 0;
font-size: 2.4vh;
}
#l2-sentence { font-size: 2.6vh; margin-bottom: 2vh; }
/* Level 3: Chart */
.chart-row {
display: grid;
grid-template-columns: 0.8fr 1.2fr 2fr 1.5fr;
gap: 12px;
width: 100%;
margin-bottom: 6px;
font-size: 1.6vh;
align-items: center;
background: #2a2a35;
padding: 0.8vh 1.6vw;
border: 2px solid #555;
transition: all 0.2s;
position: relative;
opacity: 0.45;
}
.chart-row.active-turn-row {
opacity: 1;
border: 2px solid #ffff00;
background: #3f3f50;
transform: scale(1.02);
z-index: 5;
}
.chart-row.solved {
opacity: 1;
border: 2px solid #4caf50;
}
.mini-portrait {
width: 5vh;
height: 5vh;
border: 2px solid #fff;
background: #888;
image-rendering: pixelated;
}
.mini-portrait canvas { width: 100%; height: 100%; }
.chart-row.header {
background: #555;
color: #ffff00;
font-weight: bold;
opacity: 1;
position: sticky;
top: 0;
z-index: 10;
}
.chart-row div { text-align: left; padding: 0 4px; }
.word-bank {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 10px;
justify-content: center;
background: rgba(0,0,0,0.3);
padding: 10px;
border-radius: 8px;
}
.word-bank-btn {
font-size: 1.5vh;
padding: 8px 12px;
background: #444;
border-color: #888;
}
.sentence-builder {
min-height: 70px;
border-bottom: 4px dashed #fff;
width: 85%;
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: center;
margin: 2vh 0;
padding: 12px;
font-size: 2.6vh;
}
.token-bank {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
}
.token {
background: #444;
padding: 8px 16px;
border: 2px solid #888;
cursor: pointer;
font-size: 2.2vh;
border-radius: 4px;
}
.control-row {
display: flex;
gap: 10px;
margin: 1vh 0;
justify-content: center;
}
.control-btn {
font-size: 1.4vh;
padding: 8px 14px;
border: 2px solid #888;
background: #222;
}
#l4-hint-text {
color: #ffff00;
font-size: 1.6vh;
min-height: 2vh;
margin-top: 4px;
}
/* Bonus Round */
#screen-bonus {
background-color: #3e2723;
border: 4px solid var(--accent-gold);
}
.bonus-title {
color: var(--accent-gold);
font-size: 3.5vh;
animation: pulse 0.7s infinite;
margin-bottom: 1.5vh;
}
.bonus-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 25px;
margin-top: 2vh;
}
.bonus-option {
border: 4px solid #fff;
cursor: pointer;
transition: transform 0.2s;
padding: 8px;
background: #241411;
}
.bonus-option:hover {
transform: scale(1.08);
border-color: var(--accent-gold);
}
/* Visual Juice and Feedback */
.stamp-anim {
display: inline-block;
animation: stamp 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
color: #00ff00;
text-shadow: 0 0 6px #00ff00;
}
@keyframes stamp {
0% { transform: scale(3); opacity: 0; }
100% { transform: scale(1); opacity: 1; }
}
.shake {
animation: shake 0.4s;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
20%, 60% { transform: translateX(-6px); }
40%, 80% { transform: translateX(6px); }
}
.confetti {
position: absolute;
width: 8px;
height: 8px;
pointer-events: none;
z-index: 999;
}
.bonus-float {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
color: #ffff00;
font-size: 1.4vh;
white-space: nowrap;
animation: floatUp 1.4s forwards;
pointer-events: none;
text-shadow: 2px 2px #000;
z-index: 101;
}
.penalty-float {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
color: #ff3333;
font-size: 1.8vh;
font-weight: bold;
white-space: nowrap;
animation: floatDown 1s forwards;
pointer-events: none;
text-shadow: 2px 2px #000;
z-index: 102;
}
@keyframes floatUp {
0% { top: 100%; opacity: 1; transform: translateX(-50%) scale(1); }
100% { top: -40%; opacity: 0; transform: translateX(-50%) scale(1.4); }
}
@keyframes floatDown {
0% { top: 50%; opacity: 1; transform: translateX(-50%) scale(1); }
100% { top: 110%; opacity: 0; transform: translateX(-50%) scale(0.8); }
}
/* In-game Overlay Modal (No alerts) */
#msg-overlay {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
background: rgba(0,0,0,0.92);
border: 4px solid #f44336;
color: #f44336;
padding: 30px 40px;
font-size: 2.6vh;
z-index: 9999;
display: none;
text-align: center;
pointer-events: none;
box-shadow: 0 0 20px #000;
line-height: 1.5;
}
.msg-warning {
border-color: #ff9800 !important;
color: #ff9800 !important;
}
/* End Screen Parade Styling (Fixed vertical center) */
.parade-container {
width: 100%;
height: 130px;
overflow: hidden;
background: #252530;
margin: 3vh 0;
display: flex;
align-items: center;
position: relative;
border-top: 4px solid #fff;
border-bottom: 4px solid #fff;
}
.parade-track {
display: flex;
align-items: center;
gap: 40px;
position: absolute;
left: 0;
animation: scrollParade 18s linear infinite;
will-change: transform;
}
@keyframes scrollParade {
0% { transform: translateX(95vw); }
100% { transform: translateX(-100%); }
}
</style>
</head>
<body>
<div id="game-container">
<!-- IN-GAME MESSAGE OVERLAY (NO BROWSER ALERT) -->
<div id="msg-overlay">WRONG!<br>SWITCHING TEAM</div>
<!-- PERSISTENT SCOREBOARD -->
<div id="scoreboard">
<div id="score-left" class="team-score">TEAM LEFT: <span id="val-left">0</span></div>
<div class="turn-indicator" id="turn-msg">TEAM RIGHT START!</div>
<div id="score-right" class="team-score">TEAM RIGHT: <span id="val-right">0</span></div>
</div>
<!-- INTRO SCREEN -->
<div id="screen-intro" class="screen active">
<div class="huge-text">
Those two countries were Russia and Japan.
</div>
<div style="display: flex; gap: 50px; justify-content: center; margin: 4vh 0; flex-wrap: wrap;">
<div id="intro-russia"></div>
<div id="intro-japan"></div>
</div>
<button id="start-btn" onclick="goToHub()">CONTINUE</button>
</div>
<!-- REVISION SCREEN -->
<div id="screen-hub" class="screen">
<h2>NATIONALITIES REVISION</h2>
<div id="revision-display">
<div class="rev-row">
<span class="rev-label">COUNTRY:</span>
<span id="revision-country" class="rev-val">HOVER A FLAG</span>
</div>
<div class="rev-row">
<span class="rev-label">PERSON:</span>
<canvas id="static-person-icon" width="32" height="32" class="person-icon"></canvas>
<span id="revision-nat" class="rev-val">---</span>
</div>
</div>
<div id="flag-wall" class="hub-grid"></div>
<button id="start-game-btn" onclick="startTeamGame()">START THE NATIONALITIES GAME</button>
</div>
<!-- LEVEL 1: SPELLING -->
<div id="screen-level1" class="screen">
<h2>SPELL IT!</h2>
<div class="question-box">
<div id="l1-flag-container"></div>
<div id="l1-word-display" class="spelling-word"></div>
</div>
<div id="l1-keyboard" class="keyboard"></div>
</div>
<!-- LEVEL 2: CHOICE -->
<div id="screen-level2" class="screen">
<h2>COUNTRY OR NATIONALITY?</h2>
<div class="question-box">
<div id="l2-portrait" class="portrait"></div>
<div id="l2-flag-container"></div>
</div>
<div id="l2-sentence"></div>
<div id="l2-options" style="width: 100%; display: flex; flex-direction: column; align-items: center;"></div>
</div>
<!-- LEVEL 3: CHART -->
<div id="screen-level3" class="screen">
<h2>PASSPORT CHECK</h2>
<div id="l3-chart-container" style="width:95%; max-width:1100px;">
<div class="chart-row header">
<div>Photo</div>
<div>Name</div>
<div>Place of Birth</div>
<div>Nationality</div>
</div>
</div>
<div id="l3-word-bank" class="word-bank"></div>
</div>
<!-- LEVEL 4: SENTENCE -->
<div id="screen-level4" class="screen">
<h2>SAY THE NATIONALITY</h2>
<div class="question-box">
<div id="l4-portrait" class="portrait"></div>
<div id="l4-flag-container"></div>
</div>
<div id="l4-prompt" style="color: #ffeb3b; font-size: 2.2vh; margin-bottom: 1vh;"></div>
<div id="l4-builder" class="sentence-builder"></div>
<div class="control-row">
<button class="control-btn" onclick="l4Hint()">HINT?</button>
<button class="control-btn" onclick="l4Delete()">DEL</button>
<button class="control-btn" onclick="l4Clear()">CLR</button>
</div>
<div id="l4-hint-text"></div>
<div id="l4-tokens" class="token-bank"></div>
<button id="l4-check-btn" onclick="checkLevel4()" style="margin-top: 2vh;">CHECK</button>
</div>
<!-- BONUS ROUND SCREEN -->
<div id="screen-bonus" class="screen">
<h1 class="bonus-title">BONUS ROUND!</h1>
<div class="huge-text" id="bonus-country-name" style="color:#fff; margin:1vh 0 2vh 0;">---</div>
<div style="color: #ffff00; font-size: 1.6vh; margin-bottom: 2vh;">PICK THE CORRECT FLAG</div>
<div id="bonus-grid" class="bonus-grid"></div>
</div>
<!-- END SCREEN -->
<div id="screen-end" class="screen">
<h1>GAME OVER!</h1>
<div class="huge-text" id="winner-msg">TEAM X WINS!</div>
<div style="display:flex; gap:60px; justify-content:center; margin: 20px 0;">
<div>
<h3 style="color:#aaa; font-size: 2vh;">LEFT</h3>
<h1 id="final-left" style="color:#4caf50;">0</h1>
</div>
<div>
<h3 style="color:#aaa; font-size: 2vh;">RIGHT</h3>
<h1 id="final-right" style="color:#2196f3;">0</h1>
</div>
</div>
<div class="parade-container">
<div id="flag-parade" class="parade-track"></div>
</div>
<button onclick="location.reload()" style="margin-top: 1vh;">PLAY AGAIN</button>
</div>
</div>
<script>
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function playTone(freq, type, duration) {
if(audioCtx.state === 'suspended') {
audioCtx.resume();
}
try {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
gain.gain.setValueAtTime(0.08, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + duration);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + duration);
} catch(e) {
console.warn("Audio playback issue:", e);
}
}
const sfx = {
click: () => playTone(440, 'square', 0.08),
correct: () => {
playTone(587.33, 'sine', 0.1);
setTimeout(() => playTone(739.99, 'sine', 0.1), 90);
setTimeout(() => playTone(1174.66, 'square', 0.22), 180);
},
wrong: () => {
playTone(160, 'sawtooth', 0.25);
setTimeout(() => playTone(110, 'sawtooth', 0.3), 120);
},
turn: () => {
playTone(320, 'triangle', 0.08);
setTimeout(() => playTone(480, 'triangle', 0.15), 90);
},
win: () => {
[440, 554.37, 659.25, 880, 1108.73].forEach((f, i) => {
setTimeout(() => playTone(f, 'square', 0.2), i * 140);
});
},
bonus: () => {
playTone(1400, 'sine', 0.08);
setTimeout(() => playTone(1900, 'square', 0.18), 80);
},
bonusStart: () => {
for(let i = 0; i < 3; i++) {
setTimeout(() => playTone(784, 'square', 0.08), i * 180);
setTimeout(() => playTone(659, 'square', 0.08), i * 180 + 90);
}
}
};
const countries = [
{ name: "Russia", nat: "Russian", colors: ['white', '#0039a6', '#d52b1e'], type: 'h-stripe' },
{ name: "Japan", nat: "Japanese", colors: ['white', '#bc002d'], type: 'circle' },
{ name: "Australia", nat: "Australian", colors: ['#000080', '#cc0000', '#ffffff'], type: 'aus' },
{ name: "Mexico", nat: "Mexican", colors: ['#006847', '#ffffff', '#ce1126'], type: 'v-stripe-emblem' },
{ name: "France", nat: "French", colors: ['#0055a4', '#ffffff', '#ef4135'], type: 'v-stripe' },
{ name: "Canada", nat: "Canadian", colors: ['#ff0000', '#ffffff'], type: 'maple' },
{ name: "U.S.A.", nat: "American", colors: ['#3c3b6e', '#ffffff', '#b22234'], type: 'usa' },
{ name: "China", nat: "Chinese", colors: ['#de2910', '#ffde00'], type: 'china' },
{ name: "England", nat: "English", colors: ['#ffffff', '#ce1126'], type: 'cross' },
{ name: "Italy", nat: "Italian", colors: ['#009246', '#ffffff', '#ce1126'], type: 'v-stripe' }
];
const spellingWords = [
{ c: "Japan", w: "JAPANESE", mask: "J_P_N_S_" },
{ c: "Canada", w: "CANADIAN", mask: "C_N_D__N" },
{ c: "France", w: "FRENCH", mask: "FR_N_H" },
{ c: "Mexico", w: "MEXICAN", mask: "M_X_C_N" },
{ c: "U.S.A.", w: "AMERICAN", mask: "AM_R_C_N" },
{ c: "China", w: "CHINESE", mask: "CH_N_S_" },
{ c: "Italy", w: "ITALIAN", mask: "IT_L__N" },
{ c: "Australia", w: "AUSTRALIAN", mask: "A_STR_L__N" }
];
const choiceQuestions = [
{ name: "Greg", q: "Greg is (___).", opt1: "Australia", opt2: "Australian", ans: "Australian", flag: "Australia" },
{ name: "Nicole", q: "Nicole was born in (___).", opt1: "France", opt2: "French", ans: "France", flag: "France" },
{ name: "Me", q: "I was born in (___).", opt1: "England", opt2: "English", ans: "England", flag: "England" },
{ name: "Ivan", q: "Ivan is (___).", opt1: "Russia", opt2: "Russian", ans: "Russian", flag: "Russia" },
{ name: "Akiko", q: "Akiko is (___).", opt1: "Japan", opt2: "Japanese", ans: "Japanese", flag: "Japan" },
{ name: "Dad", q: "My father was born in (___).", opt1: "China", opt2: "Chinese", ans: "China", flag: "China" }
];
const chartData = [
{ name: "Greg", place: "Sydney, Australia", nat: "Australian", flag: "Australia" },
{ name: "Claudia", place: "Mexico City, Mexico", nat: "Mexican", flag: "Mexico" },
{ name: "Sophie", place: "Paris, France", nat: "French", flag: "France" },
{ name: "Steve", place: "Toronto, Canada", nat: "Canadian", flag: "Canada" },
{ name: "Jane", place: "San Fran, U.S.A.", nat: "American", flag: "U.S.A." },
{ name: "Akiko", place: "Tokyo, Japan", nat: "Japanese", flag: "Japan" }
];
const sentenceData = [
{ name: "Nicole", country: "France", prompt: "(Nicole / France)", required: ["She", "is", "French", "."] },
{ name: "Greg", country: "Australia", prompt: "(Greg / Australia)", required: ["He", "is", "Australian", "."] },
{ name: "Tom", country: "China", prompt: "(Tom / China)", required: ["He", "is", "Chinese", "."] },
{ name: "Jane", country: "U.S.A.", prompt: "(Jane / U.S.A.)", required: ["She", "is", "American", "."] }
];
const scores = { left: 0, right: 0 };
let currentTeam = 'right';
let taskQueue = [];
let currentTaskIndex = 0;
let turnStartTime = 0;
let bonusMistakes = 0;
let currentBuilder = [];
function createFlagCanvas(countryName, width = 60) {
const c = document.createElement('canvas');
c.width = width;
c.height = width * 0.66;
c.className = 'flag';
const ctx = c.getContext('2d');
const country = countries.find(x => x.name === countryName);
if(!country) return c;
const w = c.width;
const h = c.height;
ctx.fillStyle = country.colors[0];
if (["Japan", "England"].includes(country.name)) ctx.fillStyle = "white";
ctx.fillRect(0, 0, w, h);
switch (country.type) {
case 'h-stripe':
ctx.fillStyle = country.colors[0]; ctx.fillRect(0, 0, w, h/3);
ctx.fillStyle = country.colors[1]; ctx.fillRect(0, h/3, w, h/3);
ctx.fillStyle = country.colors[2]; ctx.fillRect(0, h*2/3, w, h/3);
break;
case 'v-stripe':
case 'v-stripe-emblem':
ctx.fillStyle = country.colors[0]; ctx.fillRect(0, 0, w/3, h);
ctx.fillStyle = country.colors[1]; ctx.fillRect(w/3, 0, w/3, h);
ctx.fillStyle = country.colors[2]; ctx.fillRect(w*2/3, 0, w/3, h);
if(country.name === 'Mexico') {
ctx.fillStyle = "#6d4c41";
ctx.fillRect(w/2 - 3, h/2 - 3, 6, 6);
}
break;
case 'circle':
ctx.fillStyle = country.colors[1];
ctx.beginPath();
ctx.arc(w/2, h/2, h/3, 0, Math.PI * 2);
ctx.fill();
break;
case 'usa':
ctx.fillStyle = "white"; ctx.fillRect(0, 0, w, h);
ctx.fillStyle = country.colors[2];
const sH = h / 13;
for(let i = 0; i < 13; i += 2) ctx.fillRect(0, i * sH, w, sH);
ctx.fillStyle = country.colors[0];
ctx.fillRect(0, 0, w * 0.4, sH * 7);
ctx.fillStyle = "white";
const sS = Math.max(1.5, w / 60);
for(let r = 1; r <= 7; r++) {
for(let col = 1; col <= 5; col++) {
ctx.fillRect(col * (w * 0.4 / 6) - sS/2, r * (sH * 7 / 8) - sS/2, sS, sS);
}
}
break;
case 'aus':
ctx.fillStyle = "#000080"; ctx.fillRect(0, 0, w, h);
const uW = w / 2;
const uH = h / 2;
// White Diagonals
ctx.beginPath(); ctx.strokeStyle = "white"; ctx.lineWidth = uH * 0.22;
ctx.moveTo(0, 0); ctx.lineTo(uW, uH); ctx.moveTo(uW, 0); ctx.lineTo(0, uH); ctx.stroke();
// Red Diagonals
ctx.beginPath(); ctx.strokeStyle = "#CC0000"; ctx.lineWidth = uH * 0.08;
ctx.moveTo(0, 0); ctx.lineTo(uW, uH); ctx.moveTo(uW, 0); ctx.lineTo(0, uH); ctx.stroke();
// White Cross
ctx.fillStyle = "white";
const wThick = uH * 0.35;
ctx.fillRect((uW - wThick)/2, 0, wThick, uH);
ctx.fillRect(0, (uH - wThick)/2, uW, wThick);
// Red Cross
ctx.fillStyle = "#CC0000";
const rThick = uH * 0.16;
ctx.fillRect((uW - rThick)/2, 0, rThick, uH);
ctx.fillRect(0, (uH - rThick)/2, uW, rThick);
// Southern Cross Stars
ctx.fillStyle = "white";
const star = (cx, cy, s) => {
ctx.fillRect(cx - s/2, cy - s/2, s, s);
ctx.fillRect(cx - s, cy - s/4, s*2, s/2);
ctx.fillRect(cx - s/4, cy - s, s/2, s*2);
};
star(w * 0.25, h * 0.75, w * 0.12);
star(w * 0.75, h * 0.2, w * 0.07);
star(w * 0.62, h * 0.48, w * 0.07);
star(w * 0.88, h * 0.45, w * 0.07);
star(w * 0.75, h * 0.80, w * 0.07);
ctx.fillRect(w * 0.81, h * 0.58, w * 0.035, w * 0.035);
break;
case 'china':
ctx.fillStyle = country.colors[0]; ctx.fillRect(0, 0, w, h);
ctx.fillStyle = country.colors[1];
ctx.fillRect(w * 0.08, h * 0.15, w * 0.14, w * 0.14);
ctx.fillRect(w * 0.25, h * 0.08, w * 0.04, w * 0.04);
ctx.fillRect(w * 0.30, h * 0.18, w * 0.04, w * 0.04);
ctx.fillRect(w * 0.30, h * 0.32, w * 0.04, w * 0.04);
ctx.fillRect(w * 0.25, h * 0.42, w * 0.04, w * 0.04);
break;
case 'maple':
ctx.fillStyle = "#FF0000"; ctx.fillRect(0, 0, w/4, h); ctx.fillRect(w - w/4, 0, w/4, h);
ctx.fillStyle = "white"; ctx.fillRect(w/4, 0, w/2, h);
ctx.fillStyle = "#FF0000";
ctx.beginPath();
const cx = w/2;
const cy = h/2;
const s = Math.min(w, h) * 0.36;
ctx.moveTo(cx, cy - s);
ctx.lineTo(cx + s*0.3, cy - s*0.35);
ctx.lineTo(cx + s*0.7, cy - s*0.6);
ctx.lineTo(cx + s*0.6, cy - s*0.1);
ctx.lineTo(cx + s*0.9, cy + s*0.1);
ctx.lineTo(cx + s*0.35, cy + s*0.4);
ctx.lineTo(cx + s*0.1, cy + s*0.9);
ctx.lineTo(cx - s*0.1, cy + s*0.9);
ctx.lineTo(cx - s*0.35, cy + s*0.4);
ctx.lineTo(cx - s*0.9, cy + s*0.1);
ctx.lineTo(cx - s*0.6, cy - s*0.1);
ctx.lineTo(cx - s*0.7, cy - s*0.6);
ctx.lineTo(cx - s*0.3, cy - s*0.35);
ctx.closePath();
ctx.fill();
break;
case 'cross':
ctx.fillStyle = "white"; ctx.fillRect(0, 0, w, h);
ctx.fillStyle = country.colors[1];
ctx.fillRect(w/2 - w*0.06, 0, w*0.12, h);
ctx.fillRect(0, h/2 - h*0.09, w, h*0.18);
break;
}
return c;
}
function createPortrait(seed) {
const c = document.createElement('canvas');
c.width = 24 * 8;
c.height = 24 * 8;
const ctx = c.getContext('2d');
let localSeed = seed;
const r = () => {
const x = Math.sin(localSeed++) * 10000;
return x - Math.floor(x);
};
const skins = ["#ffdbac", "#f1c27d", "#e0ac69", "#c68642", "#8d5524"];
const hairs = ["#090806", "#4b301b", "#e6be8a", "#8d2b2b", "#5a5a5a", "#d4a017"];
const sC = skins[Math.floor(r() * skins.length)] || skins[0];
const hC = hairs[Math.floor(r() * hairs.length)] || hairs[0];
const rect = (x, y, w, h, cl) => {
ctx.fillStyle = cl;
ctx.fillRect(x * 8, y * 8, w * 8, h * 8);
};
// Base Face and contours
rect(7, 6, 10, 13, sC);
rect(6, 8, 1, 9, sC);
rect(17, 8, 1, 9, sC);
rect(8, 19, 8, 2, sC);
// Eyes
rect(7, 11, 4, 3, "white");
rect(8, 11, 2, 3, "#222");
rect(13, 11, 4, 3, "white");
rect(14, 11, 2, 3, "#222");
// Eyebrows
rect(7, 10, 4, 1, hC);
rect(13, 10, 4, 1, hC);
// Hair style
rect(6, 3, 12, 3, hC);
rect(7, 2, 10, 1, hC);
if(r() > 0.45) {
rect(5, 4, 2, 8, hC);
rect(17, 4, 2, 8, hC);
} else {
rect(5, 3, 14, 5, hC);
}
// Mouth
rect(9, 18, 6, 1, "#b05555");
return c;
}
window.onload = function() {
document.getElementById('intro-russia').appendChild(createFlagCanvas("Russia", 220));
document.getElementById('intro-japan').appendChild(createFlagCanvas("Japan", 220));
const pIcon = document.getElementById('static-person-icon');
const pCtx = pIcon.getContext('2d');
pCtx.fillStyle = "#fff";
pCtx.fillRect(10, 4, 12, 12);
pCtx.fillRect(4, 18, 24, 14);
const wall = document.getElementById('flag-wall');
const hubCountries = ["Australia", "Mexico", "France", "Canada", "U.S.A.", "Japan", "China", "England", "Italy", "Russia"];
hubCountries.forEach(cName => {
const country = countries.find(c => c.name === cName);
const div = document.createElement('div');
div.className = 'hub-flag-container';
div.appendChild(createFlagCanvas(cName, 76));
div.onmouseover = () => {
sfx.click();
document.getElementById('revision-country').innerText = country.name;
document.getElementById('revision-nat').innerText = country.nat;
};
wall.appendChild(div);
});
};
function goToHub() {
sfx.click();
showScreen('screen-hub');
}
function showScreen(id) {
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
const target = document.getElementById(id);
if(target) target.classList.add('active');
const sb = document.getElementById('scoreboard');
if(id === 'screen-intro' || id === 'screen-hub' || id === 'screen-end') {
sb.style.display = 'none';
} else {
sb.style.display = 'flex';
}
}
function buildTaskQueue() {
const standards = [];
spellingWords.forEach(t => standards.push({ type: 'spelling', data: t }));
choiceQuestions.forEach(t => standards.push({ type: 'choice', data: t }));
chartData.forEach((t, i) => standards.push({ type: 'chart', idx: i }));
sentenceData.forEach(t => standards.push({ type: 'sentence', data: t }));
// 24 standard tasks + 4 staggered bonuses = 28 turns total
// Team Right plays EVEN indices (0, 2, 4...)
// Team Left plays ODD indices (1, 3, 5...)
const oddOptions1 = [3, 5, 7]; // Team Left bonus #1
const evenOptions1 = [10, 12, 14]; // Team Right bonus #1
const oddOptions2 = [17, 19, 21]; // Team Left bonus #2
const evenOptions2 = [24, 26]; // Team Right bonus #2
const b1 = oddOptions1[Math.floor(Math.random() * oddOptions1.length)];
const b2 = evenOptions1[Math.floor(Math.random() * evenOptions1.length)];
const b3 = oddOptions2[Math.floor(Math.random() * oddOptions2.length)];
const b4 = evenOptions2[Math.floor(Math.random() * evenOptions2.length)];
const bonusIndices = [b1, b2, b3, b4].sort((a,b) => a - b);
taskQueue = new Array(28);
bonusIndices.forEach(idx => {
taskQueue[idx] = { type: 'bonus', data: null };
});
let stdIdx = 0;
for(let i = 0; i < 28; i++) {
if(!taskQueue[i]) {
taskQueue[i] = standards[stdIdx++] || standards[0];
}
}
}
function startTeamGame() {
sfx.click();
scores.left = 0;
scores.right = 0;
currentTeam = 'right';
currentTaskIndex = 0;
buildTaskQueue();
updateScoreboard();
renderChartBase();
runNextTask();
}
function updateScoreboard() {
const leftEl = document.getElementById('score-left');
const rightEl = document.getElementById('score-right');
const msgEl = document.getElementById('turn-msg');
document.getElementById('val-left').innerText = scores.left;
document.getElementById('val-right').innerText = scores.right;
leftEl.classList.remove('active-turn');
rightEl.classList.remove('active-turn');
if(currentTeam === 'left') {
leftEl.classList.add('active-turn');
msgEl.innerText = "TEAM LEFT'S TURN";
msgEl.style.color = "#4caf50";
} else {
rightEl.classList.add('active-turn');
msgEl.innerText = "TEAM RIGHT'S TURN";
msgEl.style.color = "#2196f3";
}
}
function subtractScore(points) {
if(currentTeam === 'left') scores.left -= points;
else scores.right -= points;
const targetId = (currentTeam === 'left') ? 'score-left' : 'score-right';
const target = document.getElementById(targetId);
const float = document.createElement('div');
float.className = 'penalty-float';
float.innerText = "-" + points;
target.appendChild(float);
setTimeout(() => { if(float.parentNode) float.parentNode.removeChild(float); }, 1000);
updateScoreboard();
}
function completeTurn(points) {
sfx.turn();
const elapsed = (Date.now() - turnStartTime) / 1000;
const taskType = taskQueue[currentTaskIndex].type;
let timeLimit = 6;
if(taskType === 'spelling' || taskType === 'sentence') timeLimit = 12;
let bonus = 0;
if(elapsed <= timeLimit && points > 0 && taskType !== 'bonus') {
bonus = 5;
sfx.bonus();
showBonusAnim();
}
const total = points + bonus;
if(currentTeam === 'left') scores.left += total;
else scores.right += total;
currentTeam = (currentTeam === 'left') ? 'right' : 'left';
currentTaskIndex++;
updateScoreboard();
runNextTask();
}
function showBonusAnim() {
const targetId = (currentTeam === 'left') ? 'score-left' : 'score-right';
const target = document.getElementById(targetId);
const float = document.createElement('div');
float.className = 'bonus-float';
float.innerText = "+5 SPEED BONUS!";
target.appendChild(float);
setTimeout(() => { if(float.parentNode) float.parentNode.removeChild(float); }, 1800);
}
function runNextTask() {
if(currentTaskIndex >= taskQueue.length) {
endGame();
return;
}
const task = taskQueue[currentTaskIndex];
if(task.type === 'spelling') setupSpelling(task.data);
else if(task.type === 'choice') setupChoice(task.data);
else if(task.type === 'chart') setupChart(task.idx);
else if(task.type === 'sentence') setupSentence(task.data);
else if(task.type === 'bonus') setupBonus();
turnStartTime = Date.now();
}
function setupSpelling(data) {
showScreen('screen-level1');
const container = document.getElementById('l1-flag-container');
container.innerHTML = '';
const flag = createFlagCanvas(data.c, 180);
container.appendChild(flag);
let currentMask = data.mask.split('');
const wordDisplay = document.getElementById('l1-word-display');
const renderWord = () => wordDisplay.innerHTML = currentMask.join(' ');
renderWord();
const kb = document.getElementById('l1-keyboard');
kb.innerHTML = '';
const alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
alpha.split('').forEach(letter => {
const btn = document.createElement('button');
btn.className = 'key-btn';
btn.innerText = letter;
btn.onclick = () => {
let hit = false;
for(let i = 0; i < data.w.length; i++) {
if(data.w[i] === letter && currentMask[i] === '_') {
currentMask[i] = letter;
hit = true;
}
}
if(hit) {
sfx.click();
btn.style.opacity = '0.5';
btn.disabled = true;
} else {
sfx.wrong();
subtractScore(5);
}
renderWord();
if(hit && !currentMask.includes('_')) {
sfx.correct();
flag.classList.add('pulsing');
spawnConfetti(window.innerWidth/2, window.innerHeight/2);
setTimeout(() => completeTurn(10), 1400);
}
};
kb.appendChild(btn);
});
}
function setupChoice(data) {
showScreen('screen-level2');
let seedOffset = 0;
if(["Nicole", "Akiko", "Jane"].includes(data.name)) seedOffset = 3;
const pContainer = document.getElementById('l2-portrait');
pContainer.innerHTML = '';
pContainer.appendChild(createPortrait(currentTaskIndex + seedOffset + 40));
const fContainer = document.getElementById('l2-flag-container');
fContainer.innerHTML = '';
fContainer.appendChild(createFlagCanvas(data.flag, 120));
document.getElementById('l2-sentence').innerText = data.q.replace("(___)", "_______");
const ops = document.getElementById('l2-options');
ops.innerHTML = '';
[data.opt1, data.opt2].forEach(opt => {
const btn = document.createElement('button');
btn.className = 'choice-btn';
btn.innerText = opt;
btn.onclick = () => {
if(opt === data.ans) {
sfx.correct();
btn.classList.add('correct');
const flagCanvas = document.querySelector('#l2-flag-container canvas');
if(flagCanvas) flagCanvas.classList.add('pulsing');
document.getElementById('l2-sentence').innerText = data.q.replace("(___)", data.ans);
spawnConfetti(window.innerWidth/2, window.innerHeight/2);
setTimeout(() => completeTurn(10), 1400);
} else {
sfx.wrong();
btn.classList.add('wrong');
subtractScore(10);
}
};
ops.appendChild(btn);
});
}
function renderChartBase() {
const container = document.getElementById('l3-chart-container');
while(container.children.length > 1) {
container.removeChild(container.lastChild);
}
chartData.forEach((d, idx) => {
const row = document.createElement('div');
row.className = 'chart-row';
row.id = `chart-row-${idx}`;
const picCell = document.createElement('div');
const miniPic = document.createElement('div');
miniPic.className = 'mini-portrait';
const genderSeeds = [12, 33, 51, 14, 55, 30];
miniPic.appendChild(createPortrait(genderSeeds[idx] || (idx + 70)));
picCell.appendChild(miniPic);
const nameCell = document.createElement('div');
nameCell.innerText = d.name;
nameCell.style.color = "#ddd";
const placeCell = document.createElement('div');
placeCell.style.display = "flex";
placeCell.style.alignItems = "center";
placeCell.style.gap = "8px";
placeCell.appendChild(createFlagCanvas(d.flag, 34));
placeCell.appendChild(document.createTextNode(d.place));
const natCell = document.createElement('div');
natCell.id = `nat-cell-${idx}`;
natCell.innerText = "????";
natCell.style.color = "#777";
natCell.style.fontStyle = "italic";
row.appendChild(picCell);
row.appendChild(nameCell);
row.appendChild(placeCell);
row.appendChild(natCell);
container.appendChild(row);
});
}
function setupChart(targetRowIdx) {
showScreen('screen-level3');
document.querySelectorAll('.chart-row').forEach(r => {
if(!r.classList.contains('header')) r.classList.remove('active-turn-row');
});
const activeRow = document.getElementById(`chart-row-${targetRowIdx}`);
if(activeRow) activeRow.classList.add('active-turn-row');
const bank = document.getElementById('l3-word-bank');
bank.innerHTML = '';
const nats = chartData.map(d => d.nat).sort(() => Math.random() - 0.5);
nats.forEach(n => {
const btn = document.createElement('button');
btn.className = 'word-bank-btn';
btn.innerText = n;
btn.onclick = () => {
const targetData = chartData[targetRowIdx];
if(targetData.nat === n) {
sfx.correct();
const cell = document.getElementById(`nat-cell-${targetRowIdx}`);
cell.innerHTML = "";
const stampSpan = document.createElement('span');
stampSpan.innerText = n;
stampSpan.className = 'stamp-anim';
cell.appendChild(stampSpan);
activeRow.classList.remove('active-turn-row');
activeRow.classList.add('solved');
btn.style.visibility = 'hidden';
spawnConfetti(window.innerWidth/2, window.innerHeight/2);
setTimeout(() => completeTurn(10), 1400);
} else {
sfx.wrong();
btn.classList.add('wrong');
activeRow.classList.add('shake');
setTimeout(() => {
btn.classList.remove('wrong');
activeRow.classList.remove('shake');
}, 500);
}
};
bank.appendChild(btn);
});
}
function l4Delete() {
if(currentBuilder.length > 0) {
sfx.click();
currentBuilder.pop();
updateBuilderUI();
}
}
function l4Clear() {
sfx.click();
currentBuilder = [];
updateBuilderUI();
}
function l4Hint() {
sfx.click();
const data = taskQueue[currentTaskIndex].data;
const hintEl = document.getElementById('l4-hint-text');
hintEl.innerText = "HINT: Starts with \"" + data.required[0] + "\"...";
setTimeout(() => hintEl.innerText = "", 3500);
}
function setupSentence(data) {
showScreen('screen-level4');
currentBuilder = [];
updateBuilderUI();
const pContainer = document.getElementById('l4-portrait');
pContainer.innerHTML = '';
let genderSeed = 30;
if(data.name === "Greg" || data.name === "Tom") genderSeed = 15;
pContainer.appendChild(createPortrait(currentTaskIndex + genderSeed));
const fContainer = document.getElementById('l4-flag-container');
fContainer.innerHTML = '';
fContainer.appendChild(createFlagCanvas(data.country, 120));
document.getElementById('l4-prompt').innerText = data.prompt;
document.getElementById('l4-hint-text').innerText = "";
const bank = document.getElementById('l4-tokens');
bank.innerHTML = '';
const tokens = ["He", "She", "is", "am", "are", "French", "Australian", "Chinese", "American", "Canadian", "."];
tokens.forEach(t => {
const tok = document.createElement('div');
tok.className = 'token';
tok.innerText = t;
tok.onclick = () => {
sfx.click();
currentBuilder.push(t);
updateBuilderUI();
};
bank.appendChild(tok);
});
}
function updateBuilderUI() {
const area = document.getElementById('l4-builder');
area.innerHTML = '';
currentBuilder.forEach((word, idx) => {
const span = document.createElement('span');
span.innerText = word;
span.style.padding = "4px 8px";
span.style.background = "#222";
span.style.borderRadius = "4px";
span.style.cursor = "pointer";
span.title = "Click to remove";
span.onclick = () => {
sfx.click();
currentBuilder.splice(idx, 1);
updateBuilderUI();
};
area.appendChild(span);
});
}
function checkLevel4() {
const data = taskQueue[currentTaskIndex].data;
const attempt = currentBuilder.join(' ');
const target = data.required.join(' ');
const hasFullStop = attempt.endsWith('.');
const attemptWithDot = hasFullStop ? attempt : (attempt + " .").trim();
if(attemptWithDot === target) {
if(hasFullStop) {
sfx.correct();
const flagCanvas = document.querySelector('#l4-flag-container canvas');
if(flagCanvas) flagCanvas.classList.add('pulsing');
spawnConfetti(window.innerWidth/2, window.innerHeight/2);
setTimeout(() => completeTurn(10), 1400);
} else {
sfx.correct();
const msg = document.getElementById('msg-overlay');
msg.innerHTML = "DON'T FORGET<br>FULL STOP NEXT TIME!<br>-5 PTS";
msg.classList.add('msg-warning');
msg.style.display = 'block';
subtractScore(5);
setTimeout(() => {
msg.style.display = 'none';
msg.classList.remove('msg-warning');
msg.innerHTML = "WRONG!<br>SWITCHING TEAM";
completeTurn(10);
}, 1800);
}
} else {
sfx.wrong();
const msg = document.getElementById('msg-overlay');
msg.innerHTML = "WRONG!<br>SWITCHING TEAM";
msg.style.display = 'block';
setTimeout(() => { msg.style.display = 'none'; }, 1400);
currentTeam = (currentTeam === 'left') ? 'right' : 'left';
updateScoreboard();
currentBuilder = [];
updateBuilderUI();
turnStartTime = Date.now();
}
}
function setupBonus() {
showScreen('screen-bonus');
sfx.bonusStart();
bonusMistakes = 0;
const correctCountry = countries[Math.floor(Math.random() * countries.length)];
const options = [correctCountry];
while(options.length < 4) {
const candidate = countries[Math.floor(Math.random() * countries.length)];
if(!options.some(o => o.name === candidate.name)) options.push(candidate);
}
options.sort(() => Math.random() - 0.5);
document.getElementById('bonus-country-name').innerText = correctCountry.name;
const grid = document.getElementById('bonus-grid');
grid.innerHTML = '';
options.forEach(opt => {
const div = document.createElement('div');
div.className = 'bonus-option';
div.appendChild(createFlagCanvas(opt.name, 140));
div.onclick = () => {
if(div.style.visibility === 'hidden') return;
if(opt.name === correctCountry.name) {
sfx.bonus();
spawnConfetti(window.innerWidth/2, window.innerHeight/2);
setTimeout(() => completeTurn(65), 1400);
} else {
sfx.wrong();
bonusMistakes++;
div.style.visibility = 'hidden';
if(bonusMistakes === 1) {
subtractScore(5);
} else if(bonusMistakes === 2) {
subtractScore(10);
} else if(bonusMistakes >= 3) {
subtractScore(30);
const msg = document.getElementById('msg-overlay');
msg.innerHTML = "BONUS FAILED!<br>-30 PTS";
msg.style.display = 'block';
setTimeout(() => {
msg.style.display = 'none';
msg.innerHTML = "WRONG!<br>SWITCHING TEAM";
completeTurn(0);
}, 1800);
}
}
};
grid.appendChild(div);
});
}
function endGame() {
sfx.win();
showScreen('screen-end');
document.getElementById('final-left').innerText = scores.left;
document.getElementById('final-right').innerText = scores.right;
const winMsg = document.getElementById('winner-msg');
if(scores.left > scores.right) {
winMsg.innerText = "TEAM LEFT WINS!";
winMsg.style.color = "#4caf50";
} else if(scores.right > scores.left) {
winMsg.innerText = "TEAM RIGHT WINS!";
winMsg.style.color = "#2196f3";
} else {
winMsg.innerText = "IT'S A DRAW!";
winMsg.style.color = "#fff";
}
const parade = document.getElementById('flag-parade');
parade.innerHTML = '';
// Add double loop of flags for uninterrupted parade scroll
const paradeList = [...countries, ...countries];
paradeList.forEach(c => {
const item = document.createElement('div');
item.appendChild(createFlagCanvas(c.name, 80));
parade.appendChild(item);
});
}
function spawnConfetti(x, y) {
const colors = ['#f44336', '#4caf50', '#2196f3', '#ffeb3b', '#ff9800', '#e91e63'];
for(let i = 0; i < 35; i++) {
const conf = document.createElement('div');
conf.className = 'confetti';
conf.style.left = x + 'px';
conf.style.top = y + 'px';
conf.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
document.body.appendChild(conf);
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * 7 + 2;
let velX = Math.cos(angle) * speed;
let velY = Math.sin(angle) * speed;
let frame = 0;
const anim = setInterval(() => {
frame++;
velY += 0.45; // gravity
conf.style.left = (parseFloat(conf.style.left) + velX) + 'px';
conf.style.top = (parseFloat(conf.style.top) + velY) + 'px';
conf.style.opacity = (1 - (frame / 38)).toString();
if(frame > 38) {
clearInterval(anim);
if(conf.parentNode) conf.parentNode.removeChild(conf);
}
}, 25);
}
}
</script>
</body>
</html>