diff --git a/.gitignore b/.gitignore index 1704cc2..ea1c2d9 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,4 @@ tmp/ autonomous-coding/ claude-plugins/ roa2web/ +.gstack/ diff --git a/gomag-vending b/gomag-vending new file mode 160000 index 0000000..5191014 --- /dev/null +++ b/gomag-vending @@ -0,0 +1 @@ +Subproject commit 51910148ef642b274883aa9d970bb41c348cc3c4 diff --git a/paula-escape/escape_room.html b/paula-escape/escape_room.html index a60c57e..33f17a5 100644 --- a/paula-escape/escape_room.html +++ b/paula-escape/escape_room.html @@ -20,8 +20,7 @@ const PROBLEMS = [ { label: "C", text: "x = 20, y = 16", correct: false }, { label: "D", text: "x = 30, y = 6", correct: false } ], - marker: { top: "13%", left: "15%" }, - overlay: { top: "0%", left: "3%", width: "24%", height: "19%" } + marker: { top: "18%", left: "16%" } }, { id: 2, @@ -34,8 +33,7 @@ const PROBLEMS = [ { label: "C", text: "p = 10, c = 10", correct: false }, { label: "D", text: "p = 16, c = 4", correct: false } ], - marker: { top: "50%", left: "12%" }, - overlay: { top: "33%", left: "1%", width: "22%", height: "17%" } + marker: { top: "46%", left: "12%" } }, { id: 3, @@ -48,8 +46,7 @@ const PROBLEMS = [ { label: "C", text: "h = 12, s = 10", correct: false }, { label: "D", text: "h = 11, s = 11", correct: false } ], - marker: { top: "12%", left: "65%" }, - overlay: { top: "1%", left: "52%", width: "32%", height: "20%" } + marker: { top: "22%", left: "72%" } }, { id: 4, @@ -62,8 +59,7 @@ const PROBLEMS = [ { label: "C", text: "m = 18, c = 6", correct: false }, { label: "D", text: "m = 14, c = 10", correct: false } ], - marker: { top: "72%", left: "22%" }, - overlay: { top: "57%", left: "8%", width: "25%", height: "22%" } + marker: { top: "72%", left: "24%" } }, { id: 5, @@ -76,8 +72,7 @@ const PROBLEMS = [ { label: "C", text: "p = 14, c = 8", correct: false }, { label: "D", text: "p = 18, c = 4", correct: false } ], - marker: { top: "58%", left: "48%" }, - overlay: { top: "41%", left: "34%", width: "26%", height: "20%" } + marker: { top: "58%", left: "60%" } }, { id: 6, @@ -90,8 +85,7 @@ const PROBLEMS = [ { label: "C", text: "m = 15, n = 15", correct: false }, { label: "D", text: "m = 22, n = 8", correct: false } ], - marker: { top: "28%", left: "80%" }, - overlay: { top: "16%", left: "60%", width: "32%", height: "38%" } + marker: { top: "50%", left: "88%" } } ]; @@ -147,7 +141,6 @@ const TOTAL_HEARTS = 10; --heart-empty: #bdc3c7; --star-gold: #f1c40f; --star-locked: #95a5a6; - --overlay-bg: rgba(244,228,193,0.80); --success-green: #4caf50; --backdrop: rgba(0,0,0,0.5); } @@ -210,12 +203,20 @@ html, body { .star { font-size: clamp(22px, 3vmin, 36px); color: var(--star-locked); - transition: color 0.5s, transform 0.5s; + transition: color 0.5s, transform 0.5s, filter 0.5s; } .star.solved { color: var(--star-gold); - transform: scale(1.2); 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; } .heart { @@ -227,40 +228,15 @@ html, body { 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 ============================================================ */ .marker { position: absolute; - width: 3.5vmin; height: 3.5vmin; - min-width: 30px; min-height: 30px; - border: 2px solid rgba(139,115,85,0.6); - background: rgba(244,228,193,0.4); + width: 5vmin; height: 5vmin; + min-width: 40px; min-height: 40px; + border: 3px solid #8b7355; + background: #f4e4c1; border-radius: 50%; cursor: pointer; display: flex; @@ -269,14 +245,12 @@ html, body { transform: translate(-50%, -50%); transition: all 0.2s; z-index: 6; - /* Larger hitbox via padding */ - padding: 1vmin; - margin: -1vmin; + box-shadow: 0 2px 8px rgba(0,0,0,0.3); } .marker:hover:not(.solved) { - background: rgba(244,228,193,0.7); - transform: translate(-50%, -50%) scale(1.1); - box-shadow: 0 0 8px rgba(139,115,85,0.4); + background: #ffe0a0; + transform: translate(-50%, -50%) scale(1.15); + box-shadow: 0 4px 12px rgba(139,115,85,0.5); } .marker:active:not(.solved) { transform: translate(-50%, -50%) scale(0.95); @@ -293,9 +267,8 @@ html, body { font-weight: 700; } .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); - opacity: 0.7; } .marker.solved .marker-num { display: none; } @@ -602,6 +575,24 @@ html, body { font: 400 clamp(16px, 1.6vmin, 22px) / 1.5 'Nunito', sans-serif; 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 @@ -685,6 +676,10 @@ html, body {
Ați descoperit toate secretele
drumului lui Nică prin Humulești!
+
+ + +
@@ -705,6 +700,48 @@ html, body { 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 // ============================================================ @@ -733,6 +770,8 @@ html, body { const victoryEl = $("victory"); const vicStars = $("vicStars"); const vicBonus = $("vicBonus"); + const vicCloseBtn = $("vicCloseBtn"); + const vicRestartBtn = $("vicRestartBtn"); // ============================================================ // INIT UI @@ -749,19 +788,8 @@ html, body { } // Hearts renderHearts(); - // Overlays & markers + // Markers 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 = '🔒'; - mapWrapper.appendChild(ov); - // Marker const mk = document.createElement("div"); mk.className = "marker"; @@ -815,13 +843,26 @@ html, body { feedbackWrong.style.display = "none"; 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(); - p.options.forEach((opt, i) => { + order.forEach((origIdx, displayIdx) => { + const opt = p.options[origIdx]; const btn = document.createElement("button"); btn.className = "option-btn"; - btn.textContent = opt.label + ") " + opt.text; - btn.disabled = disabled.has(i); - btn.addEventListener("click", () => checkAnswer(p.id, i)); + btn.textContent = labels[displayIdx] + ") " + opt.text; + btn.disabled = disabled.has(origIdx); + btn.addEventListener("click", () => checkAnswer(p.id, origIdx)); optionsArea.appendChild(btn); }); @@ -847,38 +888,42 @@ html, body { if (opt.correct) { // Correct! + sfxCorrect(); optionsArea.style.display = "none"; feedbackBravo.style.display = "block"; feedbackWrong.style.display = "none"; feedbackEl.classList.add("visible"); state.solved.add(problemId); - updateStar(PROBLEMS.findIndex(x => x.id === problemId)); - - // Reveal overlay - const ov = $("overlay-" + problemId); - if (ov) ov.classList.add("revealed"); + const starIdx = PROBLEMS.findIndex(x => x.id === problemId); // Mark marker solved const mk = $("marker-" + problemId); if (mk) mk.classList.add("solved"); - // Close after 2s + // Close after 2s, THEN animate star so it's visible setTimeout(() => { closeModal(); - if (state.solved.size === PROBLEMS.length) { - showVictory(); - } + setTimeout(() => { + sfxStar(); + updateStar(starIdx); + if (state.solved.size === PROBLEMS.length) { + setTimeout(showVictory, 1000); + } + }, 300); }, 2000); } else { // Wrong + sfxWrong(); + setTimeout(sfxHeartLost, 400); loseHeart(); if (!state.disabledOptions[problemId]) state.disabledOptions[problemId] = new Set(); state.disabledOptions[problemId].add(optionIdx); - // Disable the button + // Disable the button (find display index from original index) 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 modal.classList.remove("shake"); @@ -929,6 +974,7 @@ html, body { if (state.hintUsed.has(state.currentProblem.id)) return; state.hintUsed.add(state.currentProblem.id); + sfxHeartLost(); loseHeart(); hintText.classList.add("shown"); hintBtn.classList.add("used"); @@ -952,23 +998,24 @@ html, body { gameOver.classList.add("visible"); } - restartBtn.addEventListener("click", () => { - // Reset everything + function resetGame() { state.hearts = TOTAL_HEARTS; state.solved.clear(); state.currentProblem = null; state.modalLocked = false; state.hintUsed.clear(); state.disabledOptions = {}; + state.shuffledOptions = {}; renderHearts(); 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")); gameOver.classList.remove("visible"); victoryEl.classList.remove("visible"); - }); + } + + restartBtn.addEventListener("click", resetGame); // ============================================================ // VICTORY @@ -980,6 +1027,12 @@ html, body { startConfetti(); } + vicCloseBtn.addEventListener("click", () => { + victoryEl.classList.remove("visible"); + }); + + vicRestartBtn.addEventListener("click", resetGame); + // ============================================================ // CONFETTI (100 particles, 3s burst) // ============================================================ diff --git a/roaauto b/roaauto new file mode 160000 index 0000000..d93a7b2 --- /dev/null +++ b/roaauto @@ -0,0 +1 @@ +Subproject commit d93a7b29039d8be4a6a1daa446ad447ae1083631 diff --git a/space-booking b/space-booking new file mode 160000 index 0000000..953f312 --- /dev/null +++ b/space-booking @@ -0,0 +1 @@ +Subproject commit 953f3121cf02522bc34aa771d6e3d8f46b65e24e diff --git a/vending_data_intelligence_report b/vending_data_intelligence_report new file mode 160000 index 0000000..3f410c3 --- /dev/null +++ b/vending_data_intelligence_report @@ -0,0 +1 @@ +Subproject commit 3f410c3fb8b7d9dc075d62986c47a4697356df82