feat: add footer component with zoom in/out functionality

This commit is contained in:
2025-12-10 10:31:44 +08:00
parent 81f84a97f1
commit 20df4a9e8e
2 changed files with 138 additions and 3 deletions

View File

@@ -0,0 +1,99 @@
---
---
<footer class="footer">
<div class="controls">
<button id="zoom-out" aria-label="Zoom Out">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</button>
<button id="zoom-in" aria-label="Zoom In">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</button>
</div>
</footer>
<style>
.footer {
position: fixed;
bottom: 2rem;
right: 2rem;
z-index: 50;
pointer-events: none; /* Allow clicks to pass through empty space */
}
.controls {
display: flex;
gap: 1rem;
pointer-events: auto;
}
button {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
padding: 0;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.18);
background: linear-gradient(
145deg,
rgba(255, 255, 255, 0.06),
rgba(255, 255, 255, 0.03)
);
backdrop-filter: blur(16px);
box-shadow:
0 0 0 1px rgba(255, 255, 255, 0.08),
0 4px 12px rgba(0, 0, 0, 0.2);
color: rgba(255, 255, 255, 0.9);
cursor: pointer;
transition:
transform 0.1s ease,
background 0.2s ease;
}
button:hover {
background: linear-gradient(
145deg,
rgba(255, 255, 255, 0.12),
rgba(255, 255, 255, 0.06)
);
transform: translateY(-2px);
}
button:active {
transform: translateY(0);
}
button svg {
width: 24px;
height: 24px;
opacity: 0.8;
}
</style>

View File

@@ -2,6 +2,7 @@
import titleSvg from "../assets/title.svg?raw";
import Navbar from "./Navbar.astro";
import Particles from "./Particles.astro";
import Footer from "./Footer.astro";
---
<Navbar />
@@ -9,6 +10,7 @@ import Particles from "./Particles.astro";
<div class="halo"></div>
<div class="grain"></div>
<div id="marble-field"></div>
<Footer />
<div class="content">
<div class="title-card">
@@ -52,12 +54,13 @@ import Particles from "./Particles.astro";
const marbles: Marble[] = [];
let fieldWidth = window.innerWidth;
let fieldHeight = window.innerHeight;
let zoomLevel = 1.0;
const getMarbleSize = () => {
const base = 192;
const quarter = Math.min(window.innerWidth, window.innerHeight) * 0.25;
const capped = Math.min(base, quarter || base);
return Math.max(96, Math.floor(capped));
return Math.max(96, Math.floor(capped)) * zoomLevel;
};
const createMarble = (entry: {
@@ -207,6 +210,36 @@ import Particles from "./Particles.astro";
};
requestAnimationFrame(loop);
const updateMarbleSize = () => {
const size = getMarbleSize();
const radius = size / 2;
for (const m of marbles) {
m.radius = radius;
m.node.style.width = `${size}px`;
m.node.style.height = `${size}px`;
// Recalculate mass if desired, but not strictly necessary for visual zoom
m.mass = radius * radius * 0.01 + 1;
}
};
const zoomInBtn = document.getElementById("zoom-in");
const zoomOutBtn = document.getElementById("zoom-out");
if (zoomInBtn) {
zoomInBtn.addEventListener("click", () => {
zoomLevel = Math.min(zoomLevel + 0.1, 2.0); // Cap max zoom
updateMarbleSize();
});
}
if (zoomOutBtn) {
zoomOutBtn.addEventListener("click", () => {
zoomLevel = Math.max(zoomLevel - 0.1, 0.5); // Cap min zoom
updateMarbleSize();
});
}
}
</script>
@@ -350,8 +383,11 @@ import Particles from "./Particles.astro";
:global(.marble-wrapper) {
position: absolute;
will-change: transform;
transition: opacity 1s ease;
will-change: transform, width, height;
transition:
opacity 1s ease,
width 0.3s ease,
height 0.3s ease;
}
:global(.marble-wrapper:hover) {