Fix: Carousel stays inside container for left alignment, breaks right only via negative margin-right. Fix contrast on mobile bottom nav (neutral-800, 10px).

This commit is contained in:
2026-06-23 02:02:39 +02:00
parent 5dc2a9fd37
commit 85d366cfe2
2 changed files with 17 additions and 23 deletions

View File

@@ -110,24 +110,17 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
<div className="container relative z-10"> <div className="container relative z-10">
<div className="absolute right-0 top-0 bottom-0 w-16 bg-gradient-to-l from-neutral-dark to-transparent z-20 md:hidden pointer-events-none" /> <div className="absolute right-0 top-0 bottom-0 w-16 bg-gradient-to-l from-neutral-dark to-transparent z-20 md:hidden pointer-events-none" />
{/* Pure CSS Full-Bleed Alignment Container */} {/* Carousel: stays inside container for left alignment, breaks right to viewport edge */}
<div style={{ '--offset': 'calc(50vw - 50%)' } as React.CSSProperties}> <div
<div ref={containerRef}
ref={containerRef} onMouseDown={onMouseDown}
onMouseDown={onMouseDown} onMouseLeave={onMouseLeave}
onMouseLeave={onMouseLeave} onMouseUp={onMouseUp}
onMouseUp={onMouseUp} onMouseMove={onMouseMove}
onMouseMove={onMouseMove} style={{ marginRight: 'calc(-50vw + 50%)' }}
style={{ className={`select-none flex gap-6 overflow-x-auto pb-8 pt-4 [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden ${isDragging ? 'cursor-grabbing snap-none' : 'cursor-grab snap-x snap-mandatory'}`}
marginLeft: 'calc(-1 * var(--offset))', >
marginRight: 'calc(-1 * var(--offset))', {references.map((ref, i) => {
paddingLeft: 'var(--offset)',
paddingRight: 'var(--offset)',
scrollPaddingLeft: 'var(--offset)',
}}
className={`select-none flex overflow-x-auto pb-8 pt-4 [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden ${isDragging ? 'cursor-grabbing snap-none' : 'cursor-grab snap-x snap-mandatory'}`}
>
{references.map((ref, i) => {
const imgSrc = ref.image const imgSrc = ref.image
? (typeof ref.image === 'string' ? ref.image : ref.image.url) ? (typeof ref.image === 'string' ? ref.image : ref.image.url)
: fallbacks[i % fallbacks.length]; : fallbacks[i % fallbacks.length];
@@ -139,7 +132,7 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
whileInView={{ opacity: 1, x: 0 }} whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: "-50px" }} viewport={{ once: true, margin: "-50px" }}
transition={{ delay: i * 0.1, duration: 1.0, ease: [0.16, 1, 0.3, 1] }} transition={{ delay: i * 0.1, duration: 1.0, ease: [0.16, 1, 0.3, 1] }}
className="flex-shrink-0 w-[320px] md:w-[480px] snap-start group pointer-events-auto mr-6" className="flex-shrink-0 w-[320px] md:w-[480px] snap-start group pointer-events-auto"
> >
<Link <Link
href={`/${locale}/referenzen#${ref.slug}`} href={`/${locale}/referenzen#${ref.slug}`}
@@ -173,7 +166,8 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
</motion.div> </motion.div>
); );
})} })}
</div> {/* Right padding spacer so last card doesn't clip at viewport edge */}
<div className="flex-shrink-0 w-6" aria-hidden="true" />
</div> </div>
</div> </div>

View File

@@ -130,7 +130,7 @@ export function MobileBottomNav({ navLinks, currentLocale }: MobileBottomNavProp
</motion.div> </motion.div>
<motion.span <motion.span
animate={{ y: isActive ? 0 : 2 }} animate={{ y: isActive ? 0 : 2 }}
className="text-[9px] font-bold mt-1 tracking-wide" className="text-[10px] font-bold mt-1 tracking-wide"
> >
{item.label} {item.label}
</motion.span> </motion.span>
@@ -144,7 +144,7 @@ export function MobileBottomNav({ navLinks, currentLocale }: MobileBottomNavProp
key={item.label} key={item.label}
onClick={() => setIsFlyoutOpen(!isFlyoutOpen)} onClick={() => setIsFlyoutOpen(!isFlyoutOpen)}
className={`relative flex flex-col items-center justify-center flex-1 h-[62px] transition-colors duration-300 select-none w-full focus:outline-none ${ className={`relative flex flex-col items-center justify-center flex-1 h-[62px] transition-colors duration-300 select-none w-full focus:outline-none ${
isActive ? 'text-primary' : 'text-neutral-600 hover:text-neutral-900' isActive ? 'text-primary' : 'text-neutral-800 hover:text-neutral-900'
}`} }`}
style={{ WebkitTapHighlightColor: 'transparent', touchAction: 'manipulation' }} style={{ WebkitTapHighlightColor: 'transparent', touchAction: 'manipulation' }}
> >
@@ -159,7 +159,7 @@ export function MobileBottomNav({ navLinks, currentLocale }: MobileBottomNavProp
href={mappedUrl} href={mappedUrl}
onClick={() => setIsFlyoutOpen(false)} onClick={() => setIsFlyoutOpen(false)}
className={`relative flex flex-col items-center justify-center flex-1 h-[62px] transition-colors duration-300 select-none ${ className={`relative flex flex-col items-center justify-center flex-1 h-[62px] transition-colors duration-300 select-none ${
isActive ? 'text-primary' : 'text-neutral-600 hover:text-neutral-900' isActive ? 'text-primary' : 'text-neutral-800 hover:text-neutral-900'
}`} }`}
style={{ WebkitTapHighlightColor: 'transparent', touchAction: 'manipulation' }} style={{ WebkitTapHighlightColor: 'transparent', touchAction: 'manipulation' }}
> >