Compare commits
6 Commits
v2.2.45-rc
...
v2.2.46
| Author | SHA1 | Date | |
|---|---|---|---|
| 343b484c6e | |||
| a6a8dbd134 | |||
| 8f2509c3fc | |||
| 41a3c10cf8 | |||
| 103e4e63ed | |||
| 34860cab18 |
@@ -42,10 +42,11 @@ export async function generateMetadata(props: {
|
||||
metadataBase: new URL(baseUrl),
|
||||
manifest: '/manifest.webmanifest',
|
||||
alternates: {
|
||||
canonical: `${baseUrl}/${locale}`,
|
||||
canonical: `/${locale}`,
|
||||
languages: {
|
||||
de: `${baseUrl}/de`,
|
||||
en: `${baseUrl}/en`,
|
||||
'de': `/de`,
|
||||
'en': `/en`,
|
||||
'x-default': `/de`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ const HomeCompetenceBentoGrid = nextDynamic(() => import('@/components/blocks/Co
|
||||
const HomeReferencesSlider = nextDynamic(() => import('@/components/blocks/ReferencesSlider').then(mod => mod.ReferencesSlider));
|
||||
const FaqBlock = nextDynamic(() => import('@/components/blocks/FaqBlock').then(mod => mod.FaqBlock));
|
||||
const CertificatesBlock = nextDynamic(() => import('@/components/blocks/CertificatesBlock').then(mod => mod.CertificatesBlock));
|
||||
import { HeroSection } from '@/components/blocks/HeroSection';
|
||||
const HeroSection = nextDynamic(() => import('@/components/blocks/HeroSection').then(mod => mod.HeroSection));
|
||||
import JsonLd from '@/components/JsonLd';
|
||||
|
||||
import { Button } from '@/components/ui/Button';
|
||||
|
||||
@@ -46,7 +46,21 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
const scrollLeftRef = React.useRef(0);
|
||||
const isDraggingRef = React.useRef(false);
|
||||
const [isDragging, setIsDragging] = React.useState(false);
|
||||
|
||||
|
||||
// Center the first card on mount so it appears in the middle with peek on both sides
|
||||
React.useEffect(() => {
|
||||
const el = containerRef.current;
|
||||
if (!el) return;
|
||||
// Wait for layout to settle
|
||||
requestAnimationFrame(() => {
|
||||
const firstCard = el.querySelector('[data-card]') as HTMLElement | null;
|
||||
if (!firstCard) return;
|
||||
const cardCenter = firstCard.offsetLeft + firstCard.offsetWidth / 2;
|
||||
const containerCenter = el.offsetWidth / 2;
|
||||
el.scrollLeft = cardCenter - containerCenter;
|
||||
});
|
||||
}, [references.length]);
|
||||
|
||||
const badge = props.badge || data?.badge || t('badge');
|
||||
const title = props.title || data?.title || t('title');
|
||||
const description = props.description || data?.description || t('description');
|
||||
@@ -97,20 +111,22 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
<section id="referenzen" className="pt-16 pb-8 md:py-24 lg:py-32 bg-neutral-dark text-white relative overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-neutral-dark via-neutral-900 to-neutral-dark z-0" />
|
||||
|
||||
<div className="container relative z-10 mb-12 flex flex-col md:flex-row justify-between items-start md:items-end gap-6">
|
||||
<div className="text-left">
|
||||
<div className="container relative z-10 mb-12 flex flex-col items-center justify-center text-center gap-6">
|
||||
<div>
|
||||
<h2 className="text-primary-light font-bold tracking-wider uppercase text-sm mb-3">{badge}</h2>
|
||||
<h3 className="font-heading text-3xl md:text-5xl font-extrabold">{title}</h3>
|
||||
</div>
|
||||
<div className="text-left max-w-3xl mx-auto md:mx-0 mb-8 md:mb-16">
|
||||
<div className="max-w-3xl mx-auto mb-4 md:mb-8 text-neutral-300">
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="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" />
|
||||
{/* Fade edges for peek effect */}
|
||||
<div className="absolute left-0 top-0 bottom-0 w-16 bg-gradient-to-r from-neutral-dark to-transparent z-20 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 pointer-events-none" />
|
||||
|
||||
{/* Carousel: breaks viewport edge on both sides but initial padding aligns with container */}
|
||||
{/* Carousel: centered first card with peek on both sides */}
|
||||
<LazyMotion features={domAnimation}>
|
||||
<div
|
||||
ref={containerRef}
|
||||
@@ -118,7 +134,7 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseUp={onMouseUp}
|
||||
onMouseMove={onMouseMove}
|
||||
className={`select-none flex gap-6 overflow-x-auto pb-8 pt-4 px-6 md:px-[max(2rem,calc((100vw-1280px)/2))] lg:px-[max(2.5rem,calc((100vw-1280px)/2))] [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden ${isDragging ? 'cursor-grabbing snap-none' : 'cursor-grab snap-x snap-mandatory'}`}
|
||||
className={`select-none flex gap-6 overflow-x-auto pb-8 pt-4 px-[calc(50vw-160px)] md:px-[calc(50vw-240px)] [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
|
||||
@@ -128,11 +144,12 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
return (
|
||||
<m.div
|
||||
key={ref.id}
|
||||
data-card
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
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"
|
||||
className="flex-shrink-0 w-[320px] md:w-[480px] snap-center group pointer-events-auto"
|
||||
>
|
||||
<Link
|
||||
href={`/${locale}/referenzen#${ref.slug}`}
|
||||
@@ -169,8 +186,7 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
</m.div>
|
||||
);
|
||||
})}
|
||||
{/* Right padding spacer so last card doesn't clip at viewport edge */}
|
||||
<div className="flex-shrink-0 w-6" aria-hidden="true" />
|
||||
|
||||
</div>
|
||||
</LazyMotion>
|
||||
</div>
|
||||
|
||||
@@ -37,7 +37,7 @@ export function TransitionLink({ children, href, onClick, transitionMessage, ...
|
||||
};
|
||||
|
||||
return (
|
||||
<Link href={href} onClick={handleClick} className={cn('cursor-pointer', props.className)} {...props}>
|
||||
<Link href={href} onClick={handleClick} prefetch={props.prefetch ?? false} className={cn('cursor-pointer', props.className)} {...props}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
"prepare": "husky",
|
||||
"preinstall": "npx only-allow pnpm"
|
||||
},
|
||||
"version": "2.2.44",
|
||||
"version": "2.2.46",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
|
||||
BIN
public/apple-touch-icon.png
Normal file
BIN
public/apple-touch-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user