feat: redesign Hero block, update global design tokens and fonts to new editorial direction
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { Inter, Outfit } from 'next/font/google'
|
||||
import { Public_Sans, Fraunces } from 'next/font/google'
|
||||
import { Providers } from '../../components/Providers'
|
||||
import { LanguageSlugsProvider } from '../../components/LanguageSlugsProvider'
|
||||
import { Header } from '../../components/Header'
|
||||
@@ -8,8 +8,13 @@ import { getPayload } from 'payload'
|
||||
import configPromise from '@payload-config'
|
||||
import '../globals.css'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'], variable: '--font-sans' })
|
||||
const outfit = Outfit({ subsets: ['latin'], variable: '--font-heading' })
|
||||
const publicSans = Public_Sans({ subsets: ['latin'], variable: '--font-sans' })
|
||||
const fraunces = Fraunces({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-heading',
|
||||
axes: ['opsz'],
|
||||
weight: 'variable',
|
||||
})
|
||||
|
||||
export const metadata = {
|
||||
title: 'Finanz- & Lohnbuchhaltung Dipl.-Ing. Janusz Kędzierski',
|
||||
@@ -50,7 +55,7 @@ export default async function RootLayout({
|
||||
const footerNavItems = (footerData.navigation || []).map((item: any) => item.page).filter((page: any) => typeof page === 'object' && page !== null);
|
||||
|
||||
return (
|
||||
<html lang="pl" suppressHydrationWarning className={`${inter.variable} ${outfit.variable}`}>
|
||||
<html lang="pl" suppressHydrationWarning className={`${publicSans.variable} ${fraunces.variable}`}>
|
||||
<body>
|
||||
<Providers>
|
||||
<LanguageSlugsProvider>
|
||||
|
||||
+16
-18
@@ -10,35 +10,33 @@
|
||||
--color-card: var(--card-color);
|
||||
--color-border: var(--border-color);
|
||||
|
||||
--font-sans: 'Inter', sans-serif;
|
||||
--font-heading: 'Outfit', sans-serif;
|
||||
--font-sans: 'Public Sans', sans-serif;
|
||||
--font-heading: 'Fraunces', serif;
|
||||
}
|
||||
|
||||
:root {
|
||||
/* Jasny motyw */
|
||||
--bg-color: #FAFAFA;
|
||||
--fg-color: #18181B;
|
||||
--bg-color: #F7F5F1;
|
||||
--fg-color: #1C1B1A;
|
||||
--card-color: #FFFFFF;
|
||||
--border-color: #E4E4E7;
|
||||
--border-color: #E2DDD3;
|
||||
|
||||
/* Ciemniejszy złoty/ochra dla zachowania kontrastu na jasnym tle */
|
||||
--accent-color: #7B5C35;
|
||||
--accent-hover-color: #614828;
|
||||
--accent-color: #A13D2C;
|
||||
--accent-hover-color: #8A3324;
|
||||
|
||||
/* Globalny token dla przycisków prostokątnych (umiarkowane zaokrąglenie, nie pill) */
|
||||
--radius-button: 6px;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
/* Ciemny motyw: czysty, głęboki antracyt (bez niebieskiego odcienia) */
|
||||
--bg-color: #121212;
|
||||
--fg-color: #F8FAFC;
|
||||
--card-color: #18181B;
|
||||
--border-color: #27272A;
|
||||
/* Ciemny motyw */
|
||||
--bg-color: #17140F;
|
||||
--fg-color: #F5F1E8;
|
||||
--card-color: #1F1B14;
|
||||
--border-color: #332C21;
|
||||
|
||||
/* Jasny złoty/ochra dla zachowania kontrastu na ciemnym tle */
|
||||
--accent-color: #C5A880;
|
||||
--accent-hover-color: #D4B995;
|
||||
--accent-color: #D4634A;
|
||||
--accent-hover-color: #E17B62;
|
||||
}
|
||||
|
||||
/* Globalne style dla HTML i Body */
|
||||
@@ -57,7 +55,7 @@ body {
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--font-heading);
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
@@ -68,7 +66,7 @@ h1, h2, h3, h4, h5, h6 {
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-color: var(--accent-hover-color);
|
||||
@apply shadow-lg transform -translate-y-0.5;
|
||||
@apply shadow-lg;
|
||||
}
|
||||
|
||||
/* Utility dla szerokich kontenerów i dużego oddechu (whitespace) */
|
||||
|
||||
@@ -1,78 +1,29 @@
|
||||
'use client'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import type { Page } from '../../payload-types'
|
||||
import React from 'react'
|
||||
import type { Page, Media } from '../../payload-types'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
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=2000&q=80',
|
||||
alt: 'Dokumenty podatkowe i praca biurowa',
|
||||
},
|
||||
{
|
||||
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=2000&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)
|
||||
}, 6000)
|
||||
return () => clearInterval(timer)
|
||||
}, [])
|
||||
export const HeroBlock: React.FC<Props> = ({ title, subtitle, primaryCta, secondaryCta, image }) => {
|
||||
// Sprawdzamy czy obrazek jest obiektem Media czy tylko ID
|
||||
const heroMedia = typeof image === 'object' && image !== null ? (image as Media) : null;
|
||||
// PLACEHOLDER - do podmiany na zdjęcie klienta jeśli brak w CMS
|
||||
const imageUrl = heroMedia?.url || 'https://images.unsplash.com/photo-1554224155-8d04cb21cd6c?auto=format&fit=crop&w=1200&q=80';
|
||||
const imageAlt = heroMedia?.alt || 'Doradztwo podatkowe - biuro';
|
||||
|
||||
return (
|
||||
<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>
|
||||
<section className="w-full bg-background text-foreground pt-32 pb-16 md:pt-48 md:pb-24">
|
||||
<div className="container-premium">
|
||||
<div className="flex flex-col-reverse md:flex-row items-center justify-between gap-12 lg:gap-24">
|
||||
|
||||
<div className="container-premium relative z-10">
|
||||
<div className="max-w-4xl">
|
||||
{yearsOfExperience && (
|
||||
<div className="font-mono text-sm md:text-base font-bold text-accent tracking-widest uppercase mb-4">
|
||||
// {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">
|
||||
<div className="w-full md:w-1/2 max-w-xl flex flex-col">
|
||||
<h1 className="font-heading text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold 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">
|
||||
<p className="text-xl sm:text-2xl text-foreground/80 mb-12 leading-relaxed font-light">
|
||||
{subtitle}
|
||||
</p>
|
||||
|
||||
@@ -81,21 +32,35 @@ export const HeroBlock: React.FC<Props> = ({ title, subtitle, primaryCta, second
|
||||
href={process.env.NEXT_PUBLIC_WHATSAPP_LINK || '#'}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-8 py-4 bg-accent hover:bg-accent/90 text-background font-bold text-base sm:text-lg transition-colors flex items-center justify-center rounded-[var(--radius-button)]"
|
||||
className="btn-primary"
|
||||
>
|
||||
{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"
|
||||
className="text-foreground font-semibold text-lg hover:text-accent hover:underline underline-offset-4 transition-colors"
|
||||
>
|
||||
<span>{secondaryCta.text}</span>
|
||||
<ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
|
||||
{secondaryCta.text}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full md:w-1/2 flex justify-center md:justify-end">
|
||||
<div className="relative w-full aspect-[4/5] max-w-md">
|
||||
<Image
|
||||
src={imageUrl}
|
||||
alt={imageAlt}
|
||||
fill
|
||||
priority
|
||||
sizes="(max-width: 768px) 100vw, 50vw"
|
||||
className="object-cover object-center"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
||||
@@ -14,6 +14,6 @@ export const Hero: Block = {
|
||||
{ name: 'url', type: 'text' },
|
||||
],
|
||||
},
|
||||
{ name: 'yearsOfExperience', type: 'text', localized: true },
|
||||
{ name: 'image', type: 'upload', relationTo: 'media', required: true },
|
||||
],
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ export interface Page {
|
||||
text?: string | null;
|
||||
url?: string | null;
|
||||
};
|
||||
yearsOfExperience?: string | null;
|
||||
image: number | Media;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'hero';
|
||||
@@ -624,7 +624,7 @@ export interface PagesSelect<T extends boolean = true> {
|
||||
text?: T;
|
||||
url?: T;
|
||||
};
|
||||
yearsOfExperience?: T;
|
||||
image?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user