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">
<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>
{service.cta_label && service.cta_url && (
<Link to={service.cta_url} className="text-sm font-semibold text-brand-600 hover:underline dark:text-brand-400">
{service.cta_label}
</Link>
)}
{/* Karten ohne eigenen CTA (alles ausser Car-Hifi, das auf /fahrzeuge zeigt)
landen auf dem Kontaktformular statt ins Leere zu laufen. */}
<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>
</div>
</Reveal>
))}

View file

@ -122,12 +122,12 @@ function PackageCard({ pkg, tier, tierT, layout, bullets, formatPrice, contactUr
}}
className={
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'
? // Feste Breite + snap-center: die 3D-Transformation (rotateY/scale/translateZ)
// 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-[640px] flex-col overflow-hidden rounded-[18px] border shadow-xl shadow-neutral-900/20 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-[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). */}
@ -336,17 +336,39 @@ export default function ModelPage() {
const isTouch = window.matchMedia('(pointer: coarse)').matches;
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reducedMotion) return undefined;
hintPlayedRef.current = true;
const peek = Math.min(el.clientWidth * 0.4, 160);
const timers = [
setTimeout(() => el.scrollTo({ left: peek, behavior: 'smooth' }), 500),
setTimeout(() => el.scrollTo({ left: 0, behavior: 'smooth' }), 1250),
];
if (!isTouch) {
timers.push(setTimeout(() => setWheelHintVisible(true), 450));
timers.push(setTimeout(() => setWheelHintVisible(false), 1700));
}
return () => timers.forEach(clearTimeout);
let timers = [];
const playHint = () => {
hintPlayedRef.current = true;
const peek = Math.min(el.clientWidth * 0.4, 160);
timers = [
setTimeout(() => el.scrollTo({ left: peek, behavior: 'smooth' }), 500),
setTimeout(() => el.scrollTo({ left: 0, behavior: 'smooth' }), 1250),
];
if (!isTouch) {
timers.push(setTimeout(() => setWheelHintVisible(true), 450));
timers.push(setTimeout(() => setWheelHintVisible(false), 1700));
}
};
// 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]);
// 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"]
}
</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="stylesheet" crossorigin href="/assets/index-B83Ts70I.css">
<link rel="stylesheet" crossorigin href="/assets/index-DK8bkdcK.css">
</head>
<body class="bg-white text-slate-900 dark:bg-slate-950 dark:text-slate-100">
<div id="root"></div>