diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 2c0044e..36a03db 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,12 +1,35 @@ import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; -import { backOut } from "svelte/easing"; +import { backOut, cubicInOut } from "svelte/easing"; import type { TransitionConfig } from "svelte/transition"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } +type WarpParams = { + duration?: number; + delay?: number; +}; + +export const warp = ( + node: Element, + params: WarpParams = {} +): TransitionConfig => { + return { + duration: params.duration ?? 400, + delay: params.delay ?? 0, + css: (t) => { + const eased = cubicInOut(t); + const blur = (1 - eased) * 4; + + return ` + filter: blur(${blur}px); + `; + } + }; +}; + type FlyAndScaleParams = { y?: number; x?: number; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index e6edbe7..391f4b9 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -18,6 +18,29 @@ let mobileMenuOpen = false; let showScrollTop = false; + // Navigation sliding background + let navItemElements: (HTMLAnchorElement | null)[] = []; + let activeIndicatorStyle = ''; + + $: if (navItemElements.length === navItems.length && BROWSER) { + updateActiveIndicator(); + } + + function updateActiveIndicator() { + const activeIndex = navItems.findIndex(item => $page.url.pathname === item.href); + if (activeIndex !== -1 && navItemElements[activeIndex]) { + const activeElement = navItemElements[activeIndex]; + const navContainer = activeElement?.parentElement; + if (activeElement && navContainer) { + const containerRect = navContainer.getBoundingClientRect(); + const activeRect = activeElement.getBoundingClientRect(); + const left = activeRect.left - containerRect.left; + const width = activeRect.width; + activeIndicatorStyle = `left: ${left}px; width: ${width}px;`; + } + } + } + // Reactive statement to check if win2000 theme is active $: isWin2000Theme = ($currentTheme === 'win2000'); @@ -27,6 +50,10 @@ // Close mobile menu when route changes $: if ($page.url.pathname) { mobileMenuOpen = false; + // Update active indicator when page changes + if (BROWSER) { + setTimeout(updateActiveIndicator, 0); + } } // Listen for boot animation triggers from terminal @@ -71,13 +98,23 @@ showScrollTop = window.scrollY > 300; // Show after 300px scroll }; + // Update active indicator on resize + const handleResize = () => { + updateActiveIndicator(); + }; + window.addEventListener('scroll', handleScroll, { passive: true }); + window.addEventListener('resize', handleResize); + + // Initial update + setTimeout(updateActiveIndicator, 0); } return () => { if (BROWSER) { window.removeEventListener('keydown', handleGlobalKeyDown); window.removeEventListener('scroll', handleScroll); + window.removeEventListener('resize', handleResize); } }; }); @@ -171,15 +208,22 @@