rm shine + perf

This commit is contained in:
Shine1i 2026-02-15 19:41:15 +01:00
commit ddffa2bbdc
4 changed files with 39 additions and 94 deletions

View file

@ -29,7 +29,7 @@ export function Navbar() {
const [logoHovered, setLogoHovered] = useState(false);
return (
<header data-tour="navbar" className="top-0 z-40 h-16 w-full">
<header className="top-0 z-40 h-16 w-full">
<div className="mx-auto flex h-full max-w-7xl items-center justify-between px-6">
{/* Left: logo */}
<div
@ -63,7 +63,10 @@ export function Navbar() {
</div>
{/* Center: pill nav */}
<nav className="flex items-center rounded-full border border-border bg-card p-1 ring-1 ring-foreground/5">
<nav
data-tour="navbar"
className="flex items-center rounded-full border border-border bg-card p-1 ring-1 ring-foreground/5"
>
{NAV_ITEMS.map((item) => {
const active = pathname === item.href;
if (!item.enabled) {

View file

@ -1,61 +0,0 @@
import * as React from "react"
import { cn } from "@/lib/utils"
interface ShineBorderProps extends React.HTMLAttributes<HTMLDivElement> {
/**
* Width of the border in pixels
* @default 1
*/
borderWidth?: number
/**
* Duration of the animation in seconds
* @default 14
*/
duration?: number
/**
* Color of the border, can be a single color or an array of colors
* @default "#000000"
*/
shineColor?: string | string[]
}
/**
* Shine Border
*
* An animated background border effect component with configurable properties.
*/
export function ShineBorder({
borderWidth = 1,
duration = 14,
shineColor = "#000000",
className,
style,
...props
}: ShineBorderProps) {
return (
<div
style={
{
"--border-width": `${borderWidth}px`,
"--duration": `${duration}s`,
backgroundImage: `radial-gradient(transparent,transparent, ${
Array.isArray(shineColor) ? shineColor.join(",") : shineColor
},transparent,transparent)`,
backgroundSize: "300% 300%",
mask: `linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)`,
WebkitMask: `linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)`,
WebkitMaskComposite: "xor",
maskComposite: "exclude",
padding: "var(--border-width)",
...style,
} as React.CSSProperties
}
className={cn(
"motion-safe:animate-shine pointer-events-none absolute inset-0 size-full rounded-[inherit] will-change-[background-position]",
className
)}
{...props}
/>
)
}

View file

@ -1,5 +1,4 @@
import { Button } from "@/components/ui/button";
import { ShineBorder } from "@/components/ui/shine-border";
import { cn } from "@/lib/utils";
import { HugeiconsIcon } from "@hugeicons/react";
import {
@ -174,6 +173,8 @@ export function GuidedTour({
});
const cardRef = useRef<HTMLDivElement>(null);
const closeLockRef = useRef(false);
const rafRef = useRef<number | null>(null);
const lastRectRef = useRef<Rect | null>(null);
const step = steps[idx] ?? null;
const total = steps.length;
@ -181,8 +182,9 @@ export function GuidedTour({
const spotlightRect = useMemo(() => {
if (!targetRect || !vw || !vh) return null;
return padded(targetRect, 14, vw, vh);
}, [targetRect, vw, vh]);
const pad = step?.target === "navbar" ? 8 : 14;
return padded(targetRect, pad, vw, vh);
}, [step?.target, targetRect, vw, vh]);
useEffect(() => {
if (!open) return;
@ -215,23 +217,45 @@ export function GuidedTour({
let raf = 0;
let t = 0;
const update = () => {
const read = () => {
const r = el.getBoundingClientRect();
setTargetRect(toRect(r));
const next = toRect(r);
const prev = lastRectRef.current;
if (
!prev ||
Math.abs(prev.x - next.x) > 0.5 ||
Math.abs(prev.y - next.y) > 0.5 ||
Math.abs(prev.w - next.w) > 0.5 ||
Math.abs(prev.h - next.h) > 0.5
) {
lastRectRef.current = next;
setTargetRect(next);
}
};
const schedule = () => {
if (rafRef.current != null) return;
rafRef.current = window.requestAnimationFrame(() => {
rafRef.current = null;
read();
});
};
raf = window.requestAnimationFrame(update);
t = window.setTimeout(update, 240);
raf = window.requestAnimationFrame(read);
t = window.setTimeout(schedule, 240);
const ro = new ResizeObserver(() => update());
const ro = new ResizeObserver(() => schedule());
ro.observe(el);
window.addEventListener("scroll", update, { capture: true, passive: true });
window.addEventListener("scroll", schedule, { capture: true, passive: true });
return () => {
window.cancelAnimationFrame(raf);
window.clearTimeout(t);
ro.disconnect();
window.removeEventListener("scroll", update, true);
window.removeEventListener("scroll", schedule, true);
if (rafRef.current != null) {
window.cancelAnimationFrame(rafRef.current);
rafRef.current = null;
}
};
}, [open, step]);
@ -321,15 +345,6 @@ export function GuidedTour({
fontFamily: "'Figtree Variable', ui-sans-serif, sans-serif",
}}
>
<ShineBorder
borderWidth={1}
duration={12}
shineColor={[
"rgba(16,185,129,0.65)",
"rgba(34,211,238,0.65)",
"rgba(16,185,129,0.35)",
]}
/>
<div
className={cn(
"absolute z-10 size-3 rotate-45 rounded-[3px] bg-white/95 ring-1 ring-black/10",

View file

@ -242,18 +242,6 @@
background-position: calc(100% + var(--shiny-width)) 0;
}
}
--animate-shine: shine var(--duration) infinite linear;
@keyframes shine {
0% {
background-position: 0% 0%;
}
50% {
background-position: 100% 100%;
}
100% {
background-position: 0% 0%;
}
}
}
@layer base {