feat: radical premium redesign removing AI slop (full bleed hero, open layouts, editorial typography)

This commit is contained in:
MichalC
2026-09-01 23:33:43 +02:00
parent 441ae8f272
commit afc27b0183
5 changed files with 180 additions and 197 deletions
+62 -92
View File
@@ -3,22 +3,22 @@ import React, { useState, useEffect } from 'react'
import type { Page } from '../../payload-types' import type { Page } from '../../payload-types'
import Link from 'next/link' import Link from 'next/link'
import Image from 'next/image' 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' }> type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'hero' }>
/* PLACEHOLDER - do podmiany na realne zdjęcia klienta */ /* PLACEHOLDER - do podmiany na realne zdjęcia klienta */
const heroImages = [ 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', 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', 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', alt: 'Praca z rozliczeniami podatkowymi',
}, },
] ]
@@ -29,101 +29,71 @@ export const HeroBlock: React.FC<Props> = ({ title, subtitle, primaryCta, second
useEffect(() => { useEffect(() => {
const timer = setInterval(() => { const timer = setInterval(() => {
setCurrentSlide((prev) => (prev + 1) % heroImages.length) setCurrentSlide((prev) => (prev + 1) % heroImages.length)
}, 5000) }, 6000)
return () => clearInterval(timer) return () => clearInterval(timer)
}, []) }, [])
return ( return (
<section className="relative bg-background text-foreground py-16 md:py-24 border-b border-border/60"> <section className="relative w-full min-h-[90vh] flex items-end bg-background text-foreground pb-20 md:pb-32 pt-40">
<div className="container-premium"> {/* Pełnoekranowe tło ze sliderem */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-8 items-center"> <div className="absolute inset-0 z-0 overflow-hidden">
{/* Kolumna tekstowa */} {heroImages.map((img, idx) => (
<div className="lg:col-span-7 flex flex-col items-start text-left"> <div
{yearsOfExperience && ( key={idx}
<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"> className={`absolute inset-0 transition-opacity duration-1000 ease-in-out ${
<Shield size={16} /> idx === currentSlide ? 'opacity-100' : 'opacity-0 pointer-events-none'
<span>{yearsOfExperience}</span> }`}
</div> >
)} {/* PLACEHOLDER - do podmiany na realne zdjęcia klienta */}
<Image
<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"> src={img.url}
{title} alt={img.alt}
</h1> fill
priority={idx === 0}
<p className="text-lg sm:text-xl text-foreground/75 mb-8 leading-relaxed max-w-2xl"> sizes="100vw"
{subtitle} className="object-cover object-center"
</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>
</div> </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="container-premium relative z-10">
<div className="lg:col-span-5 relative"> <div className="max-w-4xl">
<div className="relative mx-auto max-w-md lg:max-w-none"> {yearsOfExperience && (
<div className="relative aspect-[4/3] sm:aspect-[5/4] rounded-2xl overflow-hidden border border-border shadow-md bg-card"> <div className="text-accent/90 text-sm md:text-base font-bold tracking-[0.2em] uppercase mb-8">
{heroImages.map((img, idx) => ( {yearsOfExperience}
<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> </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> </div>
</div> </div>
+20 -15
View File
@@ -8,34 +8,37 @@ type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'process' }>
export const ProcessBlock: React.FC<Props> = ({ title, steps, ctaText }) => { export const ProcessBlock: React.FC<Props> = ({ title, steps, ctaText }) => {
return ( 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="container-premium">
<div className="max-w-2xl mx-auto text-center mb-16"> <div className="max-w-2xl mb-16 md:mb-24">
<h2 className="font-heading text-3xl sm:text-4xl md:text-5xl font-bold tracking-tight text-foreground mb-4"> <h2 className="font-heading text-4xl sm:text-5xl md:text-6xl font-extrabold tracking-tight text-foreground mb-6 text-balance">
{title} {title}
</h2> </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. Prosty i przejrzysty proces rozliczenia w 3 krokach — bez zbędnych formalności.
</p> </p>
</div> </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) => { {steps?.map((step: NonNullable<Props['steps']>[0], index: number) => {
const stepNumber = String(index + 1).padStart(2, '0') const stepNumber = String(index + 1).padStart(2, '0')
return ( return (
<div <div
key={step.id || index} 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-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">
<div className="font-heading text-3xl font-extrabold text-accent/80 mb-4"> {stepNumber}
{stepNumber} </div>
</div> <div className="relative z-10 pt-4 border-t-2 border-transparent group-hover:border-accent transition-colors">
<h3 className="font-heading text-xl font-bold text-foreground mb-3 leading-snug"> <h3 className="font-heading text-2xl md:text-3xl font-bold text-foreground mb-4">
{step.title} {step.title}
</h3> </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} {step.description}
</p> </p>
</div> </div>
@@ -45,13 +48,15 @@ export const ProcessBlock: React.FC<Props> = ({ title, steps, ctaText }) => {
</div> </div>
{ctaText && ( {ctaText && (
<div className="text-center"> <div>
<Link <Link
href="/jak-sie-umowic" 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> <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> </Link>
</div> </div>
)} )}
+33 -28
View File
@@ -1,44 +1,49 @@
'use client' 'use client'
import React from 'react' import React from 'react'
import { motion } from 'framer-motion'
import { ArrowRight } from 'lucide-react'
import Link from 'next/link' import Link from 'next/link'
import type { Page } from '../../payload-types' import type { Page } from '../../payload-types'
import { ArrowRight } from 'lucide-react'
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'servicesList' }> type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'servicesList' }>
export const ServicesListBlock: React.FC<Props> = ({ title, services }) => { export const ServicesListBlock: React.FC<Props> = ({ title, services }) => {
return ( return (
<section className="py-24 bg-background"> <section className="py-20 md:py-32 bg-background text-foreground">
<div className="container mx-auto px-4"> <div className="container-premium">
<motion.h2 <div className="mb-16 md:mb-24">
initial={{ opacity: 0, y: 20 }} <h2 className="font-heading text-4xl sm:text-5xl md:text-6xl font-extrabold text-foreground tracking-tight max-w-2xl text-balance">
whileInView={{ opacity: 1, y: 0 }} {title}
viewport={{ once: true }} </h2>
className="text-3xl md:text-5xl font-bold text-foreground mb-16 text-center" </div>
>
{title}
</motion.h2>
<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) => ( {services?.map((service: NonNullable<Props['services']>[0], index: number) => (
<motion.div <div
key={service.id || index} key={service.id || index}
initial={{ opacity: 0, y: 20 }} 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"
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
className="group"
> >
<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"> <div className="md:w-1/3 shrink-0">
<h3 className="text-xl font-bold text-foreground mb-4">{service.title}</h3> <h3 className="font-heading text-2xl md:text-3xl font-bold text-foreground group-hover:text-accent transition-colors">
<p className="text-foreground/80 mb-8">{service.description}</p> {service.title}
<div className="flex items-center text-accent font-semibold group-hover:text-accent-hover mt-auto"> </h3>
{service.linkText} </div>
<ArrowRight size={20} className="ml-2 transition-transform group-hover:translate-x-1" /> <div className="md:w-7/12 flex-grow">
</div> <p className="text-foreground/75 text-lg md:text-xl font-light leading-relaxed">
</Link> {service.description}
</motion.div> </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>
</div> </div>
+10 -9
View File
@@ -1,3 +1,4 @@
'use client'
import React from 'react' import React from 'react'
export interface StatsProps { export interface StatsProps {
@@ -15,37 +16,37 @@ export const StatsBlock: React.FC<StatsProps> = ({ title, subtitle, items }) =>
if (!items || items.length === 0) return null if (!items || items.length === 0) return null
return ( 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"> <div className="container-premium">
{(title || subtitle) && ( {(title || subtitle) && (
<div className="max-w-2xl mx-auto text-center mb-12"> <div className="max-w-2xl mb-16">
{title && ( {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} {title}
</h2> </h2>
)} )}
{subtitle && ( {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} {subtitle}
</p> </p>
)} )}
</div> </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) => ( {items.map((item, index) => (
<div <div
key={item.id || index} 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} {item.stat}
</div> </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} {item.label}
</div> </div>
{item.description && ( {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} {item.description}
</p> </p>
)} )}
+55 -53
View File
@@ -1,66 +1,68 @@
'use client' 'use client'
import React from 'react' import React, { useState, useEffect } from 'react'
import { Quote } from 'lucide-react'
import Image from 'next/image'
import type { Page } from '../../payload-types' import type { Page } from '../../payload-types'
type Props = Extract<NonNullable<Page['layout']>[0], { blockType: 'testimonials' }> 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 }) => { 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 ( return (
<section className="py-16 md:py-24 bg-background"> <section className="py-24 md:py-40 bg-background border-y border-border overflow-hidden">
<div className="container-premium"> <div className="container-premium max-w-4xl mx-auto text-center relative">
<div className="max-w-2xl mb-12 text-center md:text-left"> <h2 className="text-xs font-bold tracking-[0.2em] uppercase text-accent mb-12">
<h2 className="font-heading text-2xl sm:text-3xl md:text-4xl font-bold text-foreground tracking-tight"> {title}
{title} </h2>
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8"> <div className="relative min-h-[300px] sm:min-h-[240px] flex items-center justify-center">
{testimonials?.map((t: NonNullable<Props['testimonials']>[0], index: number) => { {testimonials.map((t: NonNullable<Props['testimonials']>[0], index: number) => (
const avatarUrl = avatarPlaceholders[index % avatarPlaceholders.length] <div
key={t.id || index}
return ( className={`absolute inset-0 flex flex-col items-center justify-center transition-opacity duration-1000 ease-in-out ${
<div index === currentIndex ? 'opacity-100 z-10' : 'opacity-0 z-0 pointer-events-none'
key={t.id || index} }`}
className="bg-card border border-border p-6 sm:p-8 rounded-2xl shadow-sm flex flex-col justify-between" >
> <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">
<div> "{t.quote}"
<Quote className="text-accent/30 mb-4" size={32} /> </p>
<p className="text-foreground/80 leading-relaxed text-sm sm:text-base mb-6">
"{t.quote}" <div className="flex items-center gap-4">
</p> <div className="w-12 h-px bg-accent/40" />
</div> <p className="font-heading font-bold text-lg sm:text-xl text-foreground/80 uppercase tracking-wide">
{t.author}
<div className="flex items-center gap-3.5 pt-4 border-t border-border/50"> </p>
<div className="relative w-11 h-11 rounded-full overflow-hidden border border-border shrink-0 bg-muted"> <div className="w-12 h-px bg-accent/40" />
{/* 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> </div>
) </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> </div>
</section> </section>
) )