Compare commits

...

3 commits

Author SHA1 Message Date
Dariusz Niemczyk 88f9aa1812
fix: mobile-nav navigation colors
Some checks failed
/ deploy (push) Failing after 2s
2025-02-13 02:31:05 +01:00
Dariusz Niemczyk 05bb49fbc8
fix: Fully optimize rendering of navigations
Some checks failed
/ deploy (push) Failing after 2s
Fallback to vanillaJS due to React taking a significant
amount of rendering time when we wanted to only update
two classes on the navigation bar.

In addition to that, turns out Firefox has a decade-old
bug related to history.replaceState that causes it to
leak memory. This is a known issue and it's not going
to be fixed.
Our solution is to just get rid of the history.replaceState
and not update the URL when you're scrolling.

Tough luck, but hey, not gonna fix a decade old bug for a
simple throw-away website.
2025-02-13 02:09:47 +01:00
Dariusz Niemczyk 3b1bbdf67c
feat: add async rendering to hopefully speed things up
Some checks failed
/ deploy (push) Failing after 2s
2025-02-13 00:57:06 +01:00
3 changed files with 110 additions and 68 deletions

View file

@ -2,19 +2,39 @@
import { Button } from "@/components/ui/button"
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
import { useColorSections } from "@/hooks/color-sections"
import type { Sections, translations } from "@/i18n/translations"
import { cn } from "@/lib/utils"
import { Menu } from "lucide-react"
import { useRef } from "react"
import { LanguageSelector } from "./ui/language-selector"
function NavContent({
t,
linksOrder
}: {
t: typeof translations.pl,
linksOrder: Array<Sections>
}) {
const parent = useRef<HTMLDivElement>(null);
useColorSections(parent);
return (
<nav className="flex flex-col gap-4 mt-8" ref={parent}>
{linksOrder.map((value) => (
<a key={value} href={`#${value}`} className="text-lg hover:text-primary transition-colors">
{t.nav[value]}
</a>
))}
<LanguageSelector />
</nav>
)
}
export function MobileNav({
t,
linksOrder,
activeSection
}: {
t: typeof translations.pl
linksOrder: Array<Sections>
activeSection: Sections
}) {
return (
<Sheet >
@ -24,20 +44,11 @@ export function MobileNav({
<span className="sr-only">{t.mobileNav.toggleMenu}</span>
</Button>
</SheetTrigger>
<SheetContent side="right" className="w-[80vw] sm:w-[385px]">
<SheetContent side="right" className="w-[80vw] sm:w-[385px] z-max">
<SheetHeader>
<SheetTitle>{t.mobileNav.menu}</SheetTitle>
</SheetHeader>
<nav className="flex flex-col gap-4 mt-8">
{linksOrder.map((value) => (
<a key={value} href={`#${value}`} className={cn("text-lg hover:text-primary transition-colors", {
'text-primary': activeSection === value
})}>
{t.nav[value]}
</a>
))}
<LanguageSelector />
</nav>
<NavContent t={t} linksOrder={linksOrder} />
</SheetContent>
</Sheet>
)

View file

@ -1,9 +1,10 @@
"use client"
import { Button } from "@/components/ui/button"
import { Sections, type translations } from "@/i18n/translations"
import { useColorSections } from "@/hooks/color-sections"
import { type translations } from "@/i18n/translations"
import { cn } from "@/lib/utils"
import { MoonIcon, SunIcon } from "lucide-react"
import { useEffect, useState } from "react"
import { useRef } from "react"
import { MobileNav } from "./mobile-nav"
import { useTheme } from "./providers"
import { LanguageSelector } from "./ui/language-selector"
@ -25,50 +26,8 @@ export function Nav({
t: typeof translations.pl
}) {
const { theme, setTheme } = useTheme()
const [activeSection, setActiveSection] = useState<Sections>("about")
useEffect(() => {
const options = {
root: null,
rootMargin: "-10px",
threshold: 0.5, // Adjust the visibility threshold as needed
};
let timeout: NodeJS.Timeout | null = null;
const observer = new IntersectionObserver((entries) => {
if (timeout) {
clearTimeout(timeout);
}
entries.forEach(entry => {
const target = entry.target.id as keyof (typeof translations.pl)["nav"]
if (entry.isIntersecting) {
setActiveSection(target);
if (window.location.hash !== `#${target}` && history.replaceState) {
timeout = setTimeout(() => {
history.replaceState(null, "", `#${target}`)
}, 150)
}
}
});
}, options);
const sections = linksOrder.map(value => document.getElementById(value));
sections.forEach(section => {
if (section) {
observer.observe(section);
}
});
return () => {
sections.forEach(section => {
if (section) {
observer.unobserve(section);
}
});
};
}, []);
const parent = useRef<HTMLDivElement>(null);
useColorSections(parent);
return (
<nav className="fixed top-0 left-0 right-0 backdrop-blur-xs bg-background/40 border-b z-[10000]">
@ -82,19 +41,16 @@ export function Nav({
</div>
<div className="flex items-center">
{/* Desktop Navigation */}
<div className="hidden md:flex md:items-center md:gap-4 lg:gap-8">
<div className="hidden md:flex md:items-center md:gap-4 lg:gap-8" ref={parent}>
{linksOrder.map((value) => (
<a
key={value}
href={`#${value}`}
className={cn("text-sm md:text-md hover:text-primary transition-colors relative group", {
'text-primary': activeSection === value
})}
className="text-sm md:text-md hover:text-primary transition-colors relative group will-change-[color]"
>
{t.nav[value]}
<span className={cn("absolute inset-x-0 -bottom-1 h-0.5 bg-primary transform scale-x-0 group-hover:scale-x-100 transition-transform", {
'scale-x-100': activeSection === value
<span data-sub={value} className={cn("absolute inset-x-0 -bottom-1 h-0.5 bg-primary transform scale-x-0 group-hover:scale-x-100 transition-transform will-change-transform", {
})} />
</a>
))}
@ -113,7 +69,7 @@ export function Nav({
{/* Mobile Navigation */}
<div className="md:hidden ml-2">
<MobileNav t={t} linksOrder={linksOrder} activeSection={activeSection} />
<MobileNav t={t} linksOrder={linksOrder} />
</div>
</div>
</div>

View file

@ -0,0 +1,75 @@
import { Sections } from "@/i18n/translations";
import { useLayoutEffect, useRef } from "react";
export const linksOrder: Array<Sections> = [
"hero",
"about",
"where",
"when",
"tickets",
"accommodation",
"food",
"contact",
]
export function useColorSections(parent: React.RefObject<HTMLDivElement | null>) {
const previous = useRef<Sections>(linksOrder[0])
useLayoutEffect(() => {
if (parent.current === null) return;
const options = {
root: null,
rootMargin: "-10px",
threshold: 0.5, // Adjust the visibility threshold as needed
};
const sections = linksOrder.map(value => document.getElementById(value));
const subs = linksOrder.reduce((acc, value) => {
acc[value] = parent.current!.querySelector('[data-sub="' + value + '"]')!;
return acc;
}, {} as Record<Sections, HTMLAnchorElement>);
const links = linksOrder.reduce((acc, value) => {
acc[value] = parent.current!.querySelector('[href="#' + value + '"]')!;
return acc;
}, {} as Record<Sections, HTMLAnchorElement>);
console.log(links)
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
const target = entry.target.id as Sections
if (entry.isIntersecting) {
// FIXME: This seems to be VERY broken on firefox.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1250972
// It basically spikes up CPU usage to some enormous values just to update the hash, like WTF firefox.
// if (history.replaceState) {
// timeout = setTimeout(() => {
// history.replaceState(null, "", `#${target}`)
// }, 150)
// }
subs[previous.current]?.classList.remove('scale-x-100');
links[previous.current]?.classList.remove('text-primary');
previous.current = target;
subs[previous.current]?.classList.add('scale-x-100');
links[previous.current]?.classList.add('text-primary');
break;
}
}
}, options);
sections.forEach(section => {
if (section) {
observer.observe(section);
}
});
return () => {
observer.disconnect()
};
}, [parent]);
}