feat: implement UX patterns inspired by Greenback (Hero slider, Stats block, numbered Process cards)

This commit is contained in:
MichalC
2026-09-01 23:21:36 +02:00
parent 66c62f8a31
commit 441ae8f272
8 changed files with 259 additions and 61 deletions
+60 -13
View File
@@ -1,6 +1,5 @@
'use client'
import React from 'react'
import { motion } from 'framer-motion'
import React, { useState, useEffect } from 'react'
import type { Page } from '../../payload-types'
import Link from 'next/link'
import Image from 'next/image'
@@ -8,7 +7,32 @@ import { Shield, MessageCircle } from 'lucide-react'
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'hero' }>
/* PLACEHOLDER - do podmiany na realne zdjęcia klienta */
const heroImages = [
{
url: 'https://images.unsplash.com/photo-1554224155-8d04cb21cd6c?auto=format&fit=crop&w=1200&q=80',
alt: 'Dokumenty podatkowe i praca biurowa',
},
{
url: 'https://images.unsplash.com/photo-1497366216548-37526070297c?auto=format&fit=crop&w=1200&q=80',
alt: 'Profesjonalne biuro doradztwa podatkowego',
},
{
url: 'https://images.unsplash.com/photo-1486312338219-ce68d2c6f44d?auto=format&fit=crop&w=1200&q=80',
alt: 'Praca z rozliczeniami podatkowymi',
},
]
export const HeroBlock: React.FC<Props> = ({ title, subtitle, primaryCta, secondaryCta, yearsOfExperience }) => {
const [currentSlide, setCurrentSlide] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
setCurrentSlide((prev) => (prev + 1) % heroImages.length)
}, 5000)
return () => clearInterval(timer)
}, [])
return (
<section className="relative bg-background text-foreground py-16 md:py-24 border-b border-border/60">
<div className="container-premium">
@@ -51,23 +75,46 @@ export const HeroBlock: React.FC<Props> = ({ title, subtitle, primaryCta, second
</div>
</div>
{/* Kolumna ze zdjęciem */}
{/* Kolumna ze sliderem zdjęć */}
<div className="lg:col-span-5 relative">
<div className="relative mx-auto max-w-md lg:max-w-none">
<div className="relative aspect-[4/3] sm:aspect-[5/4] rounded-2xl overflow-hidden border border-border shadow-md bg-card">
{/* PLACEHOLDER - do podmiany na realne zdjęcia klienta */}
<Image
src="https://images.unsplash.com/photo-1497366216548-37526070297c?auto=format&fit=crop&w=1200&q=80"
alt="Biuro doradztwa podatkowego"
fill
priority
sizes="(max-width: 1024px) 100vw, 50vw"
className="object-cover"
/>
{heroImages.map((img, idx) => (
<div
key={idx}
className={`absolute inset-0 transition-opacity duration-700 ease-in-out ${
idx === currentSlide ? 'opacity-100 z-10' : 'opacity-0 z-0 pointer-events-none'
}`}
>
{/* PLACEHOLDER - do podmiany na realne zdjęcia klienta */}
<Image
src={img.url}
alt={img.alt}
fill
priority={idx === 0}
sizes="(max-width: 1024px) 100vw, 50vw"
className="object-cover"
/>
</div>
))}
{/* Kropki nawigacji slidera */}
<div className="absolute bottom-3 right-3 z-20 flex items-center gap-1.5 bg-background/80 backdrop-blur-md px-2.5 py-1 rounded-full border border-border/80">
{heroImages.map((_, idx) => (
<button
key={idx}
onClick={() => setCurrentSlide(idx)}
className={`w-2 h-2 rounded-full transition-all ${
idx === currentSlide ? 'bg-accent w-4' : 'bg-foreground/30 hover:bg-foreground/60'
}`}
aria-label={`Przejdź do zdjęcia ${idx + 1}`}
/>
))}
</div>
</div>
{/* Subtelna plakietka informacyjna */}
<div className="absolute -bottom-5 -left-4 sm:left-6 bg-card/95 backdrop-blur-sm border border-border rounded-xl p-4 shadow-lg flex items-center gap-3.5 max-w-xs">
<div className="absolute -bottom-5 -left-4 sm:left-6 bg-card/95 backdrop-blur-sm border border-border rounded-xl p-4 shadow-lg flex items-center gap-3.5 max-w-xs z-30">
<div className="w-10 h-10 rounded-lg bg-accent text-white font-bold font-heading flex items-center justify-center shrink-0">
JK
</div>