This commit is contained in:
Claude Agent
2026-04-06 16:26:23 +00:00
parent b41bd4eee9
commit 3b0af8348d
6 changed files with 141 additions and 83 deletions

1
.gitignore vendored
View File

@@ -60,3 +60,4 @@ tmp/
autonomous-coding/ autonomous-coding/
claude-plugins/ claude-plugins/
roa2web/ roa2web/
.gstack/

1
gomag-vending Submodule

Submodule gomag-vending added at 51910148ef

View File

@@ -20,8 +20,7 @@ const PROBLEMS = [
{ label: "C", text: "x = 20, y = 16", correct: false }, { label: "C", text: "x = 20, y = 16", correct: false },
{ label: "D", text: "x = 30, y = 6", correct: false } { label: "D", text: "x = 30, y = 6", correct: false }
], ],
marker: { top: "13%", left: "15%" }, marker: { top: "18%", left: "16%" }
overlay: { top: "0%", left: "3%", width: "24%", height: "19%" }
}, },
{ {
id: 2, id: 2,
@@ -34,8 +33,7 @@ const PROBLEMS = [
{ label: "C", text: "p = 10, c = 10", correct: false }, { label: "C", text: "p = 10, c = 10", correct: false },
{ label: "D", text: "p = 16, c = 4", correct: false } { label: "D", text: "p = 16, c = 4", correct: false }
], ],
marker: { top: "50%", left: "12%" }, marker: { top: "46%", left: "12%" }
overlay: { top: "33%", left: "1%", width: "22%", height: "17%" }
}, },
{ {
id: 3, id: 3,
@@ -48,8 +46,7 @@ const PROBLEMS = [
{ label: "C", text: "h = 12, s = 10", correct: false }, { label: "C", text: "h = 12, s = 10", correct: false },
{ label: "D", text: "h = 11, s = 11", correct: false } { label: "D", text: "h = 11, s = 11", correct: false }
], ],
marker: { top: "12%", left: "65%" }, marker: { top: "22%", left: "72%" }
overlay: { top: "1%", left: "52%", width: "32%", height: "20%" }
}, },
{ {
id: 4, id: 4,
@@ -62,8 +59,7 @@ const PROBLEMS = [
{ label: "C", text: "m = 18, c = 6", correct: false }, { label: "C", text: "m = 18, c = 6", correct: false },
{ label: "D", text: "m = 14, c = 10", correct: false } { label: "D", text: "m = 14, c = 10", correct: false }
], ],
marker: { top: "72%", left: "22%" }, marker: { top: "72%", left: "24%" }
overlay: { top: "57%", left: "8%", width: "25%", height: "22%" }
}, },
{ {
id: 5, id: 5,
@@ -76,8 +72,7 @@ const PROBLEMS = [
{ label: "C", text: "p = 14, c = 8", correct: false }, { label: "C", text: "p = 14, c = 8", correct: false },
{ label: "D", text: "p = 18, c = 4", correct: false } { label: "D", text: "p = 18, c = 4", correct: false }
], ],
marker: { top: "58%", left: "48%" }, marker: { top: "58%", left: "60%" }
overlay: { top: "41%", left: "34%", width: "26%", height: "20%" }
}, },
{ {
id: 6, id: 6,
@@ -90,8 +85,7 @@ const PROBLEMS = [
{ label: "C", text: "m = 15, n = 15", correct: false }, { label: "C", text: "m = 15, n = 15", correct: false },
{ label: "D", text: "m = 22, n = 8", correct: false } { label: "D", text: "m = 22, n = 8", correct: false }
], ],
marker: { top: "28%", left: "80%" }, marker: { top: "50%", left: "88%" }
overlay: { top: "16%", left: "60%", width: "32%", height: "38%" }
} }
]; ];
@@ -147,7 +141,6 @@ const TOTAL_HEARTS = 10;
--heart-empty: #bdc3c7; --heart-empty: #bdc3c7;
--star-gold: #f1c40f; --star-gold: #f1c40f;
--star-locked: #95a5a6; --star-locked: #95a5a6;
--overlay-bg: rgba(244,228,193,0.80);
--success-green: #4caf50; --success-green: #4caf50;
--backdrop: rgba(0,0,0,0.5); --backdrop: rgba(0,0,0,0.5);
} }
@@ -210,12 +203,20 @@ html, body {
.star { .star {
font-size: clamp(22px, 3vmin, 36px); font-size: clamp(22px, 3vmin, 36px);
color: var(--star-locked); color: var(--star-locked);
transition: color 0.5s, transform 0.5s; transition: color 0.5s, transform 0.5s, filter 0.5s;
} }
.star.solved { .star.solved {
color: var(--star-gold); color: var(--star-gold);
transform: scale(1.2);
filter: drop-shadow(0 0 6px rgba(241,196,15,0.6)); filter: drop-shadow(0 0 6px rgba(241,196,15,0.6));
animation: starPop 0.8s ease-out;
}
@keyframes starPop {
0% { transform: scale(1); }
20% { transform: scale(2.2) rotate(-15deg); }
40% { transform: scale(1.8) rotate(10deg); }
60% { transform: scale(2.0) rotate(-5deg); }
80% { transform: scale(1.3) rotate(2deg); }
100% { transform: scale(1.2) rotate(0deg); }
} }
.hearts { display: flex; gap: 0.5vmin; } .hearts { display: flex; gap: 0.5vmin; }
.heart { .heart {
@@ -227,40 +228,15 @@ html, body {
transform: scale(0.8); transform: scale(0.8);
} }
/* ============================================================
OVERLAYS (cover text areas)
============================================================ */
.overlay {
position: absolute;
background: var(--overlay-bg);
border: 1px dashed rgba(139,115,85,0.4);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
z-index: 5;
}
.overlay .lock-icon {
font-size: clamp(20px, 2.5vmin, 36px);
opacity: 0.4;
user-select: none;
}
.overlay.revealed {
opacity: 0;
transform: scale(1.02);
pointer-events: none;
}
/* ============================================================ /* ============================================================
MARKERS MARKERS
============================================================ */ ============================================================ */
.marker { .marker {
position: absolute; position: absolute;
width: 3.5vmin; height: 3.5vmin; width: 5vmin; height: 5vmin;
min-width: 30px; min-height: 30px; min-width: 40px; min-height: 40px;
border: 2px solid rgba(139,115,85,0.6); border: 3px solid #8b7355;
background: rgba(244,228,193,0.4); background: #f4e4c1;
border-radius: 50%; border-radius: 50%;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
@@ -269,14 +245,12 @@ html, body {
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
transition: all 0.2s; transition: all 0.2s;
z-index: 6; z-index: 6;
/* Larger hitbox via padding */ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
padding: 1vmin;
margin: -1vmin;
} }
.marker:hover:not(.solved) { .marker:hover:not(.solved) {
background: rgba(244,228,193,0.7); background: #ffe0a0;
transform: translate(-50%, -50%) scale(1.1); transform: translate(-50%, -50%) scale(1.15);
box-shadow: 0 0 8px rgba(139,115,85,0.4); box-shadow: 0 4px 12px rgba(139,115,85,0.5);
} }
.marker:active:not(.solved) { .marker:active:not(.solved) {
transform: translate(-50%, -50%) scale(0.95); transform: translate(-50%, -50%) scale(0.95);
@@ -293,9 +267,8 @@ html, body {
font-weight: 700; font-weight: 700;
} }
.marker .marker-num { .marker .marker-num {
font: 700 clamp(10px, 1vmin, 14px) 'Nunito', sans-serif; font: 700 clamp(14px, 1.5vmin, 20px) 'Nunito', sans-serif;
color: var(--text-dark); color: var(--text-dark);
opacity: 0.7;
} }
.marker.solved .marker-num { display: none; } .marker.solved .marker-num { display: none; }
@@ -602,6 +575,24 @@ html, body {
font: 400 clamp(16px, 1.6vmin, 22px) / 1.5 'Nunito', sans-serif; font: 400 clamp(16px, 1.6vmin, 22px) / 1.5 'Nunito', sans-serif;
color: var(--text-body); color: var(--text-body);
} }
.victory .vic-buttons {
display: flex;
gap: 1em;
justify-content: center;
margin-top: 1.2em;
}
.vic-btn {
border: none;
border-radius: 10px;
padding: 0.7em 2em;
font: 700 clamp(16px, 1.6vmin, 22px) 'Nunito', sans-serif;
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 3px 8px rgba(0,0,0,0.2);
}
.vic-btn:hover { filter: brightness(1.1); transform: translateY(-2px); }
.vic-btn-map { background: var(--btn-blue); color: white; }
.vic-btn-restart { background: var(--success-green); color: white; }
/* ============================================================ /* ============================================================
CONFETTI CANVAS CONFETTI CANVAS
@@ -685,6 +676,10 @@ html, body {
<div class="vic-stars" id="vicStars"></div> <div class="vic-stars" id="vicStars"></div>
<div class="vic-bonus" id="vicBonus"></div> <div class="vic-bonus" id="vicBonus"></div>
<div class="vic-text">Ați descoperit toate secretele<br>drumului lui Nică prin Humulești!</div> <div class="vic-text">Ați descoperit toate secretele<br>drumului lui Nică prin Humulești!</div>
<div class="vic-buttons">
<button class="vic-btn vic-btn-map" id="vicCloseBtn">Vezi harta</button>
<button class="vic-btn vic-btn-restart" id="vicRestartBtn">Începe din nou</button>
</div>
</div> </div>
</div> </div>
<canvas id="confetti-canvas"></canvas> <canvas id="confetti-canvas"></canvas>
@@ -705,6 +700,48 @@ html, body {
disabledOptions: {} disabledOptions: {}
}; };
// ============================================================
// SOUND EFFECTS (Web Audio API — no external files)
// ============================================================
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function playTone(freq, type, duration, volume) {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.value = freq;
gain.gain.setValueAtTime(volume, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + duration);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + duration);
}
function sfxCorrect() {
playTone(523, "sine", 0.15, 0.3);
setTimeout(() => playTone(659, "sine", 0.15, 0.3), 100);
setTimeout(() => playTone(784, "sine", 0.25, 0.3), 200);
}
function sfxWrong() {
playTone(200, "square", 0.2, 0.2);
setTimeout(() => playTone(160, "square", 0.3, 0.2), 150);
}
function sfxHeartLost() {
playTone(440, "sine", 0.1, 0.2);
setTimeout(() => playTone(370, "sine", 0.15, 0.2), 80);
setTimeout(() => playTone(300, "sine", 0.25, 0.15), 160);
}
function sfxStar() {
const notes = [784, 988, 1175, 1319];
notes.forEach((f, i) => {
setTimeout(() => playTone(f, "sine", 0.2, 0.25), i * 80);
});
}
// ============================================================ // ============================================================
// DOM REFS // DOM REFS
// ============================================================ // ============================================================
@@ -733,6 +770,8 @@ html, body {
const victoryEl = $("victory"); const victoryEl = $("victory");
const vicStars = $("vicStars"); const vicStars = $("vicStars");
const vicBonus = $("vicBonus"); const vicBonus = $("vicBonus");
const vicCloseBtn = $("vicCloseBtn");
const vicRestartBtn = $("vicRestartBtn");
// ============================================================ // ============================================================
// INIT UI // INIT UI
@@ -749,19 +788,8 @@ html, body {
} }
// Hearts // Hearts
renderHearts(); renderHearts();
// Overlays & markers // Markers
PROBLEMS.forEach(p => { PROBLEMS.forEach(p => {
// Overlay
const ov = document.createElement("div");
ov.className = "overlay";
ov.id = "overlay-" + p.id;
ov.style.top = p.overlay.top;
ov.style.left = p.overlay.left;
ov.style.width = p.overlay.width;
ov.style.height = p.overlay.height;
ov.innerHTML = '<span class="lock-icon">🔒</span>';
mapWrapper.appendChild(ov);
// Marker // Marker
const mk = document.createElement("div"); const mk = document.createElement("div");
mk.className = "marker"; mk.className = "marker";
@@ -815,13 +843,26 @@ html, body {
feedbackWrong.style.display = "none"; feedbackWrong.style.display = "none";
optionsArea.style.display = "grid"; optionsArea.style.display = "grid";
// Shuffle options order (once per problem, persist for reopens)
if (!state.shuffledOptions) state.shuffledOptions = {};
if (!state.shuffledOptions[p.id]) {
const indices = p.options.map((_, i) => i);
for (let i = indices.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[indices[i], indices[j]] = [indices[j], indices[i]];
}
state.shuffledOptions[p.id] = indices;
}
const order = state.shuffledOptions[p.id];
const labels = ["A", "B", "C", "D"];
const disabled = state.disabledOptions[p.id] || new Set(); const disabled = state.disabledOptions[p.id] || new Set();
p.options.forEach((opt, i) => { order.forEach((origIdx, displayIdx) => {
const opt = p.options[origIdx];
const btn = document.createElement("button"); const btn = document.createElement("button");
btn.className = "option-btn"; btn.className = "option-btn";
btn.textContent = opt.label + ") " + opt.text; btn.textContent = labels[displayIdx] + ") " + opt.text;
btn.disabled = disabled.has(i); btn.disabled = disabled.has(origIdx);
btn.addEventListener("click", () => checkAnswer(p.id, i)); btn.addEventListener("click", () => checkAnswer(p.id, origIdx));
optionsArea.appendChild(btn); optionsArea.appendChild(btn);
}); });
@@ -847,38 +888,42 @@ html, body {
if (opt.correct) { if (opt.correct) {
// Correct! // Correct!
sfxCorrect();
optionsArea.style.display = "none"; optionsArea.style.display = "none";
feedbackBravo.style.display = "block"; feedbackBravo.style.display = "block";
feedbackWrong.style.display = "none"; feedbackWrong.style.display = "none";
feedbackEl.classList.add("visible"); feedbackEl.classList.add("visible");
state.solved.add(problemId); state.solved.add(problemId);
updateStar(PROBLEMS.findIndex(x => x.id === problemId)); const starIdx = PROBLEMS.findIndex(x => x.id === problemId);
// Reveal overlay
const ov = $("overlay-" + problemId);
if (ov) ov.classList.add("revealed");
// Mark marker solved // Mark marker solved
const mk = $("marker-" + problemId); const mk = $("marker-" + problemId);
if (mk) mk.classList.add("solved"); if (mk) mk.classList.add("solved");
// Close after 2s // Close after 2s, THEN animate star so it's visible
setTimeout(() => { setTimeout(() => {
closeModal(); closeModal();
setTimeout(() => {
sfxStar();
updateStar(starIdx);
if (state.solved.size === PROBLEMS.length) { if (state.solved.size === PROBLEMS.length) {
showVictory(); setTimeout(showVictory, 1000);
} }
}, 300);
}, 2000); }, 2000);
} else { } else {
// Wrong // Wrong
sfxWrong();
setTimeout(sfxHeartLost, 400);
loseHeart(); loseHeart();
if (!state.disabledOptions[problemId]) state.disabledOptions[problemId] = new Set(); if (!state.disabledOptions[problemId]) state.disabledOptions[problemId] = new Set();
state.disabledOptions[problemId].add(optionIdx); state.disabledOptions[problemId].add(optionIdx);
// Disable the button // Disable the button (find display index from original index)
const btns = optionsArea.querySelectorAll(".option-btn"); const btns = optionsArea.querySelectorAll(".option-btn");
if (btns[optionIdx]) btns[optionIdx].disabled = true; const displayIdx = state.shuffledOptions[problemId].indexOf(optionIdx);
if (displayIdx >= 0 && btns[displayIdx]) btns[displayIdx].disabled = true;
// Shake // Shake
modal.classList.remove("shake"); modal.classList.remove("shake");
@@ -929,6 +974,7 @@ html, body {
if (state.hintUsed.has(state.currentProblem.id)) return; if (state.hintUsed.has(state.currentProblem.id)) return;
state.hintUsed.add(state.currentProblem.id); state.hintUsed.add(state.currentProblem.id);
sfxHeartLost();
loseHeart(); loseHeart();
hintText.classList.add("shown"); hintText.classList.add("shown");
hintBtn.classList.add("used"); hintBtn.classList.add("used");
@@ -952,23 +998,24 @@ html, body {
gameOver.classList.add("visible"); gameOver.classList.add("visible");
} }
restartBtn.addEventListener("click", () => { function resetGame() {
// Reset everything
state.hearts = TOTAL_HEARTS; state.hearts = TOTAL_HEARTS;
state.solved.clear(); state.solved.clear();
state.currentProblem = null; state.currentProblem = null;
state.modalLocked = false; state.modalLocked = false;
state.hintUsed.clear(); state.hintUsed.clear();
state.disabledOptions = {}; state.disabledOptions = {};
state.shuffledOptions = {};
renderHearts(); renderHearts();
starsEl.querySelectorAll(".star").forEach(s => s.classList.remove("solved")); starsEl.querySelectorAll(".star").forEach(s => s.classList.remove("solved"));
document.querySelectorAll(".overlay").forEach(o => o.classList.remove("revealed"));
document.querySelectorAll(".marker").forEach(m => m.classList.remove("solved")); document.querySelectorAll(".marker").forEach(m => m.classList.remove("solved"));
gameOver.classList.remove("visible"); gameOver.classList.remove("visible");
victoryEl.classList.remove("visible"); victoryEl.classList.remove("visible");
}); }
restartBtn.addEventListener("click", resetGame);
// ============================================================ // ============================================================
// VICTORY // VICTORY
@@ -980,6 +1027,12 @@ html, body {
startConfetti(); startConfetti();
} }
vicCloseBtn.addEventListener("click", () => {
victoryEl.classList.remove("visible");
});
vicRestartBtn.addEventListener("click", resetGame);
// ============================================================ // ============================================================
// CONFETTI (100 particles, 3s burst) // CONFETTI (100 particles, 3s burst)
// ============================================================ // ============================================================

1
roaauto Submodule

Submodule roaauto added at d93a7b2903

1
space-booking Submodule

Submodule space-booking added at 953f3121cf

Submodule vending_data_intelligence_report added at 3f410c3fb8