From e21929532717f67a53a78835c9359eb52c2b9be6 Mon Sep 17 00:00:00 2001 From: MOH Date: Tue, 10 Mar 2026 05:23:27 +0100 Subject: [PATCH] Fix marquee behavior and unify marquee settings --- app/root/marquee/actions.ts | 52 +++++------ components/layout/site-footer.tsx | 3 - components/layout/stacked-marquee-section.tsx | 92 ++++++++++++++----- components/root/marquee-settings-form.tsx | 50 +++++----- lib/app-config.ts | 8 +- lib/marquee-settings.ts | 20 +++- 6 files changed, 134 insertions(+), 91 deletions(-) diff --git a/app/root/marquee/actions.ts b/app/root/marquee/actions.ts index 01e5f79..eea05ad 100644 --- a/app/root/marquee/actions.ts +++ b/app/root/marquee/actions.ts @@ -37,45 +37,35 @@ export async function saveMarqueeSettingsAction(formData: FormData) { ensureAdmin(); try { + const germanSettings = { + row1: String(formData.get("row1-de") ?? "").trim(), + row2: String(formData.get("row2-de") ?? "").trim(), + row3: String(formData.get("row3-de") ?? "").trim(), + row4: String(formData.get("row4-de") ?? "").trim(), + }; + const settings = { locales: { - ar: { - row1: String(formData.get("row1-ar") ?? "").trim(), - row2: String(formData.get("row2-ar") ?? "").trim(), - row3: String(formData.get("row3-ar") ?? "").trim(), - row4: String(formData.get("row4-ar") ?? "").trim(), - }, - en: { - row1: String(formData.get("row1-en") ?? "").trim(), - row2: String(formData.get("row2-en") ?? "").trim(), - row3: String(formData.get("row3-en") ?? "").trim(), - row4: String(formData.get("row4-en") ?? "").trim(), - }, - de: { - row1: String(formData.get("row1-de") ?? "").trim(), - row2: String(formData.get("row2-de") ?? "").trim(), - row3: String(formData.get("row3-de") ?? "").trim(), - row4: String(formData.get("row4-de") ?? "").trim(), - }, + ar: { ...germanSettings }, + en: { ...germanSettings }, + de: germanSettings, }, }; - for (const locale of routing.locales) { - if (!settings.locales[locale].row1) { - throw new Error(`Row 1 fuer ${locale} ist erforderlich.`); - } + if (!germanSettings.row1) { + throw new Error("Row 1 fuer de ist erforderlich."); + } - if (!settings.locales[locale].row2) { - throw new Error(`Row 2 fuer ${locale} ist erforderlich.`); - } + if (!germanSettings.row2) { + throw new Error("Row 2 fuer de ist erforderlich."); + } - if (!settings.locales[locale].row3) { - throw new Error(`Row 3 fuer ${locale} ist erforderlich.`); - } + if (!germanSettings.row3) { + throw new Error("Row 3 fuer de ist erforderlich."); + } - if (!settings.locales[locale].row4) { - throw new Error(`Row 4 fuer ${locale} ist erforderlich.`); - } + if (!germanSettings.row4) { + throw new Error("Row 4 fuer de ist erforderlich."); } await updateMarqueeSettings(settings); diff --git a/components/layout/site-footer.tsx b/components/layout/site-footer.tsx index 744a6d7..94da9fa 100644 --- a/components/layout/site-footer.tsx +++ b/components/layout/site-footer.tsx @@ -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 ( diff --git a/components/layout/stacked-marquee-section.tsx b/components/layout/stacked-marquee-section.tsx index 518bbeb..8ab7889 100644 --- a/components/layout/stacked-marquee-section.tsx +++ b/components/layout/stacked-marquee-section.tsx @@ -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(null); + const segmentRef = useRef(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 ( -
+
- {Array.from({ length: 2 }).map((_, copyIndex) => ( + {Array.from({ length: copyCount }).map((_, copyIndex) => (
0} > {items.map((item, itemIndex) => (
{rows.map((row, index) => ( -
+
Marquee Rows - Ein Feld pro Zeile. Ein Begriff pro Zeile oder mit Komma getrennt. + Nur Deutsch bearbeiten. Die Werte werden fuer alle Sprachen uebernommen. - - {localeMeta.map((locale) => ( -
-
-

{locale.label}

-

Vier Marquee Zeilen fuer diese Sprache.

-
- -
- {rowMeta.map((row) => ( -
- -