diff --git a/data/projects.json b/data/projects.json
index b41ca77..8529438 100644
--- a/data/projects.json
+++ b/data/projects.json
@@ -208,7 +208,6 @@
"difficulty": "easy"
},
{
-
"title": "Tip Calculator",
"slug": "Tip Calculator",
"description": "Calculate tips and divide bills accurately in seconds",
@@ -217,14 +216,12 @@
"difficulty": "easy"
},
{
-
"title": "Hangman Game",
"slug": "hangman",
"description": "A two player word guessing game where one enters a secret word and the other tries to guess it.",
"category": "Small Games",
"categoryKey": "games",
"difficulty": "medium"
-
},
{
"title": "Orbit Game",
@@ -257,5 +254,13 @@
"category": "productivity",
"categoryKey": "productivity",
"difficulty": "intermediate"
+ },
+ {
+ "title": "gravity-flip-pinball",
+ "slug": "gravity-flip-pinball",
+ "description": "A simple pinball game where you control gravity to move the ball and score by passing through gates.",
+ "category": "Small Games",
+ "categoryKey": "games",
+ "difficulty": "haed"
}
]
diff --git a/projects/gravity-flip-pinball/index.html b/projects/gravity-flip-pinball/index.html
new file mode 100644
index 0000000..ea95fb0
--- /dev/null
+++ b/projects/gravity-flip-pinball/index.html
@@ -0,0 +1,59 @@
+
+
+
+
+
+ Gravity Flip Pinball
+
+
+
+
+
Gravity Flip Pinball
+
+ Score:
+ 0
+
+
+ Gates:
+ 0/3
+
+
+ Status:
+ Ready
+
+
+
+
+
+
+ W/A/S/D — change gravity
+ Double-click canvas to quick reset
+
+
+
+
+
+
+
+ 60
+
+
+
+
+
+
+
+
+
+
Victory!
+
You collected all gates and reached the end!
+
+
+
+
+
+
+
diff --git a/projects/gravity-flip-pinball/script.js b/projects/gravity-flip-pinball/script.js
new file mode 100644
index 0000000..4af943e
--- /dev/null
+++ b/projects/gravity-flip-pinball/script.js
@@ -0,0 +1,494 @@
+const canvas = document.getElementById("gameCanvas");
+const ctx = canvas.getContext("2d");
+
+let gravity = { x: 0, y: 0.4, dir: "down" };
+let ball = { x: 100, y: 100, vx: 0, vy: 0, radius: 14 };
+let score = 0;
+let status = "ready";
+let challengeMode = false;
+let timeRemaining = 60;
+let timerInterval = null;
+
+let walls = [];
+let gates = [];
+let startZone = { x: 50, y: 50, w: 80, h: 80 };
+let endZone = { x: 100, y: 100, w: 100, h: 100 };
+
+const layoutConfig = {
+ margin: 18,
+ thickness: 15,
+ gateDefs: [
+ { x: 0.18, y: 0.21, w: 0.16, h: 0.02 },
+ { x: 0.78, y: 0.45, w: 0.12, h: 0.02 },
+ { x: 0.18, y: 0.69, w: 0.06, h: 0.02 },
+ ],
+};
+
+function setGravity(x, y, dir) {
+ gravity = { x, y, dir };
+ updateGravityIndicator();
+}
+
+function updateGravityIndicator() {
+ document.querySelectorAll(".gravity-btn").forEach((btn) => {
+ btn.classList.remove("active");
+ });
+ const activeBtn = document.querySelector(
+ `.gravity-btn[data-key="${
+ gravity.dir === "up"
+ ? "w"
+ : gravity.dir === "down"
+ ? "s"
+ : gravity.dir === "left"
+ ? "a"
+ : "d"
+ }"]`
+ );
+ if (activeBtn) activeBtn.classList.add("active");
+}
+
+function drawWalls() {
+ ctx.strokeStyle = "#6ee7b7";
+ ctx.lineWidth = 4;
+ ctx.strokeRect(2, 2, canvas.width - 4, canvas.height - 4);
+
+ walls.forEach((w) => {
+ const gradient = ctx.createLinearGradient(w.x, w.y, w.x + w.w, w.y + w.h);
+ gradient.addColorStop(0, "rgba(110, 231, 183, 0.3)");
+ gradient.addColorStop(1, "rgba(147, 197, 253, 0.3)");
+ ctx.fillStyle = gradient;
+ ctx.fillRect(w.x, w.y, w.w, w.h);
+ ctx.strokeStyle = "rgba(110, 231, 183, 0.5)";
+ ctx.lineWidth = 2;
+ ctx.strokeRect(w.x, w.y, w.w, w.h);
+ });
+}
+
+function drawZone(zone, color, label) {
+ ctx.fillStyle = color;
+ ctx.fillRect(zone.x, zone.y, zone.w, zone.h);
+ ctx.strokeStyle = color.replace("0.2", "0.6");
+ ctx.lineWidth = 3;
+ ctx.strokeRect(zone.x, zone.y, zone.w, zone.h);
+ ctx.fillStyle = "#fff";
+ ctx.font = "bold 18px system-ui";
+ ctx.textAlign = "center";
+ ctx.fillText(label, zone.x + zone.w / 2, zone.y + zone.h / 2 + 6);
+ ctx.textAlign = "left";
+}
+
+function drawBall() {
+ // Outer glow
+ ctx.beginPath();
+ ctx.arc(ball.x, ball.y, ball.radius + 4, 0, Math.PI * 2);
+ const glowGradient = ctx.createRadialGradient(
+ ball.x,
+ ball.y,
+ 0,
+ ball.x,
+ ball.y,
+ ball.radius + 4
+ );
+ glowGradient.addColorStop(0, "rgba(110, 231, 183, 0.3)");
+ glowGradient.addColorStop(1, "rgba(110, 231, 183, 0)");
+ ctx.fillStyle = glowGradient;
+ ctx.fill();
+
+ // Ball
+ ctx.beginPath();
+ ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
+ const ballGradient = ctx.createRadialGradient(
+ ball.x - ball.radius / 3,
+ ball.y - ball.radius / 3,
+ 0,
+ ball.x,
+ ball.y,
+ ball.radius
+ );
+ ballGradient.addColorStop(0, "#6ee7b7");
+ ballGradient.addColorStop(1, "#34d399");
+ ctx.fillStyle = ballGradient;
+ ctx.shadowBlur = 20;
+ ctx.shadowColor = "#6ee7b7";
+ ctx.fill();
+ ctx.shadowBlur = 0;
+
+ // Highlight
+ ctx.beginPath();
+ ctx.arc(
+ ball.x - ball.radius / 4,
+ ball.y - ball.radius / 4,
+ ball.radius / 3,
+ 0,
+ Math.PI * 2
+ );
+ ctx.fillStyle = "rgba(255, 255, 255, 0.4)";
+ ctx.fill();
+}
+
+function drawGates() {
+ gates.forEach((g, i) => {
+ if (g.passed) {
+ ctx.fillStyle = "rgba(110, 231, 183, 0.6)";
+ ctx.strokeStyle = "rgba(110, 231, 183, 0.9)";
+ } else {
+ ctx.fillStyle = "rgba(251, 191, 36, 0.7)";
+ ctx.strokeStyle = "rgba(251, 191, 36, 1)";
+
+ // Pulsing effect for active gates
+ const pulse = Math.sin(Date.now() / 300 + i) * 0.2 + 0.8;
+ ctx.shadowBlur = 15 * pulse;
+ ctx.shadowColor = "rgba(251, 191, 36, 0.8)";
+ }
+
+ ctx.fillRect(g.x, g.y, g.w, g.h);
+ ctx.lineWidth = 3;
+ ctx.strokeRect(g.x, g.y, g.w, g.h);
+ ctx.shadowBlur = 0;
+
+ // Gate number
+ ctx.fillStyle = "#0b1020";
+ ctx.font = "bold 12px system-ui";
+ ctx.textAlign = "center";
+ ctx.fillText(i + 1, g.x + g.w / 2, g.y + g.h / 2 + 4);
+ ctx.textAlign = "left";
+ });
+}
+
+function ballRectCollision(b, rect) {
+ const closestX = Math.max(rect.x, Math.min(b.x, rect.x + rect.w));
+ const closestY = Math.max(rect.y, Math.min(b.y, rect.y + rect.h));
+
+ const dx = b.x - closestX;
+ const dy = b.y - closestY;
+ const distSq = dx * dx + dy * dy;
+
+ if (distSq < b.radius * b.radius) {
+ const dist = Math.sqrt(distSq) || 1;
+ const overlap = b.radius - dist;
+
+ b.x += (dx / dist) * overlap;
+ b.y += (dy / dist) * overlap;
+
+ const normalX = dx / dist;
+ const normalY = dy / dist;
+ const dot = b.vx * normalX + b.vy * normalY;
+ b.vx -= 2 * dot * normalX;
+ b.vy -= 2 * dot * normalY;
+
+ b.vx *= 0.8;
+ b.vy *= 0.8;
+ }
+}
+
+function updateBall() {
+ if (status !== "playing") return;
+
+ ball.vx += gravity.x;
+ ball.vy += gravity.y;
+ ball.x += ball.vx;
+ ball.y += ball.vy;
+
+ if (ball.x - ball.radius < 0 || ball.x + ball.radius > canvas.width) {
+ ball.vx *= -0.8;
+ ball.x = Math.max(
+ ball.radius,
+ Math.min(canvas.width - ball.radius, ball.x)
+ );
+ }
+ if (ball.y - ball.radius < 0 || ball.y + ball.radius > canvas.height) {
+ ball.vy *= -0.8;
+ ball.y = Math.max(
+ ball.radius,
+ Math.min(canvas.height - ball.radius, ball.y)
+ );
+ }
+
+ walls.forEach((w) => ballRectCollision(ball, w));
+
+ gates.forEach((g, i) => {
+ if (
+ !g.passed &&
+ ball.x > g.x + 1 &&
+ ball.x < g.x + g.w - 1 &&
+ ball.y > g.y + 1 &&
+ ball.y < g.y + g.h - 1
+ ) {
+ g.passed = true;
+ score += 10;
+ document.getElementById("score").textContent = score;
+ document.getElementById("gates").textContent = `${
+ gates.filter((g) => g.passed).length
+ }/${gates.length}`;
+ }
+ });
+
+ if (
+ ball.x > endZone.x &&
+ ball.x < endZone.x + endZone.w &&
+ ball.y > endZone.y &&
+ ball.y < endZone.y + endZone.h
+ ) {
+ const allGatesPassed = gates.every((g) => g.passed);
+ if (allGatesPassed) {
+ status = "won";
+ document.getElementById("status").textContent = "Victory!";
+ showVictoryModal();
+ }
+ }
+}
+
+function showVictoryModal() {
+ if (timerInterval) {
+ clearInterval(timerInterval);
+ timerInterval = null;
+ }
+
+ const modal = document.getElementById("victoryModal");
+ const title = document.getElementById("victoryTitle");
+ const message = document.getElementById("victoryMessage");
+
+ if (challengeMode) {
+ const timeTaken = 60 - timeRemaining;
+ title.textContent = "Challenge Complete!";
+ message.textContent = `You finished in ${timeTaken} seconds with a score of ${score}!`;
+ } else {
+ title.textContent = "Victory!";
+ message.textContent = `You collected all gates with a score of ${score}!`;
+ }
+
+ modal.classList.add("show");
+}
+
+function startTimer() {
+ if (timerInterval) clearInterval(timerInterval);
+
+ timeRemaining = 60;
+ const timerEl = document.getElementById("timer");
+ timerEl.textContent = timeRemaining;
+ timerEl.classList.remove("warning", "critical");
+
+ timerInterval = setInterval(() => {
+ timeRemaining--;
+ timerEl.textContent = timeRemaining;
+
+ if (timeRemaining <= 10) {
+ timerEl.classList.add("critical");
+ timerEl.classList.remove("warning");
+ } else if (timeRemaining <= 20) {
+ timerEl.classList.add("warning");
+ }
+
+ if (timeRemaining <= 0) {
+ clearInterval(timerInterval);
+ timerInterval = null;
+ status = "lost";
+ document.getElementById("status").textContent = "Time's Up!";
+ document.getElementById("victoryTitle").textContent = "Time's Up!";
+ document.getElementById(
+ "victoryMessage"
+ ).textContent = `You scored ${score} points. Try again!`;
+ document.getElementById("victoryModal").classList.add("show");
+ }
+ }, 1000);
+}
+
+function buildLayout() {
+ const w = canvas.width;
+ const h = canvas.height;
+ const m = layoutConfig.margin;
+ const t = layoutConfig.thickness;
+
+ startZone = { x: m + 8, y: m + 8, w: 88, h: 88 };
+ endZone = { x: w - m - 110, y: h - m - 110, w: 100, h: 100 };
+
+ gates = layoutConfig.gateDefs.map((g) => ({
+ x: Math.floor(g.x * w),
+ y: Math.floor(g.y * h),
+ w: Math.max(24, Math.floor(g.w * w)),
+ h: Math.max(10, Math.floor(g.h * h)),
+ passed: false,
+ }));
+
+ walls = [];
+ walls.push({ x: 0, y: 0, w: t, h: h });
+ walls.push({ x: w - t, y: 0, w: t, h: h });
+ walls.push({ x: 0, y: 0, w: w, h: t });
+ walls.push({ x: 0, y: h - t, w: w, h: t });
+
+ //horizontal walls
+ const horizontalYs = [
+ Math.floor(h * 0.22),
+ Math.floor(h * 0.46),
+ Math.floor(h * 0.7),
+ ];
+
+ horizontalYs.forEach((yy) => {
+ const wallY = yy - Math.floor(t / 2);
+ let segments = [{ x1: m, x2: w - m }];
+
+ layoutConfig.gateDefs.forEach((gd) => {
+ const gx = Math.floor(gd.x * w);
+ const gw = Math.max(24, Math.floor(gd.w * w));
+ const gy = Math.floor(gd.y * h);
+ const gh = Math.max(10, Math.floor(gd.h * h));
+
+ if (Math.abs(gy - yy) < gh + t) {
+ const newSegs = [];
+ segments.forEach((s) => {
+ if (gx > s.x1 + 10 && gx < s.x2 - 10) {
+ newSegs.push({ x1: s.x1, x2: gx - 4 });
+ newSegs.push({ x1: gx + gw + 4, x2: s.x2 });
+ } else if (gx + gw < s.x2 - 10 && gx + gw > s.x1 + 10) {
+ newSegs.push(s);
+ }
+ });
+ if (newSegs.length > 0) segments = newSegs;
+ }
+ });
+
+ segments.forEach((s) => {
+ const segW = Math.max(20, s.x2 - s.x1);
+ if (segW > 30) {
+ walls.push({ x: s.x1, y: wallY, w: segW, h: t });
+ }
+ });
+ });
+
+ //vertical walls
+ walls.push({
+ x: Math.floor(w * 0.4),
+ y: Math.floor(h * 0.31),
+ w: t,
+ h: Math.floor(h * 0.14),
+ });
+ walls.push({
+ x: Math.floor(w * 0.66),
+ y: Math.floor(h * 0.47),
+ w: t,
+ h: Math.floor(h * 0.14),
+ });
+ walls.push({
+ x: Math.floor(w * 0.32),
+ y: Math.floor(h * 0.55),
+ w: t,
+ h: Math.floor(h * 0.14),
+ });
+ walls.push({
+ x: Math.floor(w * 0.44),
+ y: Math.floor(h * 0.84),
+ w: t,
+ h: Math.floor(h * 0.14),
+ });
+
+ ball.x = startZone.x + startZone.w / 2;
+ ball.y = startZone.y + startZone.h / 2;
+ ball.vx = 0;
+ ball.vy = 0;
+}
+
+function resizeCanvas() {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ buildLayout();
+}
+
+resizeCanvas();
+
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ drawWalls();
+ drawZone(startZone, "rgba(110, 231, 183, 0.2)", "START");
+ drawZone(endZone, "rgba(239, 68, 68, 0.2)", "END");
+ drawGates();
+ drawBall();
+}
+
+function loop() {
+ updateBall();
+ draw();
+ requestAnimationFrame(loop);
+}
+
+document.addEventListener("keydown", (e) => {
+ if (status !== "playing") return;
+
+ if (e.key === "w" || e.key === "W") setGravity(0, -0.5, "up");
+ if (e.key === "s" || e.key === "S") setGravity(0, 0.5, "down");
+ if (e.key === "a" || e.key === "A") setGravity(-0.5, 0, "left");
+ if (e.key === "d" || e.key === "D") setGravity(0.5, 0, "right");
+});
+
+document.querySelectorAll(".gravity-btn").forEach((btn) => {
+ btn.addEventListener("click", () => {
+ if (status !== "playing") return;
+ const key = btn.dataset.key;
+ if (key === "w") setGravity(0, -0.4, "up");
+ if (key === "s") setGravity(0, 0.4, "down");
+ if (key === "a") setGravity(-0.4, 0, "left");
+ if (key === "d") setGravity(0.4, 0, "right");
+ });
+});
+
+window.addEventListener("resize", resizeCanvas);
+
+document.getElementById("startBtn").addEventListener("click", () => {
+ challengeMode = document.getElementById("challengeMode").checked;
+ status = "playing";
+ document.getElementById("status").textContent = "Playing";
+ document.getElementById("startBtn").style.display = "none";
+
+ if (challengeMode) {
+ document.getElementById("timer").classList.add("active");
+ startTimer();
+ }
+});
+
+function replay() {
+ document.getElementById("victoryModal").classList.remove("show");
+ resizeCanvas();
+ gates.forEach((g) => (g.passed = false));
+ score = 0;
+ document.getElementById("score").textContent = score;
+ document.getElementById("gates").textContent = `0/${gates.length}`;
+ status = "playing";
+ document.getElementById("status").textContent = "Playing";
+ document.getElementById("startBtn").style.display = "none";
+ setGravity(0, 0.5, "down");
+
+ challengeMode = document.getElementById("challengeMode").checked;
+ if (challengeMode) {
+ document.getElementById("timer").classList.add("active");
+ startTimer();
+ } else {
+ document.getElementById("timer").classList.remove("active");
+ if (timerInterval) {
+ clearInterval(timerInterval);
+ timerInterval = null;
+ }
+ }
+}
+
+document.getElementById("replayBtn").addEventListener("click", replay);
+
+canvas.addEventListener("dblclick", () => {
+ document.getElementById("victoryModal").classList.remove("show");
+ gates.forEach((g) => (g.passed = false));
+ score = 0;
+ document.getElementById("score").textContent = score;
+ document.getElementById("gates").textContent = `0/${gates.length}`;
+ status = "playing";
+ document.getElementById("status").textContent = "Playing";
+ ball.x = startZone.x + startZone.w / 2;
+ ball.y = startZone.y + startZone.h / 2;
+ ball.vx = 0;
+ ball.vy = 0;
+ setGravity(0, 0.5, "down");
+
+ if (challengeMode) {
+ startTimer();
+ }
+});
+
+updateGravityIndicator();
+loop();
diff --git a/projects/gravity-flip-pinball/style.css b/projects/gravity-flip-pinball/style.css
new file mode 100644
index 0000000..396cc75
--- /dev/null
+++ b/projects/gravity-flip-pinball/style.css
@@ -0,0 +1,265 @@
+:root {
+ --bg-gradient-start: #0b0b0e;
+ --bg-gradient-end: #121217;
+ --card: #17171c;
+ --text: #eef1f8;
+ --muted: #a6adbb;
+ --accent: #6ee7b7;
+ --border: #262631;
+}
+
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font-family: system-ui, -apple-system, sans-serif;
+ background: linear-gradient(
+ 180deg,
+ var(--bg-gradient-start),
+ var(--bg-gradient-end)
+ );
+ color: var(--text);
+ overflow: hidden;
+ width: 100vw;
+ height: 100vh;
+}
+
+.hud {
+ position: absolute;
+ bottom: 20px;
+ left: 20px;
+ background: rgba(23, 23, 28, 0.95);
+ padding: 16px 22px;
+ border-radius: 12px;
+ border: 1px solid var(--border);
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
+ z-index: 100;
+ min-width: 240px;
+}
+
+.hud h1 {
+ font-size: 1.4rem;
+ margin-bottom: 12px;
+ background: linear-gradient(135deg, var(--accent), #93c5fd);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.hud-row {
+ display: flex;
+ justify-content: space-between;
+ margin: 8px 0;
+ font-size: 0.95rem;
+}
+
+.hud-label {
+ color: var(--muted);
+}
+
+.hud-value {
+ font-weight: 600;
+ color: var(--accent);
+}
+
+.controls {
+ display: flex;
+ gap: 8px;
+ margin-top: 14px;
+}
+
+.btn {
+ flex: 1;
+ padding: 10px 16px;
+ border: none;
+ border-radius: 8px;
+ background: linear-gradient(135deg, var(--accent), #93c5fd);
+ color: #0b1020;
+ font-weight: 700;
+ font-size: 0.9rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ box-shadow: 0 4px 12px rgba(110, 231, 183, 0.3);
+}
+
+.btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 16px rgba(110, 231, 183, 0.4);
+}
+
+.btn:active {
+ transform: translateY(0);
+}
+
+.btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ transform: none;
+}
+
+.hint {
+ margin-top: 12px;
+ font-size: 0.8rem;
+ color: var(--muted);
+ line-height: 1.4;
+}
+
+.mode-toggle {
+ position: absolute;
+ top: 20px;
+ right: 20px;
+ background: rgba(23, 23, 28, 0.95);
+ padding: 12px;
+ border-radius: 12px;
+ border: 1px solid var(--border);
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
+ z-index: 100;
+}
+
+.mode-toggle label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ cursor: pointer;
+ font-size: 0.9rem;
+}
+
+.mode-toggle input {
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+}
+
+canvas {
+ display: block;
+ width: 100vw;
+ height: 100vh;
+}
+
+.gravity-indicator {
+ position: absolute;
+ bottom: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ display: flex;
+ gap: 8px;
+ background: rgba(23, 23, 28, 0.95);
+ padding: 12px 16px;
+ border-radius: 12px;
+ border: 1px solid var(--border);
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
+ z-index: 100;
+}
+
+.gravity-btn {
+ width: 40px;
+ height: 40px;
+ border: 2px solid var(--border);
+ border-radius: 8px;
+ background: rgba(15, 15, 18, 0.8);
+ color: var(--muted);
+ font-weight: 700;
+ font-size: 1rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.gravity-btn:hover {
+ border-color: var(--accent);
+ background: rgba(110, 231, 183, 0.1);
+}
+
+.gravity-btn.active {
+ border-color: var(--accent);
+ background: var(--accent);
+ color: #0b1020;
+ box-shadow: 0 0 20px rgba(110, 231, 183, 0.5);
+}
+
+.timer-display {
+ position: absolute;
+ top: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ background: rgba(23, 23, 28, 0.95);
+ padding: 12px 24px;
+ border-radius: 12px;
+ border: 1px solid var(--border);
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
+ font-size: 1.5rem;
+ font-weight: 700;
+ color: var(--accent);
+ display: none;
+ z-index: 100;
+}
+
+.timer-display.active {
+ display: block;
+}
+
+.timer-display.warning {
+ color: #fbbf24;
+ animation: pulse 0.5s ease-in-out infinite;
+}
+
+.timer-display.critical {
+ color: #ef4444;
+}
+
+@keyframes pulse {
+ 0%,
+ 100% {
+ transform: translateX(-50%) scale(1);
+ }
+ 50% {
+ transform: translateX(-50%) scale(1.05);
+ }
+}
+
+.victory-modal {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%) scale(0.9);
+ background: rgba(23, 23, 28, 0.98);
+ padding: 32px 48px;
+ border-radius: 16px;
+ border: 2px solid var(--accent);
+ box-shadow: 0 16px 64px rgba(0, 0, 0, 0.6);
+ text-align: center;
+ display: none;
+ z-index: 200;
+ opacity: 0;
+ transition: all 0.3s ease;
+}
+
+.victory-modal.show {
+ display: block;
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(1);
+}
+
+.victory-modal h2 {
+ font-size: 2rem;
+ margin-bottom: 16px;
+ background: linear-gradient(135deg, var(--accent), #93c5fd);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.victory-modal p {
+ color: var(--muted);
+ margin-bottom: 24px;
+ font-size: 1.1rem;
+}
+
+.victory-modal .btn {
+ margin: 0 8px;
+}