Fix mobile package card overflow, gate scroll hint on visibility, fix dead-end service links

Kachel-Mindesthoehe war fix auf 640px, dadurch ragten Pakete auf dem Handy
weit ueber den Bildschirm hinaus (Kundenfeedback). Jetzt 420px auf Mobile,
640px ab Desktop-Breite.

Der Mausrad-Hinweis lief bisher sofort beim Laden ab, oft bevor die
Kartenreihe ueberhaupt sichtbar gescrollt war - deshalb kam er beim Kunden
nicht an. Spielt jetzt erst ab, sobald die Reihe tatsaechlich im Blickfeld ist.

Leistungen-Karten ohne eigenen CTA (alles ausser Car-Hifi) hatten gar keinen
Link. Fallen jetzt auf das Kontaktformular zurueck statt ins Leere zu laufen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Maaxxs 2026-08-01 00:26:39 +02:00
parent 464164ff36
commit 0a96080427
5 changed files with 53 additions and 28 deletions

View file

@ -55,11 +55,14 @@ export default function Leistungen() {
<div className="p-6 pt-8"> <div className="p-6 pt-8">
<h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">{service.title}</h2> <h2 className="mb-2 font-semibold text-neutral-900 dark:text-white">{service.title}</h2>
<p className="mb-3 text-sm text-neutral-600 dark:text-neutral-300">{service.description}</p> <p className="mb-3 text-sm text-neutral-600 dark:text-neutral-300">{service.description}</p>
{service.cta_label && service.cta_url && ( {/* Karten ohne eigenen CTA (alles ausser Car-Hifi, das auf /fahrzeuge zeigt)
<Link to={service.cta_url} className="text-sm font-semibold text-brand-600 hover:underline dark:text-brand-400"> landen auf dem Kontaktformular statt ins Leere zu laufen. */}
{service.cta_label} <Link
to={service.cta_url || '/kontakt'}
className="text-sm font-semibold text-brand-600 hover:underline dark:text-brand-400"
>
{service.cta_label || t('leistungen.contact')}
</Link> </Link>
)}
</div> </div>
</Reveal> </Reveal>
))} ))}

View file

@ -122,12 +122,12 @@ function PackageCard({ pkg, tier, tierT, layout, bullets, formatPrice, contactUr
}} }}
className={ className={
layout === 'strip' layout === 'strip'
? 'relative flex min-h-[640px] grow shrink basis-[190px] min-w-[190px] max-w-[240px] snap-start flex-col overflow-hidden rounded-[18px] border shadow-xl shadow-neutral-900/20 dark:shadow-black/40 sm:basis-[210px]' ? 'relative flex min-h-[420px] grow shrink basis-[190px] min-w-[190px] max-w-[240px] snap-start flex-col overflow-hidden rounded-[18px] border shadow-xl shadow-neutral-900/20 dark:shadow-black/40 sm:min-h-[640px] sm:basis-[210px]'
: layout === 'coverflow' : layout === 'coverflow'
? // Feste Breite + snap-center: die 3D-Transformation (rotateY/scale/translateZ) ? // Feste Breite + snap-center: die 3D-Transformation (rotateY/scale/translateZ)
// setzt der Scroll-Handler in ModelPage direkt per style.transform. // setzt der Scroll-Handler in ModelPage direkt per style.transform.
'relative flex min-h-[640px] w-[230px] shrink-0 snap-center flex-col overflow-hidden rounded-[18px] border shadow-xl shadow-neutral-900/20 will-change-transform dark:shadow-black/40' 'relative flex min-h-[420px] w-[230px] shrink-0 snap-center flex-col overflow-hidden rounded-[18px] border shadow-xl shadow-neutral-900/20 will-change-transform dark:shadow-black/40 sm:min-h-[640px]'
: 'relative flex min-h-[640px] flex-col overflow-hidden rounded-[18px] border shadow-xl shadow-neutral-900/20 dark:shadow-black/40' : 'relative flex min-h-[420px] flex-col overflow-hidden rounded-[18px] border shadow-xl shadow-neutral-900/20 dark:shadow-black/40 sm:min-h-[640px]'
} }
> >
{/* Vollflaechiges Straßen-Glow-Bild der Preisstufe (Referenzdesign). */} {/* Vollflaechiges Straßen-Glow-Bild der Preisstufe (Referenzdesign). */}
@ -336,9 +336,12 @@ export default function ModelPage() {
const isTouch = window.matchMedia('(pointer: coarse)').matches; const isTouch = window.matchMedia('(pointer: coarse)').matches;
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reducedMotion) return undefined; if (reducedMotion) return undefined;
let timers = [];
const playHint = () => {
hintPlayedRef.current = true; hintPlayedRef.current = true;
const peek = Math.min(el.clientWidth * 0.4, 160); const peek = Math.min(el.clientWidth * 0.4, 160);
const timers = [ timers = [
setTimeout(() => el.scrollTo({ left: peek, behavior: 'smooth' }), 500), setTimeout(() => el.scrollTo({ left: peek, behavior: 'smooth' }), 500),
setTimeout(() => el.scrollTo({ left: 0, behavior: 'smooth' }), 1250), setTimeout(() => el.scrollTo({ left: 0, behavior: 'smooth' }), 1250),
]; ];
@ -346,7 +349,26 @@ export default function ModelPage() {
timers.push(setTimeout(() => setWheelHintVisible(true), 450)); timers.push(setTimeout(() => setWheelHintVisible(true), 450));
timers.push(setTimeout(() => setWheelHintVisible(false), 1700)); timers.push(setTimeout(() => setWheelHintVisible(false), 1700));
} }
return () => timers.forEach(clearTimeout); };
// Kundenfeedback: der Hinweis lief bisher direkt beim Laden ab, oft bevor die
// Kartenreihe ueberhaupt in den sichtbaren Bereich gescrollt war - dann hat ihn
// niemand gesehen. Jetzt erst abspielen, wenn die Reihe wirklich im Blick ist.
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
observer.disconnect();
playHint();
}
},
{ threshold: 0.35 },
);
observer.observe(el);
return () => {
observer.disconnect();
timers.forEach(clearTimeout);
};
}, [layout, data]); }, [layout, data]);
// Mausrad ueber der Kartenreihe scrollt horizontal (Kundenfeedback: die Scroll- // Mausrad ueber der Kartenreihe scrollt horizontal (Kundenfeedback: die Scroll-

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -43,9 +43,9 @@
"sameAs": ["https://www.youtube.com/@hifiplanet2812"] "sameAs": ["https://www.youtube.com/@hifiplanet2812"]
} }
</script> </script>
<script type="module" crossorigin src="/assets/index-DsWWCZq0.js"></script> <script type="module" crossorigin src="/assets/index-DO8EIr5z.js"></script>
<link rel="modulepreload" crossorigin href="/assets/lucide-icons-66ioQXWI.js"> <link rel="modulepreload" crossorigin href="/assets/lucide-icons-66ioQXWI.js">
<link rel="stylesheet" crossorigin href="/assets/index-B83Ts70I.css"> <link rel="stylesheet" crossorigin href="/assets/index-DK8bkdcK.css">
</head> </head>
<body class="bg-white text-slate-900 dark:bg-slate-950 dark:text-slate-100"> <body class="bg-white text-slate-900 dark:bg-slate-950 dark:text-slate-100">
<div id="root"></div> <div id="root"></div>