Add a one-time visual hint for wheel-scrolling the tile row
The existing peek animation (row nudges right and back on first load)
only demonstrated that the row moves, not how to move it - ambiguous
now that mouse-drag is gone. On non-touch devices it's paired with a
small pill ("Mit dem Mausrad blättern" / "Scroll to browse", mouse icon
flanked by chevrons) that fades in around the peek and out ~1.2s later,
floating centered over the row at a z-index above even the frontmost
coverflow card. Touch devices keep just the peek, since swiping is
already self-explanatory there. Plays once per page load, gated behind
the same reduced-motion check as the peek itself.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
eba15ef52b
commit
b1b66f1813
9 changed files with 124 additions and 95 deletions
|
|
@ -151,6 +151,7 @@ export default {
|
|||
featuredBadge: 'Empfohlen',
|
||||
moreBullets: (n) => (n === 1 ? '+ 1 weitere Leistung' : `+ ${n} weitere Leistungen`),
|
||||
lessBullets: 'Weniger anzeigen',
|
||||
wheelHint: 'Mit dem Mausrad blättern',
|
||||
},
|
||||
leistungen: {
|
||||
metaTitle: 'Leistungen',
|
||||
|
|
|
|||
|
|
@ -151,6 +151,7 @@ export default {
|
|||
featuredBadge: 'Recommended',
|
||||
moreBullets: (n) => (n === 1 ? '+ 1 more feature' : `+ ${n} more features`),
|
||||
lessBullets: 'Show less',
|
||||
wheelHint: 'Scroll to browse',
|
||||
},
|
||||
leistungen: {
|
||||
metaTitle: 'Services',
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import usePageMeta from '../../hooks/usePageMeta.js';
|
|||
import MaintenanceNotice from '../../components/MaintenanceNotice.jsx';
|
||||
import MaintenanceBypassBanner from '../../components/MaintenanceBypassBanner.jsx';
|
||||
import DynamicIcon from '../../components/DynamicIcon.jsx';
|
||||
import { ChevronLeft, ChevronRight, Mouse } from 'lucide-react';
|
||||
import { useMaintenance } from '../../context/MaintenanceContext.jsx';
|
||||
import { useLanguage } from '../../context/LanguageContext.jsx';
|
||||
import { useSiteSettings } from '../../context/SiteSettingsContext.jsx';
|
||||
|
|
@ -271,6 +272,7 @@ export default function ModelPage() {
|
|||
const trackRef = useRef(null);
|
||||
const thumbRef = useRef(null);
|
||||
const hintPlayedRef = useRef(false);
|
||||
const [wheelHintVisible, setWheelHintVisible] = useState(false);
|
||||
const formatPrice = (value) =>
|
||||
new Intl.NumberFormat(language === 'de' ? 'de-DE' : 'en-US', { style: 'currency', currency: 'EUR' }).format(value);
|
||||
|
||||
|
|
@ -321,24 +323,30 @@ export default function ModelPage() {
|
|||
// Scroll-Leiste; Touch-Geraete behalten ohnehin das native, fluessige
|
||||
// Wisch-Scrolling der scroll-snap-Kartenreihe.
|
||||
//
|
||||
// Stattdessen: einmaliger kurzer Wisch-Hinweis beim ersten Laden auf Touch-Geraeten
|
||||
// (Kundenwunsch) - die Reihe faehrt kurz ein Stueck nach rechts und wieder zurueck,
|
||||
// damit sofort klar ist, dass sich die Kacheln wischen lassen. Nur einmal pro
|
||||
// Seitenaufruf, nur auf echten Touch-Geraeten, nicht bei reduzierter Bewegung.
|
||||
// Stattdessen: einmaliger kurzer Wisch-Hinweis beim ersten Laden (Kundenwunsch) -
|
||||
// die Reihe faehrt kurz ein Stueck nach rechts und wieder zurueck, damit sofort
|
||||
// klar ist, dass sich die Kacheln wischen lassen. Nur einmal pro Seitenaufruf,
|
||||
// nicht bei reduzierter Bewegung. Auf Touch-Geraeten reicht die Bewegung allein
|
||||
// (Wischen ist selbsterklaerend); auf Geraeten mit Maus blendet zusaetzlich kurz
|
||||
// ein Mausrad-Icon mit Pfeilen ein, weil die Ausweich-Bewegung allein offen liesse,
|
||||
// WIE man scrollt (Ziehen mit der Maus gibt es seit Kundenfeedback nicht mehr).
|
||||
useEffect(() => {
|
||||
const el = scrollerRef.current;
|
||||
if (layout === 'grid' || !el || hintPlayedRef.current) return undefined;
|
||||
const isTouch = window.matchMedia('(pointer: coarse)').matches;
|
||||
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
if (!isTouch || reducedMotion) return undefined;
|
||||
if (reducedMotion) return undefined;
|
||||
hintPlayedRef.current = true;
|
||||
const peek = Math.min(el.clientWidth * 0.4, 160);
|
||||
const t1 = setTimeout(() => el.scrollTo({ left: peek, behavior: 'smooth' }), 500);
|
||||
const t2 = setTimeout(() => el.scrollTo({ left: 0, behavior: 'smooth' }), 1250);
|
||||
return () => {
|
||||
clearTimeout(t1);
|
||||
clearTimeout(t2);
|
||||
};
|
||||
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);
|
||||
}, [layout, data]);
|
||||
|
||||
// Mausrad ueber der Kartenreihe scrollt horizontal (Kundenfeedback: die Scroll-
|
||||
|
|
@ -589,7 +597,7 @@ export default function ModelPage() {
|
|||
// bleiben dunkel (Referenzdesign), wodurch sie auf heller Flaeche als eigenstaendige
|
||||
// "Karten" wirken. Der Scrollbalken-Stil unten passt sich per dark: mit an.
|
||||
<section className="py-10 sm:py-14" style={{ fontFamily: "'Barlow', sans-serif" }}>
|
||||
<div className={layout === 'grid' ? 'mx-auto max-w-6xl px-4 sm:px-6' : 'mx-auto max-w-[1880px] px-4 sm:px-6'}>
|
||||
<div className={layout === 'grid' ? 'mx-auto max-w-6xl px-4 sm:px-6' : 'relative mx-auto max-w-[1880px] px-4 sm:px-6'}>
|
||||
<div
|
||||
ref={layout === 'grid' ? undefined : scrollerRef}
|
||||
className={
|
||||
|
|
@ -628,6 +636,25 @@ export default function ModelPage() {
|
|||
})}
|
||||
</div>
|
||||
|
||||
{/* Mausrad-Hinweis: einmaliger, kurz ein- und ausblendender Pill mittig ueber
|
||||
der Reihe, zeitlich an die Ausweich-Bewegung oben gekoppelt (Hint-Effekt).
|
||||
z-[200] haelt ihn ueber den Coverflow-Karten (die bis z-index 100 reichen). */}
|
||||
{layout !== 'grid' && (
|
||||
<div
|
||||
className={`pointer-events-none absolute inset-x-0 top-1/2 z-[200] flex -translate-y-1/2 justify-center transition-opacity duration-300 ${
|
||||
wheelHintVisible ? 'opacity-100' : 'opacity-0'
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div className="flex items-center gap-2 rounded-full border border-white/10 bg-neutral-900/85 px-4 py-2 text-xs font-medium text-white shadow-lg backdrop-blur-sm">
|
||||
<ChevronLeft className="h-3.5 w-3.5 text-brand-400" />
|
||||
<Mouse className="h-4 w-4 text-brand-400" />
|
||||
<ChevronRight className="h-3.5 w-3.5 text-brand-400" />
|
||||
<span>{t('modelPage.wheelHint')}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Eigene Scroll-Leiste: zentrierter Track, markengruener leuchtender Daumen.
|
||||
Ziehbar + klickbar (Logik im Scrollbar-Effekt); blendet aus, wenn alle
|
||||
Karten ohne Scrollen passen. touch-action none fuer sauberes Pointer-Ziehen. */}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
78
hifi/assets/index-DQAs_YfT.js
Normal file
78
hifi/assets/index-DQAs_YfT.js
Normal file
File diff suppressed because one or more lines are too long
1
hifi/assets/index-wBHRPmjw.css
Normal file
1
hifi/assets/index-wBHRPmjw.css
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -8766,4 +8766,4 @@ function IE(t,r){for(var _=0;_<r.length;_++){const a=r[_];if(typeof a!="string"&
|
|||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const wE=[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["line",{x1:"21",x2:"16.65",y1:"21",y2:"16.65",key:"13gj7c"}],["line",{x1:"8",x2:"14",y1:"11",y2:"11",key:"durymu"}]],bPe=o("zoom-out",wE),gPe=Object.freeze(Object.defineProperty({__proto__:null,__iconNode:wE,default:bPe},Symbol.toStringTag,{value:"Module"}));export{AR as C,OPe as D,EW as E,_ae as M,Lce as P,MPe as R,s4e as S,vge as U,wie as a,Lae as b,oS as c,PPe as i,p as r};
|
||||
*/const wE=[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["line",{x1:"21",x2:"16.65",y1:"21",y2:"16.65",key:"13gj7c"}],["line",{x1:"8",x2:"14",y1:"11",y2:"11",key:"durymu"}]],bPe=o("zoom-out",wE),gPe=Object.freeze(Object.defineProperty({__proto__:null,__iconNode:wE,default:bPe},Symbol.toStringTag,{value:"Module"}));export{h$ as C,OPe as D,EW as E,Wne as M,Lce as P,MPe as R,s4e as S,vge as U,p$ as a,_ae as b,wie as c,AR as d,Lae as e,oS as f,PPe as i,p as r};
|
||||
|
|
@ -43,9 +43,9 @@
|
|||
"sameAs": ["https://www.youtube.com/@hifiplanet2812"]
|
||||
}
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-D-bbfDHb.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/lucide-icons-CnpfS6R8.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-9666bnLb.css">
|
||||
<script type="module" crossorigin src="/assets/index-DQAs_YfT.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/lucide-icons-66ioQXWI.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-wBHRPmjw.css">
|
||||
</head>
|
||||
<body class="bg-white text-slate-900 dark:bg-slate-950 dark:text-slate-100">
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue