Fix marquee behavior and unify marquee settings
This commit is contained in:
@@ -2,7 +2,6 @@ import Link from "next/link";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
|
||||
import { Container } from "@/components/layout/container";
|
||||
import { RetroLedMarquee } from "@/components/layout/retro-led-marquee";
|
||||
import { getLocalizedPath } from "@/lib/locale";
|
||||
|
||||
const navItems = [
|
||||
@@ -21,7 +20,6 @@ export function SiteFooter({ isAdmin = false }: SiteFooterProps) {
|
||||
const locale = useLocale();
|
||||
const tNav = useTranslations("navigation");
|
||||
const tFooter = useTranslations("footer");
|
||||
const ledText = "MADE IN GERMANY BY MOH";
|
||||
|
||||
return (
|
||||
<footer className="border-t border-border bg-background">
|
||||
@@ -46,7 +44,6 @@ export function SiteFooter({ isAdmin = false }: SiteFooterProps) {
|
||||
<p className="text-xs text-muted-foreground/80">
|
||||
{tFooter("copyright", { year: new Date().getFullYear() })}
|
||||
</p>
|
||||
<RetroLedMarquee text={ledText} />
|
||||
</div>
|
||||
</Container>
|
||||
</footer>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { useLayoutEffect, useRef, useState } from "react";
|
||||
import { motion, useAnimationFrame, useMotionValue, useReducedMotion } from "framer-motion";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -86,33 +87,80 @@ const DEFAULT_ROWS: MarqueeRow[] = [
|
||||
|
||||
function MarqueeRowTrack({ items, direction, duration = 24 }: MarqueeRow) {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
const viewportRef = useRef<HTMLDivElement>(null);
|
||||
const segmentRef = useRef<HTMLDivElement>(null);
|
||||
const [segmentWidth, setSegmentWidth] = useState(0);
|
||||
const [copyCount, setCopyCount] = useState(3);
|
||||
const x = useMotionValue(0);
|
||||
const isReady = shouldReduceMotion || segmentWidth > 0;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const viewport = viewportRef.current;
|
||||
const segment = segmentRef.current;
|
||||
|
||||
if (!viewport || !segment) {
|
||||
return;
|
||||
}
|
||||
|
||||
const measure = () => {
|
||||
const nextSegmentWidth = segment.scrollWidth;
|
||||
const viewportWidth = viewport.clientWidth;
|
||||
|
||||
if (!nextSegmentWidth || !viewportWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSegmentWidth(nextSegmentWidth);
|
||||
setCopyCount(Math.max(3, Math.ceil(viewportWidth / nextSegmentWidth) + 2));
|
||||
x.set(direction === "right" ? -nextSegmentWidth : 0);
|
||||
};
|
||||
|
||||
measure();
|
||||
|
||||
const observer = new ResizeObserver(() => {
|
||||
measure();
|
||||
});
|
||||
|
||||
observer.observe(viewport);
|
||||
observer.observe(segment);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [direction, items, x]);
|
||||
|
||||
useAnimationFrame((_, delta) => {
|
||||
if (shouldReduceMotion || segmentWidth === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const distancePerMs = segmentWidth / (duration * 1000);
|
||||
const offset = distancePerMs * delta;
|
||||
const current = x.get();
|
||||
|
||||
if (direction === "right") {
|
||||
const next = current + offset;
|
||||
x.set(next >= 0 ? next - segmentWidth : next);
|
||||
return;
|
||||
}
|
||||
|
||||
const next = current - offset;
|
||||
x.set(next <= -segmentWidth ? next + segmentWidth : next);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden leading-[0.92]">
|
||||
<div ref={viewportRef} className="overflow-hidden leading-[0.92]">
|
||||
<motion.div
|
||||
className="flex w-max min-w-full flex-nowrap"
|
||||
animate={
|
||||
shouldReduceMotion
|
||||
? { x: 0 }
|
||||
: {
|
||||
x: direction === "left" ? ["0%", "-50%"] : ["-50%", "0%"],
|
||||
}
|
||||
}
|
||||
transition={
|
||||
shouldReduceMotion
|
||||
? undefined
|
||||
: {
|
||||
duration,
|
||||
ease: "linear",
|
||||
repeat: Number.POSITIVE_INFINITY,
|
||||
}
|
||||
}
|
||||
dir="ltr"
|
||||
className={cn("flex w-max flex-nowrap", !isReady && "opacity-0")}
|
||||
style={{ x }}
|
||||
>
|
||||
{Array.from({ length: 2 }).map((_, copyIndex) => (
|
||||
{Array.from({ length: copyCount }).map((_, copyIndex) => (
|
||||
<div
|
||||
key={`${direction}-${copyIndex}`}
|
||||
ref={copyIndex === 0 ? segmentRef : undefined}
|
||||
className="flex shrink-0 items-center gap-3 pr-3 sm:gap-4 sm:pr-4 lg:gap-5 lg:pr-5"
|
||||
aria-hidden={copyIndex === 1}
|
||||
aria-hidden={copyIndex > 0}
|
||||
>
|
||||
{items.map((item, itemIndex) => (
|
||||
<div
|
||||
@@ -156,7 +204,7 @@ export function StackedMarqueeSection({
|
||||
>
|
||||
<div className="relative flex flex-col gap-0 sm:gap-0 lg:gap-0">
|
||||
{rows.map((row, index) => (
|
||||
<div key={`${row.direction}-${index}`} className={index === 0 ? "" : "-mt-0.5 sm:-mt-1"}>
|
||||
<div key={`${row.direction}-${index}`}>
|
||||
<MarqueeRowTrack
|
||||
items={row.items}
|
||||
direction={row.direction}
|
||||
|
||||
Reference in New Issue
Block a user