diff --git a/public/images/paving-hero.webp b/public/images/paving-hero.webp new file mode 100644 index 0000000..8fd404c Binary files /dev/null and b/public/images/paving-hero.webp differ diff --git a/src/app/(frontend)/[locale]/[[...slug]]/page.tsx b/src/app/(frontend)/[locale]/[[...slug]]/page.tsx index fc97d8e..4997e28 100644 --- a/src/app/(frontend)/[locale]/[[...slug]]/page.tsx +++ b/src/app/(frontend)/[locale]/[[...slug]]/page.tsx @@ -23,7 +23,49 @@ const pageMetadata = createPageMetadata({ export async function generateMetadata({ params }: PageProps): Promise { const { locale, slug } = await params - return pageMetadata({ payload: await getCachedPayload(), locale, slug: slug ?? [] }) + const payload = await getCachedPayload() + const baseMeta = await pageMetadata({ payload, locale, slug: slug ?? [] }) + + const baseUrl = process.env.NEXT_PUBLIC_SERVER_URL || 'http://localhost:3000' + const fallbackOgImage = `${baseUrl}/images/paving-hero.webp` + + const ogImages = + baseMeta.openGraph?.images && (baseMeta.openGraph.images as unknown[]).length > 0 + ? baseMeta.openGraph.images + : [ + { + url: fallbackOgImage, + width: 1920, + height: 1080, + alt: 'POI Clean House - Czyszczenie Kostki Brukowej', + }, + ] + + return { + ...baseMeta, + title: baseMeta.title || 'POI Clean House | Profesjonalne Czyszczenie Kostki Brukowej', + description: + baseMeta.description || + 'Specjalistyczne mycie gorącowodne do 100°C, odplamianie oleju, piaskowanie i impregnacja hydrofobowa kostki brukowej. Sprawdź bezpłatną wycenę!', + openGraph: { + ...baseMeta.openGraph, + title: (baseMeta.title as string) || 'POI Clean House | Profesjonalne Czyszczenie Kostki Brukowej', + description: + baseMeta.description || + 'Specjalistyczne mycie gorącowodne do 100°C, odplamianie oleju, piaskowanie i impregnacja hydrofobowa kostki brukowej. Sprawdź bezpłatną wycenę!', + images: ogImages, + type: 'website', + siteName: 'POI Clean House', + }, + twitter: { + card: 'summary_large_image', + title: (baseMeta.title as string) || 'POI Clean House | Profesjonalne Czyszczenie Kostki Brukowej', + description: + baseMeta.description || + 'Specjalistyczne mycie gorącowodne do 100°C, odplamianie oleju, piaskowanie i impregnacja hydrofobowa kostki brukowej.', + images: [fallbackOgImage], + }, + } } export default async function Page({ params, searchParams }: PageProps) { diff --git a/src/blocks/Hero/Component.tsx b/src/blocks/Hero/Component.tsx index 1a28f56..df3c33e 100644 --- a/src/blocks/Hero/Component.tsx +++ b/src/blocks/Hero/Component.tsx @@ -1,4 +1,5 @@ import React from 'react' +import Image from 'next/image' import { Phone, ArrowRight, Check } from 'lucide-react' import { AnimateIn } from '@/components/AnimateIn' @@ -31,15 +32,20 @@ export function HeroBlockComponent(props: HeroBlockProps) { return (
- {/* Hero Banner Section with Background Photo */} -
+ {/* Hero Banner Section with Optimized Next.js LCP Image */} +
+ {/* Next.js LCP High Priority Image */} + Profesjonalne czyszczenie i renowacja kostki brukowej + {/* Dark gradient overlay for text readability */} -
+
{/* Compact Architectural Content Card */} diff --git a/src/components/AnimateIn.tsx b/src/components/AnimateIn.tsx index d2a7f0b..0c08d50 100644 --- a/src/components/AnimateIn.tsx +++ b/src/components/AnimateIn.tsx @@ -15,22 +15,30 @@ export function AnimateIn({ delay = 0, direction = 'up', className = '', - threshold = 0.15, + threshold = 0, }: AnimateInProps) { const [isVisible, setIsVisible] = useState(false) const ref = useRef(null) useEffect(() => { + // If IntersectionObserver is not supported, show immediately + if (typeof window === 'undefined' || !('IntersectionObserver' in window)) { + setIsVisible(true) + return + } + const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setIsVisible(true) - observer.unobserve(entry.target) + if (ref.current) { + observer.unobserve(ref.current) + } } }, { threshold, - rootMargin: '0px 0px -40px 0px', + rootMargin: '0px 0px 150px 0px', } ) @@ -47,20 +55,20 @@ export function AnimateIn({ }, [threshold]) const getTransform = () => { - if (isVisible) return 'translate3d(0, 0, 0) scale(1)' + if (isVisible) return 'translate3d(0, 0, 0)' switch (direction) { case 'up': - return 'translate3d(0, 32px, 0)' + return 'translate3d(0, 24px, 0)' case 'down': - return 'translate3d(0, -32px, 0)' + return 'translate3d(0, -24px, 0)' case 'left': - return 'translate3d(32px, 0, 0)' + return 'translate3d(24px, 0, 0)' case 'right': - return 'translate3d(-32px, 0, 0)' + return 'translate3d(-24px, 0, 0)' case 'none': - return 'scale(0.96)' + return 'scale(0.98)' default: - return 'translate3d(0, 32px, 0)' + return 'translate3d(0, 24px, 0)' } } @@ -68,11 +76,11 @@ export function AnimateIn({
diff --git a/src/components/CustomCookieBanner.tsx b/src/components/CustomCookieBanner.tsx index c60f55c..ee5eeab 100644 --- a/src/components/CustomCookieBanner.tsx +++ b/src/components/CustomCookieBanner.tsx @@ -28,80 +28,110 @@ export function CustomCookieBanner() { })) } - return ( -
-
- {!showSettings ? ( -
-
-
- -
-
-

- {texts.message}{' '} - {texts.privacyLink && ( - - {texts.privacyLink.label} - - )} -

-
-
+ const handleSave = () => { + savePreferences(choices) + setShowSettings(false) + } -
- - - + const handleAcceptAll = () => { + acceptAll() + setShowSettings(false) + } + + const handleRejectAll = () => { + rejectAll() + setShowSettings(false) + } + + return ( + <> + {/* 1. Compact Bottom Banner */} + {!showSettings && ( +
+
+
+
+
+ +
+
+

+ {texts.message}{' '} + {texts.privacyLink && ( + + {texts.privacyLink.label} + + )} +

+
+
+ +
+ + + +
- ) : ( -
-
+
+ )} + + {/* 2. Modal Window for Cookie Settings (Centered & Scrollable) */} + {showSettings && ( +
+
+ {/* Modal Header */} +
-

+

{texts.settingsTitle}

-
    + {/* Scrollable Categories List */} +
    {CONSENT_CATEGORIES.map((cat) => { const isChecked = cat === 'necessary' ? true : !!choices[cat] const isDisabled = cat === 'necessary' @@ -110,18 +140,18 @@ export function CustomCookieBanner() { if (!catData) return null return ( -
  • !isDisabled && toggle(cat)} - className={`flex items-start justify-between gap-4 p-3.5 border border-[#333333] bg-[#262626] ${ + className={`flex items-start justify-between gap-4 p-4 border border-[#333333] bg-[#262626] ${ !isDisabled ? 'cursor-pointer hover:border-[#FFED00]' : 'opacity-90' } transition-colors`} > -
    +
    {catData.title}
    -

    +

    {catData.description}

    @@ -140,12 +170,13 @@ export function CustomCookieBanner() { />
  • - +
) })} - +
-
+ {/* Modal Footer Buttons */} +
- )} -
-
+
+ )} + ) }