navigation bar finished

This commit is contained in:
UniverseBow 2026-03-15 12:40:12 -07:00
parent 63a3fda67c
commit b22976bf85
2 changed files with 70 additions and 51 deletions

View file

@ -11,28 +11,18 @@
<img src="banner.png" alt="banner" />
</div>
<!-- SVG filter definition — hidden, just provides the filter for Chrome -->
<svg id="liquid-glass-svg" xmlns="http://www.w3.org/2000/svg" style="position:absolute;width:0;height:0;overflow:hidden;">
<defs>
<filter id="liquid-glass-nav" x="0" y="0" width="100%" height="100%" color-interpolation-filters="sRGB">
<feImage id="nav-disp-img" result="disp_map" />
<feDisplacementMap in="SourceGraphic" in2="disp_map" id="nav-disp-filter" xChannelSelector="R" yChannelSelector="G" result="refracted" />
<feImage id="nav-spec-img" result="spec_map" />
<feBlend in="refracted" in2="spec_map" mode="screen" result="with_specular" />
<feComposite in="with_specular" in2="SourceGraphic" operator="atop" />
</filter>
<feImage id="nav-disp-img" result="disp_map" />
<feDisplacementMap in="SourceGraphic" in2="disp_map" id="nav-disp-filter" xChannelSelector="R" yChannelSelector="G" result="refracted" />
<feImage id="nav-spec-img" result="spec_map" />
<feBlend in="refracted" in2="spec_map" mode="screen" result="with_specular" />
<feComposite in="with_specular" in2="SourceGraphic" operator="atop" />
</filter>
<filter id="liquid-glass" x="0" y="0" width="100%" height="100%" color-interpolation-filters="sRGB">
<feImage id="displacement-map-img" result="disp_map" />
<feDisplacementMap
in="SourceGraphic"
in2="disp_map"
id="displacement-map-filter"
xChannelSelector="R"
yChannelSelector="G"
result="refracted"
/>
<feDisplacementMap in="SourceGraphic" in2="disp_map" id="displacement-map-filter" xChannelSelector="R" yChannelSelector="G" result="refracted" />
<feImage id="specular-map-img" result="spec_map" />
<feBlend in="refracted" in2="spec_map" mode="screen" result="with_specular" />
<feComposite in="with_specular" in2="SourceGraphic" operator="atop" />
@ -287,9 +277,15 @@ function applyLiquidGlass() {
// ─── Nav glass ────────────────────────────────────────────────────────────────
const NAV_TRANSITION_MS = 300; // Must match CSS transition duration
// Phase durations in ms — keep in sync with CSS transitions
const OPEN_WIDTH_MS = 250; // width/radius expand duration
const OPEN_DROPDOWN_MS = 300; // dropdown slide-down duration
const OPEN_DROPDOWN_DELAY = 220; // CSS delay before dropdown opens
const CLOSE_DROPDOWN_MS = 200; // dropdown collapse duration (no CSS delay)
const CLOSE_WIDTH_MS = 250; // width/radius collapse duration
const CLOSE_WIDTH_DELAY = 200; // wait for dropdown to finish before collapsing width
// Build and apply nav glass maps at a specific border-radius value
// Build and apply nav glass maps at a specific border-radius
function applyNavGlassAt(radius) {
const pill = document.getElementById('nav-pill');
const w = pill.offsetWidth;
@ -326,18 +322,13 @@ function applyNavGlassAt(radius) {
}
}
// Rebuild maps across the transition so refraction tracks the shape live
function animateNavGlass(startRadius, targetRadius) {
const steps = 12;
const interval = NAV_TRANSITION_MS / steps;
// Animate refraction maps across a time window
function scheduleNavGlassFrames(startRadius, targetRadius, delayMs, durationMs, steps = 14) {
for (let i = 1; i <= steps; i++) {
setTimeout(() => {
const progress = i / steps;
const eased = 1 - Math.pow(1 - progress, 3); // ease-out cubic
const radius = startRadius + (targetRadius - startRadius) * eased;
applyNavGlassAt(radius);
}, i * interval);
const t = i / steps;
const eased = 1 - Math.pow(1 - t, 3);
const radius = startRadius + (targetRadius - startRadius) * eased;
setTimeout(() => applyNavGlassAt(radius), delayMs + t * durationMs);
}
}
@ -369,14 +360,31 @@ const navBar = document.getElementById('nav-pill-bar');
navBar.addEventListener('click', () => {
const opening = !navPill.classList.contains('open');
navPill.classList.toggle('open');
// 999 = collapsed pill, 22 = expanded rounded rect (must match CSS)
animateNavGlass(opening ? 999 : 22, opening ? 22 : 999);
if (opening) {
// Phase 1: width expands (0 → OPEN_WIDTH_MS)
scheduleNavGlassFrames(999, 25, 0, OPEN_WIDTH_MS);
// Phase 2: dropdown falls (OPEN_DROPDOWN_DELAY → +OPEN_DROPDOWN_MS)
// refraction height grows as the pill gets taller — track it live
scheduleNavGlassFrames(25, 25, OPEN_DROPDOWN_DELAY, OPEN_DROPDOWN_MS);
// Final settle
setTimeout(applyNavGlass, OPEN_DROPDOWN_DELAY + OPEN_DROPDOWN_MS + 50);
} else {
// Phase 1: dropdown collapses (0 → CLOSE_DROPDOWN_MS)
scheduleNavGlassFrames(25, 25, 0, CLOSE_DROPDOWN_MS);
// Phase 2: width shrinks after dropdown finishes
scheduleNavGlassFrames(25, 999, CLOSE_WIDTH_DELAY, CLOSE_WIDTH_MS);
// Final settle
setTimeout(applyNavGlass, CLOSE_WIDTH_DELAY + CLOSE_WIDTH_MS + 50);
}
});
document.addEventListener('click', (e) => {
if (!navPill.contains(e.target) && navPill.classList.contains('open')) {
navPill.classList.remove('open');
animateNavGlass(22, 999);
scheduleNavGlassFrames(25, 25, 0, CLOSE_DROPDOWN_MS);
scheduleNavGlassFrames(25, 999, CLOSE_WIDTH_DELAY, CLOSE_WIDTH_MS);
setTimeout(applyNavGlass, CLOSE_WIDTH_DELAY + CLOSE_WIDTH_MS + 50);
}
});
</script>