Color_Memory_Game / index.html
eaglelandsonce's picture
Update index.html
1b5ae87 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Pattern Sprint Interview Game</title>
<style>
:root {
--navy: #071833;
--blue: #2563eb;
--green: #16a34a;
--red: #dc2626;
--yellow: #facc15;
--purple: #7c3aed;
--orange: #f97316;
--gray: #f3f4f6;
--dark-gray: #374151;
--white: #ffffff;
--border: #d1d5db;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
font-family: Arial, Helvetica, sans-serif;
background: linear-gradient(135deg, #eef2ff, #f8fafc);
color: var(--navy);
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
}
.app {
width: min(1100px, 100%);
background: var(--white);
border: 2px solid #e5e7eb;
border-radius: 22px;
box-shadow: 0 20px 50px rgba(7, 24, 51, 0.15);
overflow: hidden;
}
header {
background: var(--navy);
color: var(--white);
padding: 24px 28px;
}
header h1 {
margin: 0;
font-size: clamp(28px, 4vw, 44px);
letter-spacing: -1px;
}
header p {
margin: 8px 0 0;
font-size: 17px;
opacity: 0.9;
}
.main {
display: grid;
grid-template-columns: 1.4fr 0.9fr;
gap: 20px;
padding: 24px;
}
.panel {
border: 2px solid #e5e7eb;
border-radius: 18px;
background: #ffffff;
padding: 22px;
}
.game-area {
min-height: 520px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.status-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin-bottom: 18px;
}
.stat {
background: var(--gray);
border-radius: 14px;
padding: 14px;
text-align: center;
border: 1px solid #e5e7eb;
}
.stat .label {
font-size: 13px;
color: var(--dark-gray);
margin-bottom: 4px;
}
.stat .value {
font-size: 24px;
font-weight: 800;
}
.screen {
flex: 1;
border: 3px dashed #c7d2fe;
border-radius: 18px;
background: #f8fafc;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 28px;
margin-bottom: 18px;
min-height: 260px;
}
.screen h2 {
font-size: clamp(28px, 4vw, 44px);
margin: 0 0 12px;
}
.screen p {
font-size: 18px;
line-height: 1.45;
margin: 8px 0;
}
.sequence-display {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 14px;
margin: 20px 0;
}
.tile {
width: 78px;
height: 78px;
border-radius: 18px;
display: grid;
place-items: center;
font-weight: 900;
font-size: 22px;
color: white;
box-shadow: 0 12px 20px rgba(0, 0, 0, 0.18);
border: 3px solid rgba(255, 255, 255, 0.7);
user-select: none;
}
.tile.small {
width: 56px;
height: 56px;
border-radius: 14px;
font-size: 17px;
cursor: pointer;
transition: transform 0.15s, box-shadow 0.15s;
}
.tile.small:hover {
transform: translateY(-3px);
box-shadow: 0 16px 24px rgba(0, 0, 0, 0.2);
}
.Blue { background: var(--blue); }
.Green { background: var(--green); }
.Red { background: var(--red); }
.Yellow { background: #ca8a04; }
.Purple { background: var(--purple); }
.Orange { background: var(--orange); }
.choice-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
width: 100%;
max-width: 720px;
margin: 0 auto;
}
.choice {
background: white;
border: 2px solid var(--border);
border-radius: 16px;
padding: 14px;
cursor: pointer;
transition: all 0.15s ease;
text-align: left;
font-weight: 700;
}
.choice:hover {
border-color: var(--blue);
transform: translateY(-2px);
box-shadow: 0 10px 18px rgba(37, 99, 235, 0.12);
}
.choice.correct {
border-color: var(--green);
background: #ecfdf5;
}
.choice.wrong {
border-color: var(--red);
background: #fef2f2;
}
.button-row {
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: center;
justify-content: center;
}
button {
border: none;
border-radius: 14px;
padding: 14px 20px;
font-size: 16px;
font-weight: 800;
cursor: pointer;
transition: transform 0.15s, opacity 0.15s;
}
button:hover {
transform: translateY(-2px);
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.primary {
background: var(--blue);
color: white;
}
.secondary {
background: #e5e7eb;
color: var(--navy);
}
.danger {
background: var(--red);
color: white;
}
.side h2 {
margin: 0 0 12px;
font-size: 26px;
}
.side-section {
margin-bottom: 22px;
padding-bottom: 18px;
border-bottom: 1px solid #e5e7eb;
}
.side-section:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.meter-wrap {
margin: 12px 0;
}
.meter-label {
display: flex;
justify-content: space-between;
font-weight: 700;
margin-bottom: 6px;
font-size: 14px;
}
.meter {
height: 12px;
background: #e5e7eb;
border-radius: 999px;
overflow: hidden;
}
.meter-fill {
height: 100%;
width: 0%;
background: var(--blue);
transition: width 0.4s ease;
}
.history {
max-height: 210px;
overflow-y: auto;
padding-right: 6px;
}
.history-item {
background: #f9fafb;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 10px;
margin-bottom: 8px;
font-size: 14px;
}
.feedback {
font-size: 20px;
font-weight: 900;
min-height: 30px;
text-align: center;
margin: 8px 0 14px;
}
.good { color: var(--green); }
.bad { color: var(--red); }
.neutral { color: var(--dark-gray); }
.report {
text-align: left;
max-width: 760px;
margin: 0 auto;
}
.report h2 {
text-align: center;
}
.report-card {
background: white;
border: 2px solid #e5e7eb;
border-radius: 16px;
padding: 16px;
margin: 12px 0;
}
.report-card strong {
color: var(--navy);
}
.hidden {
display: none !important;
}
@media (max-width: 850px) {
.main {
grid-template-columns: 1fr;
}
.status-row {
grid-template-columns: 1fr 1fr;
}
.choice-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="app">
<header>
<h1>Pattern Sprint</h1>
<p>A gamified interview assessment for memory, attention, speed, and learning improvement.</p>
</header>
<main class="main">
<section class="panel game-area">
<div>
<div class="status-row">
<div class="stat">
<div class="label">Round</div>
<div class="value" id="roundValue">0/8</div>
</div>
<div class="stat">
<div class="label">Score</div>
<div class="value" id="scoreValue">0</div>
</div>
<div class="stat">
<div class="label">Accuracy</div>
<div class="value" id="accuracyValue">0%</div>
</div>
<div class="stat">
<div class="label">Timer</div>
<div class="value" id="timerValue">--</div>
</div>
</div>
<div class="screen" id="screen">
<div>
<h2>Ready?</h2>
<p>You will see a short color pattern. Memorize it before it disappears.</p>
<p>Then choose the matching sequence as quickly and accurately as possible.</p>
</div>
</div>
<div class="feedback neutral" id="feedback">Press Start to begin.</div>
</div>
<div class="button-row">
<button class="primary" id="startBtn">Start Game</button>
<button class="secondary" id="nextBtn" disabled>Next Round</button>
<button class="danger" id="resetBtn">Reset</button>
</div>
</section>
<aside class="panel side">
<div class="side-section">
<h2>Assessment Signals</h2>
<div class="meter-wrap">
<div class="meter-label"><span>Working Memory</span><span id="memoryScore">0%</span></div>
<div class="meter"><div class="meter-fill" id="memoryFill"></div></div>
</div>
<div class="meter-wrap">
<div class="meter-label"><span>Attention</span><span id="attentionScore">0%</span></div>
<div class="meter"><div class="meter-fill" id="attentionFill"></div></div>
</div>
<div class="meter-wrap">
<div class="meter-label"><span>Speed</span><span id="speedScore">0%</span></div>
<div class="meter"><div class="meter-fill" id="speedFill"></div></div>
</div>
<div class="meter-wrap">
<div class="meter-label"><span>Learning Trend</span><span id="learningScore">0%</span></div>
<div class="meter"><div class="meter-fill" id="learningFill"></div></div>
</div>
</div>
<div class="side-section">
<h2>Game Rules</h2>
<p><strong>1.</strong> Watch the sequence.</p>
<p><strong>2.</strong> Remember the order.</p>
<p><strong>3.</strong> Pick the correct answer.</p>
<p><strong>4.</strong> Later rounds get harder.</p>
</div>
<div class="side-section">
<h2>Round History</h2>
<div class="history" id="history"></div>
</div>
</aside>
</main>
</div>
<script>
const COLORS = ["Blue", "Green", "Red", "Yellow", "Purple", "Orange"];
const TOTAL_ROUNDS = 8;
const state = {
round: 0,
score: 0,
correct: 0,
attempts: 0,
currentSequence: [],
roundStartTime: null,
timerInterval: null,
responseTimes: [],
history: [],
acceptingAnswer: false
};
const els = {
screen: document.getElementById("screen"),
feedback: document.getElementById("feedback"),
roundValue: document.getElementById("roundValue"),
scoreValue: document.getElementById("scoreValue"),
accuracyValue: document.getElementById("accuracyValue"),
timerValue: document.getElementById("timerValue"),
startBtn: document.getElementById("startBtn"),
nextBtn: document.getElementById("nextBtn"),
resetBtn: document.getElementById("resetBtn"),
history: document.getElementById("history"),
memoryScore: document.getElementById("memoryScore"),
attentionScore: document.getElementById("attentionScore"),
speedScore: document.getElementById("speedScore"),
learningScore: document.getElementById("learningScore"),
memoryFill: document.getElementById("memoryFill"),
attentionFill: document.getElementById("attentionFill"),
speedFill: document.getElementById("speedFill"),
learningFill: document.getElementById("learningFill")
};
function randomItem(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
function createSequence(length) {
const seq = [];
for (let i = 0; i < length; i++) {
seq.push(randomItem(COLORS));
}
return seq;
}
function sequenceToText(seq) {
return seq.join(" → ");
}
function makeTile(color, small = false) {
const div = document.createElement("div");
div.className = `tile ${small ? "small" : ""} ${color}`;
div.textContent = color[0];
div.title = color;
return div;
}
function renderSequence(seq, small = false) {
const wrapper = document.createElement("div");
wrapper.className = "sequence-display";
seq.forEach(color => wrapper.appendChild(makeTile(color, small)));
return wrapper;
}
function shuffle(array) {
const copy = [...array];
for (let i = copy.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[copy[i], copy[j]] = [copy[j], copy[i]];
}
return copy;
}
function mutateSequence(seq) {
const mutated = [...seq];
const mutationType = Math.floor(Math.random() * 3);
if (mutationType === 0 && mutated.length > 1) {
const i = Math.floor(Math.random() * mutated.length);
let j = Math.floor(Math.random() * mutated.length);
while (j === i) j = Math.floor(Math.random() * mutated.length);
[mutated[i], mutated[j]] = [mutated[j], mutated[i]];
} else if (mutationType === 1) {
const i = Math.floor(Math.random() * mutated.length);
let newColor = randomItem(COLORS);
while (newColor === mutated[i]) newColor = randomItem(COLORS);
mutated[i] = newColor;
} else {
mutated.reverse();
}
return mutated;
}
function createChoices(correctSeq) {
const choices = [correctSeq];
const seen = new Set([sequenceToText(correctSeq)]);
while (choices.length < 4) {
const mutated = mutateSequence(correctSeq);
const key = sequenceToText(mutated);
if (!seen.has(key)) {
choices.push(mutated);
seen.add(key);
}
}
return shuffle(choices);
}
function setFeedback(text, type = "neutral") {
els.feedback.textContent = text;
els.feedback.className = `feedback ${type}`;
}
function updateStats() {
const accuracy = state.attempts ? Math.round((state.correct / state.attempts) * 100) : 0;
els.roundValue.textContent = `${state.round}/${TOTAL_ROUNDS}`;
els.scoreValue.textContent = state.score;
els.accuracyValue.textContent = `${accuracy}%`;
const memory = accuracy;
const attention = calculateAttentionScore();
const speed = calculateSpeedScore();
const learning = calculateLearningTrend();
updateMeter("memory", memory);
updateMeter("attention", attention);
updateMeter("speed", speed);
updateMeter("learning", learning);
}
function updateMeter(name, value) {
const clamped = Math.max(0, Math.min(100, Math.round(value)));
els[`${name}Score`].textContent = `${clamped}%`;
els[`${name}Fill`].style.width = `${clamped}%`;
}
function calculateAttentionScore() {
if (state.history.length === 0) return 0;
const harderRounds = state.history.filter(item => item.length >= 5);
if (harderRounds.length === 0) return Math.round((state.correct / Math.max(1, state.attempts)) * 100);
const correctHard = harderRounds.filter(item => item.correct).length;
return (correctHard / harderRounds.length) * 100;
}
function calculateSpeedScore() {
if (state.responseTimes.length === 0) return 0;
const avg = state.responseTimes.reduce((a, b) => a + b, 0) / state.responseTimes.length;
return Math.max(0, Math.min(100, 100 - (avg - 1.5) * 18));
}
function calculateLearningTrend() {
if (state.history.length < 4) return 0;
const midpoint = Math.floor(state.history.length / 2);
const firstHalf = state.history.slice(0, midpoint);
const secondHalf = state.history.slice(midpoint);
const firstAccuracy = firstHalf.filter(x => x.correct).length / firstHalf.length;
const secondAccuracy = secondHalf.filter(x => x.correct).length / secondHalf.length;
const improvement = secondAccuracy - firstAccuracy;
return Math.max(0, Math.min(100, 50 + improvement * 100));
}
function updateHistory() {
els.history.innerHTML = "";
state.history.slice().reverse().forEach(item => {
const div = document.createElement("div");
div.className = "history-item";
div.innerHTML = `
<strong>Round ${item.round}</strong> - ${item.correct ? "Correct" : "Incorrect"}<br>
Length: ${item.length} | Time: ${item.time.toFixed(2)}s | Points: ${item.points}
`;
els.history.appendChild(div);
});
}
function startTimer() {
state.roundStartTime = performance.now();
els.timerValue.textContent = "0.0";
clearInterval(state.timerInterval);
state.timerInterval = setInterval(() => {
const seconds = (performance.now() - state.roundStartTime) / 1000;
els.timerValue.textContent = seconds.toFixed(1);
}, 100);
}
function stopTimer() {
clearInterval(state.timerInterval);
const seconds = (performance.now() - state.roundStartTime) / 1000;
els.timerValue.textContent = seconds.toFixed(1);
return seconds;
}
function startGame() {
resetGame(false);
els.startBtn.disabled = true;
els.nextBtn.disabled = true;
nextRound();
}
function nextRound() {
if (state.round >= TOTAL_ROUNDS) {
showReport();
return;
}
state.round += 1;
state.acceptingAnswer = false;
els.nextBtn.disabled = true;
setFeedback("Memorize the pattern...", "neutral");
updateStats();
const length = Math.min(3 + Math.floor((state.round - 1) / 2), 7);
state.currentSequence = createSequence(length);
els.screen.innerHTML = "";
const content = document.createElement("div");
content.innerHTML = `<h2>Round ${state.round}</h2><p>Watch carefully.</p>`;
content.appendChild(renderSequence(state.currentSequence));
els.screen.appendChild(content);
const displayTime = Math.max(1300, 2600 - state.round * 120);
setTimeout(showChoices, displayTime);
}
function showChoices() {
state.acceptingAnswer = true;
setFeedback("Choose the exact matching sequence.", "neutral");
startTimer();
const choices = createChoices(state.currentSequence);
els.screen.innerHTML = "";
const content = document.createElement("div");
content.style.width = "100%";
content.innerHTML = `<h2>Select the Match</h2><p>The original pattern is hidden.</p>`;
const grid = document.createElement("div");
grid.className = "choice-grid";
choices.forEach((choiceSeq, index) => {
const button = document.createElement("button");
button.className = "choice";
button.setAttribute("aria-label", `Choice ${index + 1}: ${sequenceToText(choiceSeq)}`);
button.innerHTML = `<div style="margin-bottom: 8px;">Choice ${index + 1}</div><div>${sequenceToText(choiceSeq)}</div>`;
button.addEventListener("click", () => handleAnswer(button, choiceSeq));
grid.appendChild(button);
});
content.appendChild(grid);
els.screen.appendChild(content);
}
function handleAnswer(button, selectedSeq) {
if (!state.acceptingAnswer) return;
state.acceptingAnswer = false;
const responseTime = stopTimer();
const isCorrect = sequenceToText(selectedSeq) === sequenceToText(state.currentSequence);
const sequenceLength = state.currentSequence.length;
const speedBonus = Math.max(0, Math.round(30 - responseTime * 4));
const points = isCorrect ? sequenceLength * 20 + speedBonus : 0;
state.attempts += 1;
state.responseTimes.push(responseTime);
if (isCorrect) {
state.correct += 1;
state.score += points;
button.classList.add("correct");
setFeedback(`Correct! +${points} points`, "good");
} else {
button.classList.add("wrong");
setFeedback("Not quite. Review the correct pattern below.", "bad");
}
document.querySelectorAll(".choice").forEach(choice => {
if (choice.textContent.includes(sequenceToText(state.currentSequence))) {
choice.classList.add("correct");
}
choice.disabled = true;
});
state.history.push({
round: state.round,
correct: isCorrect,
length: sequenceLength,
time: responseTime,
points
});
updateStats();
updateHistory();
showCorrectPattern();
if (state.round >= TOTAL_ROUNDS) {
els.nextBtn.textContent = "View Report";
} else {
els.nextBtn.textContent = "Next Round";
}
els.nextBtn.disabled = false;
}
function showCorrectPattern() {
const existing = els.screen.querySelector(".correct-pattern");
if (existing) existing.remove();
const div = document.createElement("div");
div.className = "correct-pattern";
div.style.marginTop = "18px";
div.innerHTML = `<p><strong>Correct pattern:</strong> ${sequenceToText(state.currentSequence)}</p>`;
els.screen.firstElementChild.appendChild(div);
}
function showReport() {
clearInterval(state.timerInterval);
els.startBtn.disabled = false;
els.nextBtn.disabled = true;
els.nextBtn.textContent = "Next Round";
const accuracy = state.attempts ? Math.round((state.correct / state.attempts) * 100) : 0;
const avgTime = state.responseTimes.length
? state.responseTimes.reduce((a, b) => a + b, 0) / state.responseTimes.length
: 0;
const memory = accuracy;
const attention = Math.round(calculateAttentionScore());
const speed = Math.round(calculateSpeedScore());
const learning = Math.round(calculateLearningTrend());
let recommendation = "Balanced performance. Use this as one signal, not a final hiring decision.";
if (memory >= 80 && speed >= 70) recommendation = "Strong pattern recognition and fast decision-making under time pressure.";
if (memory < 60 && learning >= 60) recommendation = "Early misses improved over time, suggesting coachability and learning adaptation.";
if (memory < 50) recommendation = "May need additional job-relevant assessment before drawing conclusions.";
els.screen.innerHTML = `
<div class="report">
<h2>Candidate Assessment Report</h2>
<div class="report-card"><strong>Total Score:</strong> ${state.score}</div>
<div class="report-card"><strong>Accuracy:</strong> ${accuracy}% (${state.correct}/${state.attempts} correct)</div>
<div class="report-card"><strong>Average Response Time:</strong> ${avgTime.toFixed(2)} seconds</div>
<div class="report-card"><strong>Working Memory:</strong> ${memory}%</div>
<div class="report-card"><strong>Attention Under Difficulty:</strong> ${attention}%</div>
<div class="report-card"><strong>Decision Speed:</strong> ${speed}%</div>
<div class="report-card"><strong>Learning Trend:</strong> ${learning}%</div>
<div class="report-card"><strong>Interpretation:</strong> ${recommendation}</div>
<div class="report-card"><strong>Fairness Note:</strong> This game should only support hiring decisions when combined with human review, job-relevant tasks, accessibility options, and bias monitoring.</div>
</div>
`;
setFeedback("Game complete. Review the assessment report.", "good");
updateStats();
}
function resetGame(showStartScreen = true) {
clearInterval(state.timerInterval);
state.round = 0;
state.score = 0;
state.correct = 0;
state.attempts = 0;
state.currentSequence = [];
state.roundStartTime = null;
state.responseTimes = [];
state.history = [];
state.acceptingAnswer = false;
els.startBtn.disabled = false;
els.nextBtn.disabled = true;
els.nextBtn.textContent = "Next Round";
els.timerValue.textContent = "--";
els.history.innerHTML = "";
if (showStartScreen) {
els.screen.innerHTML = `
<div>
<h2>Ready?</h2>
<p>You will see a short color pattern. Memorize it before it disappears.</p>
<p>Then choose the matching sequence as quickly and accurately as possible.</p>
</div>
`;
setFeedback("Press Start to begin.", "neutral");
}
updateStats();
}
els.startBtn.addEventListener("click", startGame);
els.nextBtn.addEventListener("click", nextRound);
els.resetBtn.addEventListener("click", () => resetGame(true));
resetGame(true);
</script>
</body>
</html>