feat: radical premium redesign removing AI slop (full bleed hero, open layouts, editorial typography)
This commit is contained in:
@@ -3,22 +3,22 @@ import React, { useState, useEffect } from 'react'
|
||||
import type { Page } from '../../payload-types'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
import { Shield, MessageCircle } from 'lucide-react'
|
||||
import { ArrowRight } 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',
|
||||
url: 'https://images.unsplash.com/photo-1554224155-8d04cb21cd6c?auto=format&fit=crop&w=2000&q=80',
|
||||
alt: 'Dokumenty podatkowe i praca biurowa',
|
||||
},
|
||||
{
|
||||
url: 'https://images.unsplash.com/photo-1497366216548-37526070297c?auto=format&fit=crop&w=1200&q=80',
|
||||
url: 'https://images.unsplash.com/photo-1497366216548-37526070297c?auto=format&fit=crop&w=2000&q=80',
|
||||
alt: 'Profesjonalne biuro doradztwa podatkowego',
|
||||
},
|
||||
{
|
||||
url: 'https://images.unsplash.com/photo-1486312338219-ce68d2c6f44d?auto=format&fit=crop&w=1200&q=80',
|
||||
url: 'https://images.unsplash.com/photo-1486312338219-ce68d2c6f44d?auto=format&fit=crop&w=2000&q=80',
|
||||
alt: 'Praca z rozliczeniami podatkowymi',
|
||||
},
|
||||
]
|
||||
@@ -29,101 +29,71 @@ export const HeroBlock: React.FC<Props> = ({ title, subtitle, primaryCta, second
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setCurrentSlide((prev) => (prev + 1) % heroImages.length)
|
||||
}, 5000)
|
||||
}, 6000)
|
||||
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">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-8 items-center">
|
||||
{/* Kolumna tekstowa */}
|
||||
<div className="lg:col-span-7 flex flex-col items-start text-left">
|
||||
{yearsOfExperience && (
|
||||
<div className="inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full bg-accent/10 border border-accent/20 text-accent text-xs sm:text-sm font-semibold tracking-wide mb-6">
|
||||
<Shield size={16} />
|
||||
<span>{yearsOfExperience}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<h1 className="font-heading text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-extrabold tracking-tight leading-[1.15] text-foreground mb-6">
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
<p className="text-lg sm:text-xl text-foreground/75 mb-8 leading-relaxed max-w-2xl">
|
||||
{subtitle}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 w-full sm:w-auto">
|
||||
<a
|
||||
href={process.env.NEXT_PUBLIC_WHATSAPP_LINK || '#'}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-8 py-4 bg-green-500 hover:bg-green-600 text-white font-bold rounded-xl transition-all flex items-center justify-center gap-2.5 shadow-sm text-base"
|
||||
>
|
||||
<MessageCircle size={20} />
|
||||
<span>{primaryCta}</span>
|
||||
</a>
|
||||
{secondaryCta?.text && secondaryCta?.url && (
|
||||
<Link
|
||||
href={secondaryCta.url}
|
||||
className="px-8 py-4 bg-card hover:bg-accent/5 border border-border text-foreground font-semibold rounded-xl transition-colors text-center text-base"
|
||||
>
|
||||
{secondaryCta.text}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<section className="relative w-full min-h-[90vh] flex items-end bg-background text-foreground pb-20 md:pb-32 pt-40">
|
||||
{/* Pełnoekranowe tło ze sliderem */}
|
||||
<div className="absolute inset-0 z-0 overflow-hidden">
|
||||
{heroImages.map((img, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`absolute inset-0 transition-opacity duration-1000 ease-in-out ${
|
||||
idx === currentSlide ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
||||
}`}
|
||||
>
|
||||
{/* PLACEHOLDER - do podmiany na realne zdjęcia klienta */}
|
||||
<Image
|
||||
src={img.url}
|
||||
alt={img.alt}
|
||||
fill
|
||||
priority={idx === 0}
|
||||
sizes="100vw"
|
||||
className="object-cover object-center"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{/* Potężny, gładki overlay z koloru tła aplikacji od dołu i od lewej, dla idealnej czytelności */}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-background via-background/80 to-transparent" />
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-background via-background/40 to-transparent" />
|
||||
</div>
|
||||
|
||||
{/* 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">
|
||||
{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 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>
|
||||
<div className="text-xs">
|
||||
<p className="font-bold text-foreground">Punkt Doradczy b.b.h.</p>
|
||||
<p className="text-foreground/60">Lohnsteuerhilfeverein e.V.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="container-premium relative z-10">
|
||||
<div className="max-w-4xl">
|
||||
{yearsOfExperience && (
|
||||
<div className="text-accent/90 text-sm md:text-base font-bold tracking-[0.2em] uppercase mb-8">
|
||||
{yearsOfExperience}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<h1 className="font-heading text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-extrabold tracking-tight leading-[1.05] text-foreground mb-8 text-balance">
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
<p className="text-xl sm:text-2xl text-foreground/80 mb-12 leading-relaxed max-w-2xl font-light">
|
||||
{subtitle}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-8">
|
||||
<a
|
||||
href={process.env.NEXT_PUBLIC_WHATSAPP_LINK || '#'}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-10 py-5 bg-accent hover:bg-accent/90 text-background font-bold text-lg transition-colors flex items-center justify-center"
|
||||
>
|
||||
{primaryCta}
|
||||
</a>
|
||||
{secondaryCta?.text && secondaryCta?.url && (
|
||||
<Link
|
||||
href={secondaryCta.url}
|
||||
className="group flex items-center gap-3 text-foreground font-semibold text-lg hover:text-accent transition-colors"
|
||||
>
|
||||
<span>{secondaryCta.text}</span>
|
||||
<ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,34 +8,37 @@ type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'process' }>
|
||||
|
||||
export const ProcessBlock: React.FC<Props> = ({ title, steps, ctaText }) => {
|
||||
return (
|
||||
<section className="py-20 md:py-28 bg-background text-foreground border-t border-border">
|
||||
<section className="py-20 md:py-32 bg-background text-foreground overflow-hidden">
|
||||
<div className="container-premium">
|
||||
<div className="max-w-2xl mx-auto text-center mb-16">
|
||||
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight text-foreground mb-4">
|
||||
<div className="max-w-2xl mb-16 md:mb-24">
|
||||
<h2 className="font-heading text-4xl sm:text-5xl md:text-6xl font-extrabold tracking-tight text-foreground mb-6 text-balance">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="text-foreground/70 text-base sm:text-lg">
|
||||
<p className="text-foreground/70 text-lg md:text-xl font-light">
|
||||
Prosty i przejrzysty proces rozliczenia w 3 krokach — bez zbędnych formalności.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 md:gap-8 mb-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-16 md:gap-8 mb-20 relative">
|
||||
{/* Opcjonalna pozioma linia łącząca na desktopie (bardzo subtelna) */}
|
||||
<div className="hidden md:block absolute top-[4.5rem] left-0 right-0 h-px bg-border/40 -z-10" />
|
||||
|
||||
{steps?.map((step: NonNullable<Props['steps']>[0], index: number) => {
|
||||
const stepNumber = String(index + 1).padStart(2, '0')
|
||||
|
||||
return (
|
||||
<div
|
||||
key={step.id || index}
|
||||
className="bg-card border border-border rounded-2xl p-6 sm:p-8 shadow-xs flex flex-col justify-between hover:border-accent/40 transition-colors"
|
||||
className="relative flex flex-col group"
|
||||
>
|
||||
<div>
|
||||
<div className="font-heading text-3xl font-extrabold text-accent/80 mb-4">
|
||||
{stepNumber}
|
||||
</div>
|
||||
<h3 className="font-heading text-xl font-bold text-foreground mb-3 leading-snug">
|
||||
<div className="font-heading text-7xl md:text-8xl lg:text-[10rem] font-black text-foreground/5 tracking-tighter leading-none mb-4 -ml-2 group-hover:text-accent/10 transition-colors">
|
||||
{stepNumber}
|
||||
</div>
|
||||
<div className="relative z-10 pt-4 border-t-2 border-transparent group-hover:border-accent transition-colors">
|
||||
<h3 className="font-heading text-2xl md:text-3xl font-bold text-foreground mb-4">
|
||||
{step.title}
|
||||
</h3>
|
||||
<p className="text-foreground/75 text-sm sm:text-base leading-relaxed">
|
||||
<p className="text-foreground/75 text-lg font-light leading-relaxed">
|
||||
{step.description}
|
||||
</p>
|
||||
</div>
|
||||
@@ -45,13 +48,15 @@ export const ProcessBlock: React.FC<Props> = ({ title, steps, ctaText }) => {
|
||||
</div>
|
||||
|
||||
{ctaText && (
|
||||
<div className="text-center">
|
||||
<div>
|
||||
<Link
|
||||
href="/jak-sie-umowic"
|
||||
className="inline-flex items-center gap-2 px-8 py-4 bg-card hover:bg-accent/5 border border-border hover:border-foreground/40 rounded-xl text-foreground font-semibold text-base transition-colors"
|
||||
className="inline-flex items-center gap-4 text-foreground font-semibold text-lg hover:text-accent transition-colors group"
|
||||
>
|
||||
<span>{ctaText}</span>
|
||||
<ArrowRight size={18} />
|
||||
<div className="w-10 h-10 rounded-full border border-border flex items-center justify-center group-hover:border-accent transition-colors">
|
||||
<ArrowRight className="w-4 h-4 group-hover:translate-x-0.5 transition-transform" />
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,44 +1,49 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
import { ArrowRight } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import type { Page } from '../../payload-types'
|
||||
import { ArrowRight } from 'lucide-react'
|
||||
|
||||
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'servicesList' }>
|
||||
|
||||
export const ServicesListBlock: React.FC<Props> = ({ title, services }) => {
|
||||
return (
|
||||
<section className="py-24 bg-background">
|
||||
<div className="container mx-auto px-4">
|
||||
<motion.h2
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-3xl md:text-5xl font-bold text-foreground mb-16 text-center"
|
||||
>
|
||||
{title}
|
||||
</motion.h2>
|
||||
<section className="py-20 md:py-32 bg-background text-foreground">
|
||||
<div className="container-premium">
|
||||
<div className="mb-16 md:mb-24">
|
||||
<h2 className="font-heading text-4xl sm:text-5xl md:text-6xl font-extrabold text-foreground tracking-tight max-w-2xl text-balance">
|
||||
{title}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
<div className="border-t border-border">
|
||||
{services?.map((service: NonNullable<Props['services']>[0], index: number) => (
|
||||
<motion.div
|
||||
<div
|
||||
key={service.id || index}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
className="group"
|
||||
className="group border-b border-border py-8 md:py-12 flex flex-col md:flex-row gap-6 md:gap-12 lg:gap-24 items-start md:items-center hover:bg-foreground/[0.02] transition-colors"
|
||||
>
|
||||
<Link href="/uslugi" className="block h-full p-8 rounded-2xl bg-card border border-border transition-all duration-300 hover:-translate-y-2 hover:shadow-xl hover:bg-background">
|
||||
<h3 className="text-xl font-bold text-foreground mb-4">{service.title}</h3>
|
||||
<p className="text-foreground/80 mb-8">{service.description}</p>
|
||||
<div className="flex items-center text-accent font-semibold group-hover:text-accent-hover mt-auto">
|
||||
{service.linkText}
|
||||
<ArrowRight size={20} className="ml-2 transition-transform group-hover:translate-x-1" />
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
<div className="md:w-1/3 shrink-0">
|
||||
<h3 className="font-heading text-2xl md:text-3xl font-bold text-foreground group-hover:text-accent transition-colors">
|
||||
{service.title}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="md:w-7/12 flex-grow">
|
||||
<p className="text-foreground/75 text-lg md:text-xl font-light leading-relaxed">
|
||||
{service.description}
|
||||
</p>
|
||||
</div>
|
||||
<div className="md:w-1/12 flex justify-end shrink-0">
|
||||
<Link
|
||||
href="/uslugi"
|
||||
className="flex items-center gap-3 text-foreground font-semibold hover:text-accent transition-colors"
|
||||
>
|
||||
<span className="md:hidden">{service.linkText}</span>
|
||||
<div className="w-12 h-12 rounded-full border border-border flex items-center justify-center group-hover:border-accent group-hover:bg-accent/5 transition-all">
|
||||
<ArrowRight className="w-5 h-5 group-hover:translate-x-0.5 transition-transform" />
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
|
||||
export interface StatsProps {
|
||||
@@ -15,37 +16,37 @@ export const StatsBlock: React.FC<StatsProps> = ({ title, subtitle, items }) =>
|
||||
if (!items || items.length === 0) return null
|
||||
|
||||
return (
|
||||
<section className="py-16 md:py-20 bg-card border-y border-border">
|
||||
<section className="py-20 md:py-32 bg-background border-y border-border">
|
||||
<div className="container-premium">
|
||||
{(title || subtitle) && (
|
||||
<div className="max-w-2xl mx-auto text-center mb-12">
|
||||
<div className="max-w-2xl mb-16">
|
||||
{title && (
|
||||
<h2 className="font-heading text-2xl sm:text-3xl font-bold text-foreground mb-3">
|
||||
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground mb-4 tracking-tight">
|
||||
{title}
|
||||
</h2>
|
||||
)}
|
||||
{subtitle && (
|
||||
<p className="text-foreground/70 text-sm sm:text-base leading-relaxed">
|
||||
<p className="text-foreground/70 text-lg sm:text-xl leading-relaxed font-light">
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 border-t border-l border-border mt-12">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={item.id || index}
|
||||
className="flex flex-col items-center text-center p-6 rounded-2xl bg-background/50 border border-border/80 shadow-xs"
|
||||
className="flex flex-col p-8 sm:p-12 border-b border-r border-border hover:bg-foreground/[0.02] transition-colors"
|
||||
>
|
||||
<div className="font-heading text-4xl sm:text-5xl lg:text-6xl font-extrabold text-accent mb-2 tracking-tight">
|
||||
<div className="font-heading text-5xl lg:text-7xl font-extrabold text-accent mb-4 tracking-tighter">
|
||||
{item.stat}
|
||||
</div>
|
||||
<div className="font-heading text-lg sm:text-xl font-bold text-foreground mb-1.5">
|
||||
<div className="font-heading text-xl font-bold text-foreground mb-2">
|
||||
{item.label}
|
||||
</div>
|
||||
{item.description && (
|
||||
<p className="text-sm text-foreground/70 leading-relaxed max-w-xs">
|
||||
<p className="text-base text-foreground/70 leading-relaxed font-light">
|
||||
{item.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -1,66 +1,68 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { Quote } from 'lucide-react'
|
||||
import Image from 'next/image'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import type { Page } from '../../payload-types'
|
||||
|
||||
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'testimonials' }>
|
||||
|
||||
/* PLACEHOLDER - do podmiany na realne zdjęcia klienta */
|
||||
const avatarPlaceholders = [
|
||||
'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=150&h=150&q=80',
|
||||
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&w=150&h=150&q=80',
|
||||
'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=150&h=150&q=80',
|
||||
]
|
||||
|
||||
export const TestimonialsBlock: React.FC<Props> = ({ title, testimonials }) => {
|
||||
const [currentIndex, setCurrentIndex] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
if (!testimonials || testimonials.length <= 1) return
|
||||
|
||||
const timer = setInterval(() => {
|
||||
setCurrentIndex((prev) => (prev + 1) % testimonials.length)
|
||||
}, 7000)
|
||||
|
||||
return () => clearInterval(timer)
|
||||
}, [testimonials])
|
||||
|
||||
if (!testimonials || testimonials.length === 0) return null
|
||||
|
||||
return (
|
||||
<section className="py-16 md:py-24 bg-background">
|
||||
<div className="container-premium">
|
||||
<div className="max-w-2xl mb-12 text-center md:text-left">
|
||||
<h2 className="font-heading text-2xl sm:text-3xl md:text-4xl font-bold text-foreground tracking-tight">
|
||||
{title}
|
||||
</h2>
|
||||
</div>
|
||||
<section className="py-24 md:py-40 bg-background border-y border-border overflow-hidden">
|
||||
<div className="container-premium max-w-4xl mx-auto text-center relative">
|
||||
<h2 className="text-xs font-bold tracking-[0.2em] uppercase text-accent mb-12">
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8">
|
||||
{testimonials?.map((t: NonNullable<Props['testimonials']>[0], index: number) => {
|
||||
const avatarUrl = avatarPlaceholders[index % avatarPlaceholders.length]
|
||||
|
||||
return (
|
||||
<div
|
||||
key={t.id || index}
|
||||
className="bg-card border border-border p-6 sm:p-8 rounded-2xl shadow-sm flex flex-col justify-between"
|
||||
>
|
||||
<div>
|
||||
<Quote className="text-accent/30 mb-4" size={32} />
|
||||
<p className="text-foreground/80 leading-relaxed text-sm sm:text-base mb-6">
|
||||
"{t.quote}"
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3.5 pt-4 border-t border-border/50">
|
||||
<div className="relative w-11 h-11 rounded-full overflow-hidden border border-border shrink-0 bg-muted">
|
||||
{/* PLACEHOLDER - do podmiany na realne zdjęcia klienta */}
|
||||
<Image
|
||||
src={avatarUrl}
|
||||
alt={t.author || "Klient"}
|
||||
fill
|
||||
sizes="44px"
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-heading font-bold text-sm sm:text-base text-foreground">
|
||||
{t.author}
|
||||
</p>
|
||||
<p className="text-xs text-foreground/50">Klient zweryfikowany</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative min-h-[300px] sm:min-h-[240px] flex items-center justify-center">
|
||||
{testimonials.map((t: NonNullable<Props['testimonials']>[0], index: number) => (
|
||||
<div
|
||||
key={t.id || index}
|
||||
className={`absolute inset-0 flex flex-col items-center justify-center transition-opacity duration-1000 ease-in-out ${
|
||||
index === currentIndex ? 'opacity-100 z-10' : 'opacity-0 z-0 pointer-events-none'
|
||||
}`}
|
||||
>
|
||||
<p className="font-heading text-3xl sm:text-4xl md:text-5xl font-extrabold text-foreground leading-[1.2] mb-10 text-balance tracking-tight">
|
||||
"{t.quote}"
|
||||
</p>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-px bg-accent/40" />
|
||||
<p className="font-heading font-bold text-lg sm:text-xl text-foreground/80 uppercase tracking-wide">
|
||||
{t.author}
|
||||
</p>
|
||||
<div className="w-12 h-px bg-accent/40" />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{testimonials.length > 1 && (
|
||||
<div className="flex items-center justify-center gap-3 mt-12">
|
||||
{testimonials.map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => setCurrentIndex(index)}
|
||||
className={`w-2 h-2 transition-all ${
|
||||
index === currentIndex ? 'bg-accent w-8' : 'bg-border hover:bg-foreground/30'
|
||||
}`}
|
||||
aria-label={`Pokaż opinię ${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user