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