Files
PrimeCode/app/page.tsx
2025-09-16 11:19:07 +02:00

2335 lines
85 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import {
motion,
MotionConfig,
useInView,
AnimatePresence,
useScroll,
useTransform,
useReducedMotion,
useMotionValue,
useSpring,
type Variants,
} from "framer-motion";
import { Megaphone, MessageSquare, LineChart, Users, Wrench, ClipboardCheck, Mail, Phone, Menu, X } from "lucide-react";
import React, { useRef, useEffect, useState, useCallback } from 'react'
import { Plus_Jakarta_Sans } from "next/font/google";
const headingFont = Plus_Jakarta_Sans({
subsets: ["latin"],
weight: ["700", "800"],
display: "swap",
});
import { Manrope } from "next/font/google";
const sytuacjeFont = Manrope({
subsets: ["latin"],
weight: ["700", "800"],
display: "swap",
});
/* =========================================================
Prime Code — Neo-Editorial+, full-bleed video hero
Tekst zachowany 1:1
========================================================= */
export default function Home() {
const [openIndex, setOpenIndex] = useState<number | null>(null)
const [activeSection, setActiveSection] = useState<string>('PrimeCode')
const [mobileOpen, setMobileOpen] = useState(false);
// blokuj scroll body, gdy menu otwarte
useEffect(() => {
document.body.style.overflow = mobileOpen ? "hidden" : "";
return () => { document.body.style.overflow = ""; };
}, [mobileOpen]);
useEffect(() => {
const sections = document.querySelectorAll('section[id]')
const obs = new IntersectionObserver(
(entries) => entries.forEach((e) => e.isIntersecting && setActiveSection(e.target.id)),
{ rootMargin: '-55% 0px -45% 0px' }
)
sections.forEach((s) => obs.observe(s))
return () => obs.disconnect()
}, [])
const navItems = [
{ id: 'PrimeCode', label: 'O nas' },
{ id: 'Sytuacje', label: 'Wyzwania' },
{ id: 'Partner', label: 'Twoje potrzeby' },
{ id: 'Model', label: 'Model Prime Code' },
{ id: 'CoPotrafimy', label: 'Co potrafimy?' },
{ id: 'JakDzialamy', label: 'Jak działamy?' },
{ id: 'PomogliśmyCaseStudies', label: 'Pomogliśmy' },
{ id: 'Kontakt', label: 'Kontakt' },
]
const caseStudies = [
{
title: 'Firma kosmetyczna',
short: 'Nowa strategia detaliczna i transformacja doświadczenia klientek w salonach.',
details: [
'Opracowanie nowej strategii detalicznej, integrującej sprzedaż z usługami.',
'Zaprojektowanie atmosfery sklepów oraz selekcja unikalnego asortymentu.',
'Transformacja komunikacji od eksperckiej wiedzy do relacji opartej na empatii.',
'Redefinicja propozycji wartości skupienie na doświadczeniu troski o zdrowie i piękno.',
'Stworzenie concept storeu jako pełnego doświadczenia marki.',
'Przejście z jednego formatu sklepu na trzy lepiej dopasowane formaty detaliczne.',
'Zmiana podejścia operacyjnego: od 4P do 7P, z naciskiem na doświadczenie klientki.',
],
},
{
title: 'Firma paliwowa',
short: 'Integracja doświadczenia na stacjach z tożsamością marki i branding sensoryczny.',
details: [
'Integracja strategii marki ze strategią doświadczeń klienta.',
'Badania neuromarketingowe i ponad 100 rekomendacji optymalizacyjnych.',
'Wielowarstwowa propozycja wartości od tankowania po rozbudowane doświadczenie marki.',
'Identyfikacja momentów prawdy i plan zarządzania nimi.',
'Mapa ścieżek klienta z instrukcją optymalizacji.',
'Projekt atmosfery stacji (kolorystyka, zapach, układ).',
'Kompleksowy branding sensoryczny angażujący wszystkie zmysły.',
'Standardy obsługi klienta odzwierciedlające wartości marki.',
'Mapa wdrożenia z harmonogramem i wytycznymi operacyjnymi.',
],
},
{
title: 'Firma technologiczna',
short: 'Nowa architektura marki, system kompetencji i kultura klientocentryczna.',
details: [
'Zaprojektowanie struktury organizacyjnej i systemu kompetencji.',
'Redefinicja architektury marki (przejście z house of brands na branded house).',
'System zarządzania wartością klienta poziom strategiczny, operacyjny i personalny.',
'„Playbooki” wdrażające pozycjonowanie do działań codziennych.',
'Usługi o wysokiej wartości dodanej, zmieniające postrzeganie firmy.',
'Model „high tech + high touch” łączący technologie z uważnością na klienta.',
'Strategia CRM wspierająca długofalowe relacje i wzrost.',
],
},
]
// Dlaczego (treść 1:1)
const why = {
p1: (
<>
<strong className="text-neutral-900">76% konsumentów oczekuje</strong>, że marka będzie
<strong className="text-neutral-900"> przewidywać ich potrzeby</strong>, nie tylko reagować
<span className="text-neutral-500"> (Salesforce, 2023)</span>.
</>
),
p2: (
<>
Pokolenie Z <strong className="text-neutral-900">nie buduje lojalności przez reklamy</strong>.
Buduje przez <strong className="text-neutral-900">wartość, użyteczność i autentyczność</strong>.
<span className="font-semibold text-neutral-900"> 65% młodych konsumentów</span> deklaruje,
że lojalność wobec marki zależy od spójnych i realnych doświadczeń
<span className="text-neutral-500"> (McKinsey, 2023)</span>.
</>
),
bulletsIntro: 'Marka nie żyje w kampaniach. Żyje w:',
bullets: [
'chatbotach, landingach, mailach i pushach,',
'procesach sprzedażowych i onboardingowych,',
'sposobie, w jaki działa Twój model cenowy, UX i obsługa klienta.',
],
p3: (
<>
Jeśli to wszystko nie gra razem, marka <strong className="text-neutral-900">traci moc</strong>.
<br />
<strong className="text-neutral-900">I traci klientów.</strong>
<span className="text-neutral-500"> Według badania PwC 32% klientów</span> porzuca markę po jednej
złej interakcji, nawet jeśli wcześniej byli zadowoleni.
</>
),
}
const [expandedIndex, setExpandedIndex] = useState<number | null>(null);
// === NOWE tiles (podmień całą dotychczasową definicję) ===
// === KAFELKI z jednolitym punktorowaniem ===
const tiles = [
{
title: "Brand Daily Scan™ diagnoza w rytmie codzienności",
short: "Błyskawiczna analiza punktów styku, decyzji, procesów i sygnałów marki.",
output: "Mapa luk, insighty, benchmarki, rekomendacje.",
efekt:
"Mapa luk i niespójności, insighty z rzeczywistego użycia marki, benchmarki branżowe oraz rekomendacje naprawcze i wzmacniające.",
details: (
<>
<p >
Nie patrzymy na markę z dystansu. Wchodzimy w jej codzienne tętno.
Skupiamy się na tym, co naprawdę <strong>generuje doświadczenie klienta</strong>:
</p>
{/* stałe, eleganckie kropki */}
<ul className="mt-1 space-y-1">
<li className="relative pl-4 before:content-[''] before:absolute before:left-0 before:top-[0.55rem] before:w-1.5 before:h-1.5 before:rounded-full before:bg-cyan-600">
punkty styku (touchpointy),
</li>
<li className="relative pl-4 before:content-[''] before:absolute before:left-0 before:top-[0.55rem] before:w-1.5 before:h-1.5 before:rounded-full before:bg-cyan-600">
mikrodecyzje zespołów,
</li>
<li className="relative pl-4 before:content-[''] before:absolute before:left-0 before:top-[0.55rem] before:w-1.5 before:h-1.5 before:rounded-full before:bg-cyan-600">
automatyczne procesy i sygnały marki.
</li>
</ul>
</>
),
},
{
title: "Synchronizacja rytmu faza pacingu",
short: "Zgrywamy Twoje działania z logiką marki. Automatyzujemy. Personalizujemy.",
output: "Roadmapa wdrożenia z KPI i zestawem eksperymentów.",
efekt:
"„Zgrane zegary” marki, marketingu, sprzedaży i obsługi; klarowne KPI oraz szybkie testy hipotez.",
details: (
<>
<p>
Integrujemy markę z codzienną dynamiką operacyjną. Nie robimy rewolucji
<strong> synchronizujemy rytm</strong>:
</p>
{/* stałe, eleganckie kropki */}
<ul className="mt-1 space-y-1">
<li className="relative pl-4 before:content-[''] before:absolute before:left-0 before:top-[0.55rem] before:w-1.5 before:h-1.5 before:rounded-full before:bg-cyan-600">
automatyzujemy to, co można,
</li>
<li className="relative pl-4 before:content-[''] before:absolute before:left-0 before:top-[0.55rem] before:w-1.5 before:h-1.5 before:rounded-full before:bg-cyan-600">
personalizujemy to, co warto,
</li>
<li className="relative pl-4 before:content-[''] before:absolute before:left-0 before:top-[0.55rem] before:w-1.5 before:h-1.5 before:rounded-full before:bg-cyan-600">
urealniamy to, co obiecujesz jako marka.
</li>
</ul>
</>
),
},
{
title: "Wdrożenie i skalowanie",
short: "Marka w akcji: od call center i e-maili po pricing, CRM i fizyczną dostępność.",
output: "Efektywność, zaangażowanie, lojalność.",
efekt:
"Wzrost efektywności działań marketingowych, wyższe zaangażowanie użytkowników, zwiększenie lojalności i retencji — zmierzone.",
details: (
<>
<p >
Marka przestaje być tylko deklaracją. Zaczyna
<strong> działać w realnym czasie i miejscu</strong>:
</p>
{/* stałe, eleganckie kropki */}
<ul className="mt-1 space-y-1">
<li className="relative pl-4 before:content-[''] before:absolute before:left-0 before:top-[0.55rem] before:w-1.5 before:h-1.5 before:rounded-full before:bg-cyan-600">
w aplikacji i e-mailach,
</li>
<li className="relative pl-4 before:content-[''] before:absolute before:left-0 before:top-[0.55rem] before:w-1.5 before:h-1.5 before:rounded-full before:bg-cyan-600">
w polityce cenowej i mechanizmach CRM,
</li>
<li className="relative pl-4 before:content-[''] before:absolute before:left-0 before:top-[0.55rem] before:w-1.5 before:h-1.5 before:rounded-full before:bg-cyan-600">
w sklepie internetowym i na półce w punkcie sprzedaży.
</li>
</ul>
</>
),
},
];
const scrollToIntegration = useCallback(() => {
document.getElementById('DlaczegoIntegracji')?.scrollIntoView({ behavior: 'smooth' })
}, [])
return (
<div className="relative min-h-screen text-neutral-900 overflow-x-hidden">
{/* Subtelny akcent kolorystyczny + siatka */}
<BackgroundGrid />
{/* Pasek postępu scrolla */}
<ScrollProgress />
{/* NAV */}
<nav className="fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur border-b border-black/10">
<div className="max-w-7xl mx-auto px-4 h-16 flex justify-between items-center">
<a href="#PrimeCode" className="flex items-center gap-3">
<img src="/LogoPrimeCode-transparent.png" alt="Prime Code" className="h-7 w-auto" />
<span className="sr-only">Prime Code O nas</span>
</a>
<ul className="hidden md:flex gap-7 font-medium">
{navItems.map((item) => (
<li key={item.id}>
<a
href={`#${item.id}`}
className={`hover:underline underline-offset-4 ${activeSection === item.id ? 'underline decoration-2' : 'decoration-1'
}`}
>
{item.label}
</a>
</li>
))}
</ul>
<button
onClick={() => setMobileOpen(v => !v)}
aria-label={mobileOpen ? "Zamknij menu" : "Otwórz menu"}
aria-expanded={mobileOpen}
aria-controls="mobileMenu"
className="md:hidden h-10 w-10 grid place-items-center rounded-none ring-1 ring-black/15 hover:bg-black/5"
>
{mobileOpen ? <X className="size-5" /> : <Menu className="size-5" />}
</button>
</div>
</nav>
<AnimatePresence>
{mobileOpen && (
<>
{/* półprzezroczyste tło kliknięcie zamyka */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
className="fixed inset-0 z-40 bg-black/30 md:hidden"
onClick={() => setMobileOpen(false)}
/>
{/* sam panel */}
<motion.div
id="mobileMenu"
initial={{ opacity: 0, y: -8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8 }}
transition={{ duration: 0.2, ease: "easeOut" }}
className="fixed top-16 left-0 right-0 z-50 bg-white/95 backdrop-blur border-b border-black/10 md:hidden"
>
<ul className="px-2 py-2">
{navItems.map((item) => (
<li key={item.id}>
<a
href={`#${item.id}`}
onClick={() => setMobileOpen(false)}
className="block px-4 py-3 text-base hover:bg-black/5"
>
{item.label}
</a>
</li>
))}
<li className="my-1 border-t border-black/10" />
<li>
<a href="mailto:primecode@primecode.pl" className="flex items-center gap-2 px-4 py-3 hover:bg-black/5">
<Mail className="size-4" /> Email
</a>
</li>
<li>
<a href="tel:+48500133609" className="flex items-center gap-2 px-4 py-3 hover:bg-black/5">
<Phone className="size-4" /> Zadzwoń
</a>
</li>
</ul>
</motion.div>
</>
)}
</AnimatePresence>
{/* było: <HeroInkCinematic ... /> */}
<HeroPixabayBG
src="/visuals/20072-307163785_small.mp4"
poster="/visuals/pixabay_bg_poster.jpg"
onScrollDown={scrollToIntegration}
/>
{/* PRZEJŚCIE/WIPE */}
<SectionWipe />
<DlaczegoIntegracjiWarm why={why} />
{/* SYTUACJE — horyzontalne wejścia kart */}
<SytuacjeSection />
{/* PARTNER */}
<PartnerSection />
<ModelSection tiles={tiles} />
<CoPotrafimySection />
{/* JAK DZIAŁAMY */}
<RiskFreeSection />
<PomoglismySection
caseStudies={caseStudies}
openIndex={openIndex}
setOpenIndex={setOpenIndex}
/>
<CaseStudiesSection caseStudies={caseStudies} />
{/* KONTAKT */}
<section id="Kontakt" className="relative py-24 overflow-hidden">
{/* tło: stonowany niebieski gradient (mniej jaskrawy) */}
<div aria-hidden className="absolute inset-0 -z-10">
{/* główny gradient desat + ciut ciemniej */}
<div
className="absolute inset-0"
style={{
background:
"linear-gradient(100deg, #1578A1 0%, #2E6CC1 52%, #4B57C5 100%)",
}}
/>
{/* delikatne „wygaszenie” bielą tonuje bez zmiany koloru */}
<div
className="absolute inset-0"
style={{
background: "linear-gradient(0deg, rgba(255,255,255,.12), rgba(255,255,255,.12))",
}}
/>
{/* bardzo subtelne rozświetlenie jak wcześniej, ale słabsze */}
<div
className="absolute inset-0 pointer-events-none"
style={{
background:
"radial-gradient(900px 420px at 88% -120px, rgba(255,255,255,0.22), transparent 70%)",
}}
/>
{/* hairliney */}
<div className="absolute inset-x-0 top-0 h-[2px] bg-white/20 mix-blend-screen" />
<div className="absolute inset-x-0 bottom-0 h-px bg-white/25 mix-blend-screen" />
</div>
<div className="max-w-6xl mx-auto px-6 grid lg:grid-cols-[1.15fr_0.85fr] gap-12 items-center">
{/* LEWA: nagłówek + CTA (biel jak na screenie) */}
<motion.div
initial={{ opacity: 0, y: 16, filter: "blur(6px)" }}
whileInView={{ opacity: 1, y: 0, filter: "blur(0px)" }}
viewport={{ once: true, amount: 0.6 }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="text-white"
>
<h2 className="text-4xl md:text-5xl font-extrabold tracking-[-0.01em]">
Zacznij od eksperymentu
</h2>
<p className="mt-4 text-lg text-white/85 max-w-xl">
Zobacz, co marka może zrobić w 7 dni.
</p>
{/* Lokalizacje chipsy/glass */}
<div className="mt-5 flex flex-wrap items-center gap-2 text-sm">
<span className="inline-flex items-center gap-2 px-3 py-1 rounded-full
bg-white/10 ring-1 ring-white/20 backdrop-blur">
<span className="h-2 w-2 rounded-full
bg-[radial-gradient(circle,#F28241_30%,#8C2E47_95%)]" />
Warszawa
</span>
<span className="inline-flex items-center gap-2 px-3 py-1 rounded-full
bg-white/10 ring-1 ring-white/20 backdrop-blur">
<span className="h-2 w-2 rounded-full
bg-[radial-gradient(circle,#F28241_30%,#8C2E47_95%)]" />
Kraków
</span>
<span className="inline-flex items-center gap-2 px-3 py-1 rounded-full
bg-white/10 ring-1 ring-white/20 backdrop-blur">
<span className="h-2 w-2 rounded-full
bg-[radial-gradient(circle,#F28241_30%,#8C2E47_95%)]" />
Gdańsk
</span>
<span className="inline-flex items-center gap-2 px-3 py-1 rounded-full
bg-white/10 ring-1 ring-white/20 backdrop-blur">
<span className="h-2 w-2 rounded-full
bg-[radial-gradient(circle,#F28241_30%,#8C2E47_95%)]" />
Wrocław
</span>
<span className="px-2 text-white/85"> działamy zdalnie i hybrydowo</span>
</div>
{/* CTA biała i obrysowana, jak na zrzucie */}
<div className="mt-8 flex flex-wrap gap-3">
<a
href="mailto:primecode@primecode.pl"
className="inline-flex items-center gap-2 h-12 px-6 rounded bg-white text-sky-900
ring-1 ring-white/20 hover:bg-white/95 focus:outline-none focus-visible:ring-2 focus-visible:ring-white/60"
>
<Mail className="size-5" aria-hidden />
<span>Napisz maila</span>
</a>
<a
href="tel:+48500133609"
className="inline-flex items-center gap-2 h-12 px-6 rounded text-white
ring-1 ring-white/65 hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-white/70"
>
<Phone className="size-5" aria-hidden />
<span>Zadzwoń</span>
</a>
</div>
</motion.div>
{/* PRAWA: karta kontaktowa jasna, z cieniem (jak na screenie) */}
<motion.aside
initial={{ opacity: 0, y: 18, scale: 0.985, filter: "blur(6px)" }}
whileInView={{ opacity: 1, y: 0, scale: 1, filter: "blur(0px)" }}
viewport={{ once: true, amount: 0.6 }}
transition={{ duration: 0.8, ease: [0.22, 1, 0.36, 1], delay: 0.05 }}
className="relative bg-white text-neutral-800 rounded-md p-6 lg:p-7 shadow-[0_14px_40px_rgba(0,0,0,0.18)] ring-1 ring-black/5"
>
{/* cienka chłodna linia u góry karty (bardzo subtelna) */}
<div aria-hidden className="absolute inset-x-0 top-0 h-[2px] bg-gradient-to-r from-sky-300 via-blue-400 to-indigo-500 rounded-t-md" />
<div className="flex items-center gap-4">
<img
src="/jacek.jpg"
alt="Jacek Pogorzelski Prime Code"
className="w-16 h-16 object-cover rounded-full"
/>
<div>
<a
href="https://jacekpogorzelski.pl"
target="_blank"
rel="noopener"
className="text-lg font-semibold underline underline-offset-4 text-sky-800"
>
Jacek Pogorzelski
</a>
<p className="text-sm text-neutral-600 mt-0.5">Prime Code</p>
</div>
</div>
{/* Mail i telefon osobne linie z ikonkami */}
<div className="mt-5 flex flex-col gap-2 text-[15px]">
<a href="mailto:primecode@primecode.pl" className="flex items-center gap-2 hover:underline">
<Mail className="size-4 text-sky-600" aria-hidden />
<span>primecode@primecode.pl</span>
</a>
<a href="tel:+48500133609" className="flex items-center gap-2 hover:underline">
<Phone className="size-4 text-sky-600" aria-hidden />
<span>+48 500 133 609</span>
</a>
</div>
</motion.aside>
</div>
</section>
{/* FOOTER */}
<footer className="bg-white border-t border-black/10">
<div className="max-w-6xl mx-auto px-6 py-8">
<p className="text-center text-sm text-neutral-500">© 2025 Prime Code</p>
</div>
</footer>
{/* QUICK CONTACT */}
<div
aria-label="Szybki kontakt"
className="fixed z-50 bottom-4 right-4 sm:right-6 flex items-center gap-3 bg-white/95 backdrop-blur ring-1 ring-black/10 px-3 py-2"
>
<img src="/jacek.jpg" alt="Jacek Pogorzelski" className="w-7 h-7 rounded-full object-cover" />
<a href="mailto:primecode@primecode.pl" className="text-sm underline underline-offset-4">
Email
</a>
<span className="text-neutral-300"></span>
<a href="tel:+48500133609" className="text-sm underline underline-offset-4">
Zadzwoń
</a>
</div>
</div>
)
}
/* ============================ VISUAL LAYER ============================ */
function BackgroundGrid() {
// pastelowy akcent + siatka + bardzo delikatny „glow” od góry
return (
<div aria-hidden className="fixed inset-0 -z-10 bg-white">
<div className="absolute inset-0 opacity-[0.05] [background-image:repeating-linear-gradient(0deg,transparent,transparent_23px,rgba(0,0,0,.08)_24px),repeating-linear-gradient(90deg,transparent,transparent_23px,rgba(0,0,0,.08)_24px)]" />
<div className="absolute inset-0 pointer-events-none [background:radial-gradient(800px_360px_at_50%_-120px,rgba(6,182,212,.18),transparent_70%)]" />
</div>
)
}
function ScrollProgress() {
const { scrollYProgress } = useScroll()
const scaleX = useTransform(scrollYProgress, [0, 1], [0, 1])
return (
<motion.div
style={{ scaleX }}
className="fixed top-0 left-0 right-0 h-[3px] origin-left z-[60] bg-gradient-to-r from-cyan-500 to-blue-500"
/>
)
}
/** Miękki „wipe” między sekcjami (kolorowy, ale subtelny) */
function SectionWipe() {
return (
<div aria-hidden className="relative h-10 -mt-10">
<div className="sticky top-10 h-10 bg-gradient-to-b from-transparent via-white to-white [mask-image:linear-gradient(to_bottom,transparent,black)]" />
<div className="absolute inset-0 -z-10 bg-gradient-to-r from-cyan-400/20 via-transparent to-blue-400/20" />
</div>
)
}
function Header({
title,
className = "",
gradient = "none",
}: {
title: string;
className?: string;
// --- w sygnaturze Header:
gradient?: "none" | "warm" | "cool" | "coolSoft" | "sunset" | "bloom" | "bloomSoft" | "bloomReverse" | "prime" | "primeSoft" | "primeReverse";
}) {
const TEXT = {
warm: "text-transparent bg-clip-text bg-[linear-gradient(90deg,#FDBA74_0%,#F97316_55%,#F472B6_100%)]",
cool: "text-transparent bg-clip-text bg-[linear-gradient(90deg,#0284C7_0%,#2563EB_55%,#4F46E5_100%)]",
coolSoft: "text-transparent bg-clip-text bg-[linear-gradient(90deg,#38BDF8_0%,#3B82F6_55%,#6366F1_100%)]",
sunset: "text-transparent bg-clip-text bg-[linear-gradient(90deg,#FF7A00_0%,#FF3D81_70%)]",
// NOWE — motyw PRIME (Twoje kolory)
prime:
"text-transparent bg-clip-text bg-[linear-gradient(90deg,#F28241_0%,#CC2E47_100%)]",
primeSoft:
// delikatniejsza, nadal kończy się #8C2E47 jako najciemniejszym
"text-transparent bg-clip-text bg-[linear-gradient(90deg,#F7B891_0%,#F28241_45%,#8C2E47_100%)]",
primeReverse:
// jeśli potrzebujesz odwróconego kierunku, też kończymy #8C2E47
"text-transparent bg-clip-text bg-[linear-gradient(90deg,#F28241_0%,#8C2E47_100%)]",
// Twoja wersja
bloom:
"text-transparent bg-clip-text bg-[linear-gradient(90deg,#F59E0B,#F97316)]",
// Subtelniejsza na białym tle (lekko rozjaśnione odcienie)
bloomSoft:
"text-transparent bg-clip-text bg-[linear-gradient(90deg,#0E3E59_0%,#E68A56_40%,#96402A_56%,#C96A58_78%,#9A3A56_100%)]",
// Odwrócony kierunek (ciepłe → chłodne)
bloomReverse:
"text-transparent bg-clip-text bg-[linear-gradient(90deg,#8C2E47_0%,#BF533B_24%,#731702_45%,#F28241_70%,#083A59_100%)]",
none: "",
} as const;
const RULE = {
warm: "linear-gradient(90deg,rgba(253,186,116,.9),rgba(249,115,22,.9),rgba(244,114,182,.9))",
cool: "linear-gradient(90deg,rgba(2,132,199,.9),rgba(37,99,235,.9),rgba(79,70,229,.9))",
coolSoft: "linear-gradient(90deg,rgba(56,189,248,.9),rgba(59,130,246,.9),rgba(99,102,241,.9))",
sunset: "linear-gradient(90deg,#FF7A00,#FF3D81)",
bloom: "linear-gradient(90deg,#8C2E47,#083A59,#F28241,#731702,#BF533B)",
bloomSoft: "linear-gradient(90deg,#0E3E59,#E68A56,#96402A,#C96A58,#9A3A56)",
bloomReverse: "linear-gradient(90deg,#8C2E47,#BF533B,#731702,#F28241,#083A59)",
none: "linear-gradient(90deg,rgba(0,0,0,.12),rgba(0,0,0,.12))",
// NOWE — motyw PRIME (Twoje kolory)
prime: "linear-gradient(90deg,#F28241,#8C2E47)",
primeSoft: "linear-gradient(90deg,#F7B891,#F28241,#8C2E47)",
primeReverse: "linear-gradient(90deg,#F28241,#8C2E47)",
} as const;
return (
<header className="max-w-6xl mx-auto px-6 mb-8">
<h2
className={[
"inline-block",
"text-4xl md:text-5xl font-extrabold tracking-[-0.01em] antialiased",
"leading-[1.18] md:leading-[1.2] pt-[2px] pb-[6px]",
TEXT[gradient],
className,
].join(" ")}
>
{title}
</h2>
{/* <div className="mt-6 h-[3px] rounded-full [mask-image:linear-gradient(90deg,transparent,black_10%,black_90%,transparent)]" style={{ background: RULE[gradient] }} /> */}
</header>
);
}
function BackgroundMedia({
src,
poster,
sectionRef, // zostawiamy sygnaturę, ale hooka nie używamy
}: {
src: string;
poster?: string;
sectionRef: React.RefObject<HTMLElement | null>;
}) {
return (
<div className="absolute inset-0" aria-hidden>
<video
className="absolute inset-0 w-full h-full object-cover object-[60%_45%] bg-video"
autoPlay
loop
muted
playsInline
preload="metadata"
poster={poster}
>
<source src={src} type="video/mp4" />
</video>
{/* delikatne overlaye jak wcześniej */}
<div className="absolute inset-0 pointer-events-none">
<div className="absolute inset-0 bg-gradient-to-r from-black/45 via-black/10 to-transparent" />
<div className="absolute inset-0 bg-gradient-to-b from-black/10 via-transparent to-black/12" />
</div>
</div>
);
}
type HeroPixabayBGProps = {
onScrollDown: () => void;
src?: string;
poster?: string;
};
function HeroPixabayBG({
onScrollDown,
src = "/visuals/20072-307163785_small.mp4",
poster = "/visuals/pixabay_bg_poster.jpg",
}: HeroPixabayBGProps) {
const sectionRef = React.useRef<HTMLElement | null>(null);
// 1) fail-safe: po montażu i tak pokaż treść (gdyby iOS nie wyzwolił IO)
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => setMounted(true), []);
// 2) parallax
const { scrollYProgress } = useScroll({ target: sectionRef, offset: ["start start", "end start"] });
const titleY = useTransform(scrollYProgress, [0, 1], ["0%", "-6%"]);
const canParallax = mounted;
// 3) łagodniejszy trigger + margines
const seen = useInView(sectionRef, { once: true, amount: 0.3, margin: "-10% 0px -10% 0px" });
// 4) jeśli widoczny LUB zamontowany pokazuj (koniec „pustki” na starcie)
const ready = seen || mounted;
// --- warianty ---
const h1V: Variants = {
hidden: { opacity: 0, y: 40, filter: "blur(4px)" },
show: { opacity: 1, y: 0, filter: "blur(0px)", transition: { duration: 1.2, ease: [0.22, 1, 0.36, 1] } },
};
const stackHero: Variants = {
hidden: { opacity: 0, y: 14 },
show: {
opacity: 1, y: 0,
transition: { duration: 0.8, ease: "easeOut", when: "beforeChildren", delayChildren: 0.3, staggerChildren: 0.22 },
},
};
const itemHero: Variants = {
hidden: { opacity: 0, y: 16 },
show: { opacity: 1, y: 0, transition: { duration: 0.85, ease: "easeOut" } },
};
const ruleHero: Variants = {
hidden: { opacity: 0, scaleX: 0 },
show: { opacity: 1, scaleX: 1, transition: { duration: 0.95, ease: "easeOut" } },
};
return (
<section
id="PrimeCode"
ref={sectionRef}
className="relative min-h-[100dvh] md:min-h-[calc(100svh-4rem)] isolate scroll-mt-16" // ← dvh na mobile
>
<div className="md:sticky md:top-16 md:min-h-[calc(100svh-4rem)]">
<BackgroundMedia src={src} poster={poster} sectionRef={sectionRef} />
<MotionConfig reducedMotion="never">
<div className="relative z-10 flex items-start md:items-start h-full text-white py-20 sm:py-24 md:pt-12 md:pb-60">
<div className="max-w-7xl mx-auto w-full px-4 sm:px-6 grid grid-cols-1 md:grid-cols-[1.12fr_0.88fr] gap-8 sm:gap-12 items-start md:items-center">
<motion.div
style={canParallax ? { y: titleY, willChange: "transform, opacity" } : undefined}
variants={h1V}
initial="hidden"
animate={ready ? "show" : "hidden"}
className="relative w-fit select-none pointer-events-none mb-5 sm:mb-6 md:mb-2 md:mt-10 lg:mt-14 md:min-h-[90px] mx-auto justify-self-center md:justify-self-start md:mx-0">
{/* WŁAŚCIWE LOGO większe i wyśrodkowane na mobile, z offsetem w lewo od md */}
<img
src="/LogoPrimeCode-transparent.png"
alt=""
className="
h-auto
/* mobile tak jak było: duże i czytelne */
w-[clamp(300px,88vw,760px)]
sm:w-[clamp(340px,80vw,780px)]
/* md WYRAŹNIE większe (zawsze większe niż blok H2) */
md:w-[clamp(420px,48vw,680px)]
lg:w-[clamp(540px,46vw,760px)]
xl:w-[clamp(600px,44vw,820px)]
/* wyrównanie do lewego brzegu copy */
mx-auto md:mx-0
md:-ml-5 lg:-ml-6 xl:-ml-8
brightness-0 invert drop-shadow-[0_8px_22px_rgba(0,0,0,.45)]
"
draggable={false}
/>
</motion.div>
<h1 className="sr-only">Prime Code</h1>
{/* === COPY (z mobilnym scrimem) === */}
<motion.div
variants={stackHero}
initial="hidden"
animate={ready ? "show" : "hidden"}
transition={{ delay: 0.25 }}
className="md:col-start-1 md:row-start-2"
style={{ willChange: "transform, opacity" }}
>
{/* wrapper: tło pod tekstem tylko na mobile */}
<div className="relative md:p-0 p-4 md:mx-0 -mx-2 md:rounded-none rounded-2xl">
{/* scrim (tylko mobile) */}
<div
aria-hidden
className="md:hidden absolute inset-0 z-0
bg-black/10 backdrop-blur-[1.5px]"
/>
{/* właściwa treść */}
<div className="relative z-10">
<motion.h2 variants={itemHero} className="mt-2 text-3xl md:text-4xl font-semibold drop-shadow-[0_2px_8px_rgba(0,0,0,.55)]">
Integrujemy markę w codziennych działaniach
</motion.h2>
<motion.p variants={itemHero} className="mt-4 text-xl md:text-2xl text-white/95 drop-shadow-[0_2px_6px_rgba(0,0,0,.55)]">
Marka, która działa codziennie. W czasie rzeczywistym. W każdym punkcie styku.
</motion.p>
<motion.div variants={ruleHero} className="mt-6 h-[3px] w-28 origin-left bg-gradient-to-r from-cyan-400 to-blue-500" />
<motion.p variants={itemHero} className="max-w-3xl text-lg md:text-xl text-white/92 leading-relaxed mt-6 drop-shadow-[0_2px_6px_rgba(0,0,0,.55)]">
W Prime Code integrujemy markę z tym, co najważniejsze realnymi działaniami, danymi,
technologią i zespołami. Nie tworzymy kampanii dla samej widoczności. Projektujemy
<strong> mechanizmy wzrostu</strong>, w których marka staje się fundamentem
operacyjnym: w komunikacji, sprzedaży, doświadczeniu klienta, polityce cenowej i kanałach dystrybucji.
</motion.p>
<motion.p variants={itemHero} className="mt-4 max-w-3xl text-lg md:text-xl text-white/92 leading-relaxed drop-shadow-[0_2px_6px_rgba(0,0,0,.55)]">
Bo marka <strong>to nie tylko opowieść.</strong> To system. Aktywny, skalowalny, zsynchronizowany z organizacją.
</motion.p>
</div>
</div>
</motion.div>
</div>
<button
onClick={onScrollDown}
aria-label="Przewiń"
className="absolute bottom-8 left-1/2 -translate-x-1/2 text-white/90"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-9 w-9" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
</div>
</MotionConfig>
</div >
</section >
);
}
// H1: wolno, z lekkim blur-em
const h1Slow: Variants = {
hidden: { opacity: 0, y: 30, filter: "blur(3px)" },
show: {
opacity: 1,
y: 0,
filter: "blur(0px)",
transition: { duration: 1.15, ease: [0.16, 1, 0.3, 1] },
},
};
// Kontener reszty treści (start po H1); w środku będzie stagger
const stack: Variants = {
hidden: { opacity: 0, y: 12 },
show: {
opacity: 1,
y: 0,
transition: {
duration: 0.75,
ease: "easeOut",
when: "beforeChildren",
// Stagger wolniejszy rytm
staggerChildren: 0.18,
delayChildren: 0.12,
},
},
};
// Pojedyncze elementy treści (H2, lead, akapity)
const itemSlow: Variants = {
hidden: { opacity: 0, y: 12 },
show: {
opacity: 1,
y: 0,
transition: { duration: 0.7, ease: "easeOut" },
},
};
// Niebieska linia pod leadem ładne „narysowanie”
// było: duration ~1.2, x:56
const itemV: Variants = {
hidden: (fromLeft: boolean) => ({
opacity: 0,
x: fromLeft ? -72 : 72,
y: 8,
filter: "blur(8px)",
}),
show: {
opacity: 1,
x: 0,
y: 0,
filter: "blur(0px)",
transition: { duration: 1.45, ease: [0.22, 1, 0.36, 1] }, // wolniej
},
};
type WhyContent = {
p1: React.ReactElement;
p2: React.ReactElement;
bulletsIntro: string;
bullets: string[];
p3: React.ReactElement;
};
function DlaczegoIntegracjiWarm({ why }: { why: WhyContent }) {
const items: React.ReactNode[] = [
why.p1,
why.p2,
(
<div className="text-[1.05rem] md:text-[1.15rem] leading-relaxed">
<p className="font-medium">{why.bulletsIntro}</p>
<ul className="mt-3 list-disc pl-6 space-y-2">
{why.bullets.map((b, i) => (
<li key={i}>{b}</li>
))}
</ul>
</div>
),
why.p3,
];
// ——— Tytuł: powolne wejście + delikatny parallax kursorem ———
const titleRef = React.useRef<HTMLDivElement | null>(null);
const mx = useMotionValue(0);
const my = useMotionValue(0);
const onMoveTitle = (e: React.PointerEvent<HTMLDivElement>) => {
const r = titleRef.current?.getBoundingClientRect();
if (!r) return;
mx.set((e.clientX - (r.left + r.width / 2)) / r.width);
my.set((e.clientY - (r.top + r.height / 2)) / r.height);
};
const onLeaveTitle = () => { mx.set(0); my.set(0); };
const titleV: Variants = {
hidden: { opacity: 0, y: 22, filter: "blur(6px)" },
show: {
opacity: 1,
y: 0,
filter: "blur(0px)",
transition: { duration: 1.4, ease: [0.22, 1, 0.36, 1] },
},
};
const GRAD_H_L = "linear-gradient(90deg, rgba(253,186,116,1) 0%, rgba(249,115,22,1) 55%, rgba(244,114,182,1) 100%)";
const GRAD_H_R = "linear-gradient(270deg, rgba(253,186,116,1) 0%, rgba(249,115,22,1) 55%, rgba(244,114,182,1) 100%)";
const GRAD_V = "linear-gradient(180deg, rgba(253,186,116,1) 0%, rgba(249,115,22,1) 55%, rgba(244,114,182,1) 100%)";
const itemV: Variants = {
hidden: (fromLeft: boolean) => ({ opacity: 0, x: fromLeft ? -64 : 64, y: 8, filter: "blur(8px)" }),
show: { opacity: 1, x: 0, y: 0, filter: "blur(0px)", transition: { duration: 1.3, ease: [0.22, 1, 0.36, 1] } },
};
return (
<section id="DlaczegoIntegracji" className="relative py-28">
<header className="max-w-6xl mx-auto px-6">
<motion.div
ref={titleRef}
onPointerMove={onMoveTitle}
onPointerLeave={onLeaveTitle}
>
<motion.h2
variants={titleV}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.55, margin: "-8% 0px -12% 0px" }} // ↓↓↓ KLUCZOWA ZMIANA
className={`${headingFont.className}
inline-block
text-[clamp(2rem,6vw,3.2rem)]
font-extrabold tracking-[-0.02em]
leading-[1.18]
pt-[2px] pb-[6px]
text-transparent bg-clip-text
bg-[linear-gradient(90deg,#F28241_0%,#CC2E47_100%)]
`}
>
Dlaczego integracja marki to konieczność?
</motion.h2>
</motion.div>
{/* <FancySeparator className="mt-5 max-w-4xl" /> */}
</header>
<div className="relative max-w-4xl mx-auto px-6 mt-12">
<div className="space-y-16">
{items.map((node, i) => {
const fromLeft = i % 2 === 0;
return (
<motion.article
key={i}
custom={fromLeft}
variants={itemV}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.3, margin: "-6% 0px -14% 0px" }} // ↓↓↓ KLUCZOWA ZMIANA
className={fromLeft ? "md:pl-6" : "md:pr-6"}
>
<BracketCorner
side={fromLeft ? "left" : "right"}
gradH={fromLeft ? GRAD_H_L : GRAD_H_R}
gradV={GRAD_V}
/>
<div className="relative text-neutral-900 text-[1.07rem] md:text-[1.18rem] leading-relaxed pt-3">
{node}
</div>
</motion.article>
);
})}
</div>
</div>
</section>
);
}
/* narożnik w kształcie „L” (po lewej lub prawej), rysowany bez zwiększania box height */
function BracketCorner({
side,
gradH,
gradV,
}: {
side: "left" | "right";
gradH: string;
gradV: string;
}) {
const vLineV: Variants = {
hidden: { opacity: 0.9, scaleY: 0 },
show: { opacity: 1, scaleY: 1, transition: { duration: 1.15, ease: "easeOut" } },
};
const hLineV: Variants = {
hidden: { opacity: 0.9, scaleX: 0 },
show: { opacity: 1, scaleX: 1, transition: { duration: 1.15, ease: "easeOut", delay: 0.05 } },
};
return (
<div aria-hidden className="pointer-events-none absolute inset-0">
{/* pionowa kreska */}
<motion.div
variants={vLineV}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.95 }}
className={`absolute top-0 ${side === "left"
? "left-[-10px] md:-left-4 origin-top" // ↓↓↓ KLUCZOWA ZMIANA
: "right-[-10px] md:-right-4 origin-top" // ↓↓↓ KLUCZOWA ZMIANA
} w-[3px] h-8 md:h-10 rounded-full`}
style={{ background: gradV }}
/>
{/* pozioma kreska */}
<motion.div
variants={hLineV}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.95 }}
className={`absolute top-0 h-[3px] w-24 md:w-28 rounded-full ${side === "left"
? "left-[-10px] md:-left-4 origin-left" // ↓↓↓ KLUCZOWA ZMIANA
: "right-[-10px] md:-right-4 origin-right" // ↓↓↓ KLUCZOWA ZMIANA
}`}
style={{ background: gradH }}
/>
</div>
);
}
// function FancySeparator({ className = "" }: { className?: string }) {
// return (
// <div className={`relative w-full ${className}`}>
// {/* główna linia (gradient, przygaszone końce) */}
// <div
// className="h-[2px] w-full rounded-full"
// style={{
// background:
// "linear-gradient(90deg, rgba(253,186,116,1) 0%, rgba(249,115,22,1) 55%, rgba(244,114,182,1) 100%)",
// WebkitMaskImage:
// "linear-gradient(90deg, transparent 0, black 8%, black 92%, transparent 100%)",
// maskImage:
// "linear-gradient(90deg, transparent 0, black 8%, black 92%, transparent 100%)",
// }}
// />
// {/* subtelny highlight nad linią */}
// <div
// className="absolute inset-0 -translate-y-[2px] h-[1px] bg-white/60 mix-blend-screen"
// style={{
// WebkitMaskImage:
// "linear-gradient(90deg, transparent 0, black 10%, black 90%, transparent 100%)",
// maskImage:
// "linear-gradient(90deg, transparent 0, black 10%, black 90%, transparent 100%)",
// }}
// />
// {/* delikatny glow */}
// <div
// className="pointer-events-none absolute -inset-x-1 -inset-y-[6px] blur-md"
// style={{
// background:
// "linear-gradient(90deg, rgba(253,186,116,.18), rgba(249,115,22,.18), rgba(244,114,182,.18))",
// WebkitMaskImage:
// "linear-gradient(90deg, transparent 0, black 12%, black 88%, transparent 100%)",
// maskImage:
// "linear-gradient(90deg, transparent 0, black 12%, black 88%, transparent 100%)",
// }}
// />
// </div>
// );
// }
/* ======================= CZY ZNASZ TE SYTUACJE? (nowa wersja) ======================= */
// === ANIMACJE SEKCJI "SYTUACJE" ===
function SytuacjeSection() {
// warianty: nagłówek i siatka wchodzą dopiero przy wejściu w viewport
const sectionV: Variants = {
hidden: { opacity: 0, y: 18, filter: 'blur(6px)' },
show: {
opacity: 1,
y: 0,
filter: 'blur(0px)',
transition: { duration: 0.55, ease: 'easeOut' },
},
};
// siatka tylko steruje staggerem dzieci
const gridV: Variants = {
hidden: { opacity: 1 }, // niewidoczność trzymamy na kartach
show: {
opacity: 1,
transition: {
delayChildren: 0.12,
staggerChildren: 0.08,
ease: 'easeOut',
},
},
};
const items = [
{
title: 'Brak spójności w komunikacji',
text: 'Różne działy (np. marketing, sprzedaż, obsługa klienta) komunikują się w różnym tonie i z odmiennym przekazem.',
Icon: MessageSquare,
accent: 'from-sky-300 to-blue-500', // było: from-sky-400 to-blue-500
},
{
title: 'Realizacja działań promocyjnych bez odniesienia do tożsamości marki',
text: 'Firma wdraża modne działania (np. TikTok, influencerzy, AI copywriting) bez filtrowania ich przez tożsamość i wartości marki.',
Icon: Megaphone,
accent: 'from-cyan-300 to-sky-500', // było: from-cyan-400 to-sky-600
},
{
title: 'Problemy w skalowaniu działań',
text: 'Przy szybkim wzroście lub ekspansji różne zespoły zaczynają prowadzić działania niespójne z tożsamością marki.',
Icon: LineChart,
accent: 'from-blue-300 to-indigo-500', // było: from-blue-400 to-indigo-600
},
{
title: 'Zmiany w zespole / outsourcing marketingu',
text: 'Nowe osoby lub agencje marketingowe nie rozumieją w pełni marki i działają według własnych schematów.',
Icon: Users,
accent: 'from-sky-300 to-blue-500',
},
{
title: 'Brak narzędzi lub procesów ułatwiających pracę z marką',
text: 'Pracownicy nie mają dostępu do aktualnych wytycznych (brand plan, key visuals, archetypy, kroki milowe).',
Icon: Wrench,
accent: 'from-cyan-300 to-blue-500', // łagodniej
},
{
title: 'Brak mierzalnego modelu integracji marki',
text: 'Firma nie ma sposobu, by sprawdzić, czy kampania, post czy oferta są zgodne z marką.',
Icon: ClipboardCheck,
accent: 'from-sky-300 to-indigo-500',
},
];
return (
<section id="Sytuacje" className="relative py-24">
{/* tło + delikatne rozświetlenie na wejściu */}
<SytuacjeBackground />
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true, amount: 0.25 }}
transition={{ duration: 0.5, ease: 'easeOut' }}
className="absolute inset-0 -z-10"
/>
{/* nagłówek wsuwa się miękko przy wejściu w viewport */}
<motion.div
variants={sectionV}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.55 }}
>
<Header
title="Czy znasz te sytuacje?"
gradient="coolSoft"
className={sytuacjeFont.className}
/>
</motion.div>
{/* siatka karty z kaskadą */}
<motion.div
variants={gridV}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.35 }}
className="max-w-6xl mx-auto px-6 grid gap-5 md:grid-cols-2 lg:grid-cols-3 items-stretch"
>
{items.map((it, i) => (
<SituationCardLite
key={i}
index={i}
title={it.title}
text={it.text}
Icon={it.Icon}
accent={it.accent}
/>
))}
</motion.div>
</section>
);
}
function SituationCardLite({
index,
title,
text,
Icon,
accent,
}: {
index: number;
title: string;
text: string;
Icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
accent: string;
}) {
const fromLeft = index % 2 === 0;
// wariant pojedynczej karty krótkie i miękkie wejście
const cardV: Variants = {
hidden: {
opacity: 0,
y: 12,
x: fromLeft ? -6 : 6,
scale: 0.985,
filter: 'blur(4px)',
},
show: {
opacity: 1,
y: 0,
x: 0,
scale: 1,
filter: 'blur(0px)',
transition: { duration: 0.45, ease: 'easeOut' },
},
};
return (
<motion.article
variants={cardV}
className="h-full relative overflow-hidden rounded-none bg-white ring-1 ring-black/8 hover:ring-black/10 transition
shadow-none hover:shadow-[0_8px_24px_rgba(16,24,40,0.06)]"
>
{/* cienka linia akcentowa u góry */}
<div className={`absolute inset-x-0 top-0 h-[2px] bg-gradient-to-r ${accent}`} />
{/* ultra-delikatny pattern w tle */}
<div className="absolute inset-0 pointer-events-none opacity-[0.04]
[background-image:radial-gradient(#0ea5e9_1px,transparent_1px)]
[background-size:16px_16px]" />
<div className="p-5">
<div className="grid grid-cols-[2.25rem_1fr] items-start gap-3
min-h-[5.6rem] md:min-h-[4rem]">
<div className={`shrink-0 grid place-items-center h-9 w-9 rounded-full bg-gradient-to-br ${accent} text-white`}>
<Icon className="h-[18px] w-[18px]" aria-hidden />
</div>
<h3 className="text-[1.02rem] font-semibold leading-[1.3]">
{title}
</h3>
</div>
<p className="mt-2 text-[0.94rem] leading-relaxed text-neutral-700">{text}</p>
</div>
</motion.article>
);
}
/* --------------------------- Płynące tło (scroll) --------------------------- */
/* ---------------------- TŁO SEKCJI: siatka + belka ---------------------- */
/* ---------------------- TŁO SEKCJI: siatka + ciepło/zimny split + belka ---------------------- */
function SytuacjeBackground() {
const ref = React.useRef<HTMLDivElement | null>(null);
const { scrollYProgress } = useScroll({ target: ref, offset: ['start end', 'end start'] });
const prefersReduced = useReducedMotion();
// >>> ważne: dopiero po mountcie włączamy x/y
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => setMounted(true), []);
const y = useTransform(scrollYProgress, [0, 1], prefersReduced ? [0, 0] : [8, -8]);
const x = useTransform(scrollYProgress, [0, 1], prefersReduced ? [0, 0] : [-6, 6]);
return (
<div ref={ref} aria-hidden className="absolute inset-0 -z-10 overflow-visible">
<div
className="absolute inset-0"
style={{ background: 'linear-gradient(to bottom, #fafbfd 0%, #f4f6fb 35%, #eef2f9 65%, #f7f9fd 100%)' }}
/>
<motion.div
// suppressHydrationWarning nie jest konieczne, ale pomaga gdyby devtools nadal krzyczał
suppressHydrationWarning
className="absolute inset-0 pointer-events-none"
style={{
// przed mountem: brak translacji (SSR = CSR)
x: mounted ? x : 0,
y: mounted ? y : 0,
background: [
'radial-gradient(560px 280px at 18% 12%, rgba(255,255,255,.40), transparent 68%)',
'radial-gradient(640px 320px at 80% 18%, rgba(255,255,255,.35), transparent 70%)',
'radial-gradient(700px 360px at 72% 82%, rgba(255,255,255,.30), transparent 70%)',
].join(','),
}}
/>
<div className="absolute inset-x-0 -top-8 h-8 pointer-events-none"
style={{ background: 'linear-gradient(to bottom, #ffffff, rgba(255,255,255,0))' }} />
<div className="absolute inset-x-0 -bottom-8 h-8 pointer-events-none"
style={{ background: 'linear-gradient(to top, #ffffff, rgba(255,255,255,0))' }} />
<div className="absolute inset-0 pointer-events-none"
style={{
WebkitMaskImage: 'linear-gradient(to bottom, transparent 0%, black 2.5%, black 97.5%, transparent 100%)',
maskImage: 'linear-gradient(to bottom, transparent 0%, black 2.5%, black 97.5%, transparent 100%)',
}}
/>
</div>
);
}
// --- PartnerSection: nagłówek + wcześniejszy start listy ---
function PartnerSection() {
const items = [
'myśli procesami, nie kanałami?',
'łączy branding z danymi, CX i automatyzacją?',
'projektuje mechanizmy wzrostu, a nie tylko „fajny content”?',
'wdraża AI w obsłudze marki, zamiast tylko mówić o trendach?',
'testuje i optymalizuje, zamiast zgadywać?',
];
const headingV: Variants = {
hidden: { opacity: 0, y: 14, filter: 'blur(6px)' },
show: { opacity: 1, y: 0, filter: 'blur(0px)', transition: { duration: 0.6, ease: [0.22, 1, 0.36, 1] } },
};
return (
<section id="Partner" className="relative py-24 bg-white">
{/* Kreski w tle — ważne: z-0 (nad tłem sekcji), pointer-events-none */}
{/* <FlowThread className="hidden md:block z-0 pointer-events-none" /> */}
{/* Treść nad kreskami */}
<div className="relative z-10">
<motion.div
variants={headingV}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.95 }}
>
<Header title="Potrzebujesz partnera, który:" gradient="prime" />
</motion.div>
<div className="max-w-5xl mx-auto px-6">
<NumberedList
items={items}
viewportMargin="0px 0px 22% 0px"
startDelay={0.08}
stagger={0.24}
itemDuration={0.9}
/>
</div>
</div>
</section>
);
}
// --- NumberedList: kontrola wcześniejszego wyzwalania + tempa ---
function NumberedList({
items,
viewportMargin = '0px',
startDelay = 0.02,
stagger = 0.24,
itemDuration = 0.9,
}: {
items: string[];
viewportMargin?: string;
startDelay?: number;
stagger?: number;
itemDuration?: number;
}) {
return (
// NumberedList — wymagaj większej części elementu w kadrze
<motion.ol
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.25, margin: viewportMargin }} // było: amount 0.10
variants={{
hidden: {},
show: { transition: { delayChildren: startDelay, staggerChildren: stagger } },
}}
className="divide-y divide-neutral-200"
>
{items.map((t, i) => (
<NumberedRow key={i} index={i} text={t} duration={itemDuration} />
))}
</motion.ol>
);
}
// --- NumberedRow: wolniejsze pojedyncze wejście (bez zmiany kolorów punktów) ---
function NumberedRow({ index, text, duration }: { index: number; text: string; duration: number }) {
const n = String(index + 1).padStart(2, '0');
const [first, ...rest] = text.split(' ');
const restText = rest.join(' ');
const itemV = {
hidden: { opacity: 0, y: 14, filter: 'blur(6px)' },
show: { opacity: 1, y: 0, filter: 'blur(0px)', transition: { duration, ease: [0.22, 1, 0.36, 1] } },
} as const;
return (
<motion.li variants={itemV} className="relative grid grid-cols-[82px_1fr] md:grid-cols-[110px_1fr] items-baseline py-6">
<div className="relative">
<motion.span
initial={{ opacity: 0, y: 10, scale: 0.97 }}
whileInView={{ opacity: 1, y: 0, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: duration * 0.8, ease: 'easeOut' }}
className="block select-none tabular-nums font-extrabold leading-none
text-transparent bg-clip-text
bg-[linear-gradient(180deg,#FDBA74_0%,#F97316_45%,#F472B6_100%)]
text-[3.2rem] md:text-[4.4rem]"
aria-hidden
>
{n}
</motion.span>
</div>
<div className="relative">
<div className="flex flex-wrap items-baseline gap-x-3">
<span className="text-2xl md:text-3xl font-extrabold tracking-[-0.01em]">{first}</span>
<span className="text-[1.05rem] md:text-[1.15rem] text-neutral-800">{restText}</span>
</div>
<motion.div
initial={{ scaleX: 0, opacity: 0 }}
whileInView={{ scaleX: 1, opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: duration * 0.7, ease: 'easeOut', delay: 0.05 }}
className="mt-3 h-[2px] origin-left rounded-full
bg-[linear-gradient(90deg,rgba(253,186,116,.85),rgba(249,115,22,.85),rgba(244,114,182,.85))]
[mask-image:linear-gradient(90deg,transparent,black_12%,black_88%,transparent)]"
/>
</div>
</motion.li>
);
}
/* ======================= MODEL PRIME CODE — fixed ======================= */
type ModelTile = {
title: string;
short: string;
output: string;
efekt: string;
details: React.ReactNode;
};
function ModelSection({ tiles }: { tiles: ModelTile[] }) {
const [openIndex, setOpenIndex] = useState<number | null>(null); // <- tylko jedna otwarta
const headingV: Variants = {
hidden: { opacity: 0, y: 18, filter: 'blur(6px)' },
show: { opacity: 1, y: 0, filter: 'blur(0px)', transition: { duration: 0.55, ease: 'easeOut' } },
};
const gridV: Variants = {
hidden: { opacity: 1 },
show: { opacity: 1, transition: { delayChildren: 0.12, staggerChildren: 0.08, ease: 'easeOut' } },
};
return (
<section id="Model" className="relative py-24">
<RiskFreeBackground />
<motion.div variants={headingV} initial="hidden" whileInView="show" viewport={{ once: true, amount: 0.55 }}>
<Header
title="Model Prime Code: marka jako system operacyjny"
gradient="coolSoft"
className={sytuacjeFont.className}
/>
<div className="max-w-6xl mx-auto px-6">
<p className="text-lg text-neutral-700 max-w-3xl">
W klasycznym ujęciu marka to obietnica. W dzisiejszej rzeczywistości to{' '}
<strong>system działania</strong>. Prime Code traktuje markę jak{' '}
<strong>system operacyjny</strong> firmy: regulujący rytm decyzji,
komunikacji i doświadczeń klienta. Spójność przestaje być tylko
kwestią estetyki a staje się{' '}
<strong>mierzalnym źródłem przewagi</strong>.
</p>
</div>
</motion.div>
<motion.div
variants={gridV}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.35 }}
className="max-w-6xl mx-auto px-6 grid gap-5 md:grid-cols-2 lg:grid-cols-3 mt-10 items-start" // <- items-start!
>
{tiles.map((t, i) => (
<ModelCard
key={i}
index={i}
tile={t}
open={openIndex === i}
onToggle={() => setOpenIndex(prev => (prev === i ? null : i))} // <- tylko jedna otwarta
/>
))}
</motion.div>
</section>
);
}
function ModelCard({
index,
tile,
open,
onToggle,
}: {
index: number;
tile: ModelTile;
open: boolean;
onToggle: () => void;
}) {
const fromLeft = index % 2 === 0;
const cardV: Variants = {
hidden: { opacity: 0, y: 12, x: fromLeft ? -6 : 6, scale: 0.985, filter: 'blur(4px)' },
show: { opacity: 1, y: 0, x: 0, scale: 1, filter: 'blur(0px)', transition: { duration: 0.45, ease: 'easeOut' } },
};
return (
<motion.article
variants={cardV}
layout="position" // <- pozycja sąsiadów, bez „poszerzania”
className="self-start relative overflow-hidden rounded-none bg-white ring-1 ring-black/8 hover:ring-black/10 transition
shadow-none hover:shadow-[0_8px_24px_rgba(16,24,40,0.06)]"
>
<div className="absolute inset-x-0 top-0 h-[2px] bg-gradient-to-r from-sky-300 via-blue-400 to-indigo-500" />
<div className="absolute inset-0 pointer-events-none opacity-[0.04]
[background-image:radial-gradient(#0ea5e9_1px,transparent_1px)]
[background-size:16px_16px]" />
<div className="p-5">
<div className="flex items-start gap-3">
<div className="shrink-0 mt-[2px] grid place-items-center h-9 w-9 rounded-full bg-gradient-to-br from-sky-300 to-indigo-500 text-white">
<span className="text-sm font-bold">{String(index + 1).padStart(2, '0')}</span>
</div>
<h3 className="text-[1.02rem] font-semibold leading-snug">{tile.title}</h3>
</div>
<p className="mt-2 text-[0.94rem] leading-relaxed text-neutral-700">{tile.short}</p>
{!open && (
<button onClick={onToggle} className="mt-4 underline underline-offset-4 text-left cursor-pointer" aria-expanded={open}>
Pokaż szczegóły
</button>
)}
</div>
<AnimatePresence initial={false}>
{open && (
<motion.div
key="details"
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.35, ease: 'easeOut' }}
className="px-5 pb-5"
>
<div className="text-[0.95rem] text-neutral-800 leading-relaxed [&_ul]:my-2 [&_li]:leading-relaxed">
<div className="prose prose-neutral max-w-none">{tile.details}</div>
<p className="mt-3 text-sm"><span className="font-semibold">Output:</span> {tile.output}</p>
<p className="mt-1 text-sm"><span className="font-semibold">Efekt:</span> {tile.efekt}</p>
</div>
<button onClick={onToggle} className="mt-4 underline underline-offset-4 text-left cursor-pointer">
Zwiń
</button>
</motion.div>
)}
</AnimatePresence>
</motion.article>
);
}
/* =================== CO POTRAFIMY — ostre, czyste =================== */
function CoPotrafimySection() {
// dokładnie te teksty z Twojej pierwotnej wersji
const items = [
"Projektujemy marki, które rosną dzięki danym i doświadczeniu użytkownika",
"Tworzymy systemy komunikacji, a nie tylko tone of voice",
"Zarządzamy marką tak jak innowacyjnym produktem lub usługą: zwinnie, testy, feedback, iteracje",
"Wdrażamy AI w miejscach, które mają znaczenie dla doświadczenia marki",
"Projektujemy onboarding, optymalizujemy punkty styku z klientem i mechanizmy wzrostu",
"Projektujemy szablony i piszemy playbooki dla marketingu, sprzedaży, HR i obsługi klienta z marką w centrum",
];
const prefersReduced = (useReducedMotion() ?? false);
const titleV: Variants = {
hidden: { opacity: 0, y: 22, filter: "blur(6px)" },
show: { opacity: 1, y: 0, filter: "blur(0px)", transition: { duration: 1.0, ease: [0.22, 1, 0.36, 1] } },
};
const gridV: Variants = { hidden: {}, show: { transition: { delayChildren: 0.12, staggerChildren: 0.08 } } };
return (
<section id="CoPotrafimy" className="relative py-28">
{/* <FlowThread className="hidden md:block" /> */}
<header className="max-w-6xl mx-auto px-6 mb-10 md:mb-14">
<motion.h2
variants={titleV}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.95 }}
className={`${headingFont.className}
inline-block text-[clamp(2rem,6vw,3.2rem)] font-extrabold tracking-[-0.02em]
text-transparent bg-clip-text
bg-[linear-gradient(90deg,#F28241_0%,#CC2E47_100%)]`}
>
Co potrafimy?
</motion.h2>
{/* <FancySeparator className="mt-5 max-w-4xl" /> */}
</header>
<motion.ul
variants={gridV}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.35 }}
className="max-w-6xl mx-auto px-6 grid gap-5 md:grid-cols-2"
>
{items.map((txt, i) => (
<CoCard key={i} index={i} text={txt} prefersReduced={prefersReduced} />
))}
</motion.ul>
<motion.p
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.7 }}
transition={{ duration: 0.5, ease: "easeOut", delay: 0.06 }}
className="max-w-6xl mx-auto px-6 mt-12 text-lg"
>
</motion.p>
</section>
);
}
/* ---------- KARTA: ostre krawędzie, bez dodatkowych tekstów ---------- */
function CoCard({
index,
text,
prefersReduced,
}: {
index: number;
text: string;
prefersReduced: boolean;
}) {
const fromLeft = index % 2 === 0;
const cardV: Variants = {
hidden: { opacity: 0, y: 14, x: fromLeft ? -8 : 8, scale: 0.985, filter: "blur(6px)" },
show: { opacity: 1, y: 0, x: 0, scale: 1, filter: "blur(0px)", transition: { duration: 0.55, ease: "easeOut" } },
};
// micro-tilt (z poszanowaniem prefers-reduced-motion)
const rx = useMotionValue(0);
const ry = useMotionValue(0);
const rotateX = useSpring(useTransform(ry, [-0.5, 0.5], [6, -6]), { stiffness: 140, damping: 12 });
const rotateY = useSpring(useTransform(rx, [-0.5, 0.5], [-6, 6]), { stiffness: 140, damping: 12 });
const onMove = (e: React.PointerEvent<HTMLLIElement>) => {
const r = (e.currentTarget as HTMLElement).getBoundingClientRect();
rx.set((e.clientX - (r.left + r.width / 2)) / r.width);
ry.set((e.clientY - (r.top + r.height / 2)) / r.height);
};
return (
<motion.li
variants={cardV}
whileHover={prefersReduced ? undefined : { y: -4, scale: 1.01 }}
onPointerMove={prefersReduced ? undefined : onMove}
onPointerLeave={() => { rx.set(0); ry.set(0); }}
style={prefersReduced ? undefined : { rotateX, rotateY }}
className="group relative overflow-hidden rounded-none bg-white/70 backdrop-blur-xl ring-1 ring-black/10
shadow-[0_10px_28px_rgba(16,24,40,0.08)]"
>
{/* cienka ciepła belka u góry */}
<div className="absolute inset-x-0 top-0 h-[2px] bg-gradient-to-r from-amber-400 via-rose-400 to-fuchsia-500" />
{/* bardzo subtelny pattern w tle */}
<div className="absolute inset-0 pointer-events-none opacity-[0.03]
[background-image:radial-gradient(#fb7185_1px,transparent_1px)]
[background-size:18px_18px]" />
{/* jednorazowy „shine” przy wejściu w viewport */}
<motion.span
initial={{ x: "-30%", opacity: 0 }}
whileInView={{ x: "130%", opacity: 0.35 }}
viewport={{ once: true, amount: 0.85 }}
transition={{ duration: 0.9, ease: [0.22, 1, 0.36, 1] }}
className="pointer-events-none absolute left-0 right-0 top-1/2 -translate-y-1/2 h-10
bg-gradient-to-r from-white/0 via-white/70 to-white/0"
/>
{/* pionowy akcent (spójny z całą stroną) */}
<motion.span
initial={{ scaleY: 0 }}
whileInView={{ scaleY: 1 }}
viewport={{ once: true, amount: 0.9 }}
transition={{ duration: 0.65, ease: "easeOut" }}
className="absolute left-0 top-0 origin-top h-full w-[3px]
bg-[linear-gradient(180deg,rgba(253,186,116,1),rgba(249,115,22,1),rgba(244,114,182,1))]"
aria-hidden
/>
<div className="p-5 md:p-6">
<div className="flex items-start gap-4">
{/* numer — można zostawić (to nie “tekst” dodany, a etykieta) */}
<div className="shrink-0 grid place-items-center h-9 w-9 text-white
bg-[linear-gradient(135deg,#FDBA74,#F472B6)]">
<span className="text-sm font-bold tabular-nums">{String(index + 1).padStart(2, "0")}</span>
</div>
{/* wyłącznie główny tekst z pierwotnej listy */}
<p className="text-[1.02rem] leading-relaxed text-neutral-900">{text}</p>
</div>
</div>
</motion.li>
);
}
/* ---------- tło: animowana, cienka „nić” w gradiencie (zero glow) ---------- */
function FlowThread({ className = "" }: { className?: string }) {
return (
<div aria-hidden className={`absolute inset-0 -z-10 ${className}`}>
<motion.svg
viewBox="0 0 1200 600"
className="absolute inset-0 w-full h-full"
preserveAspectRatio="none"
>
<defs>
<linearGradient id="thread" x1="0" x2="1" y1="0" y2="0">
<stop offset="0%" stopColor="#FDBA74" />
<stop offset="55%" stopColor="#F472B6" />
<stop offset="100%" stopColor="#3B82F6" />
</linearGradient>
</defs>
{/* miękkie esy-floresy za kartami, ale bardzo subtelne */}
<motion.path
d="M 40 140 C 260 60 380 210 600 140 C 820 70 940 240 1160 160"
fill="none"
stroke="url(#thread)"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
style={{ filter: "url(#)" }}
strokeDasharray="6 12"
initial={{ pathLength: 0, opacity: 0.25 }}
whileInView={{ pathLength: 1, opacity: 0.45 }}
viewport={{ once: true, amount: 0.2 }}
transition={{ duration: 1.2, ease: "easeOut" }}
/>
<motion.path
d="M 40 360 C 260 300 380 420 600 360 C 820 300 940 460 1160 380"
fill="none"
stroke="url(#thread)"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
strokeDasharray="6 12"
initial={{ pathLength: 0, opacity: 0.25 }}
whileInView={{ pathLength: 1, opacity: 0.45 }}
viewport={{ once: true, amount: 0.2 }}
transition={{ duration: 1.2, ease: "easeOut", delay: 0.15 }}
/>
</motion.svg>
{/* maska wygładzająca brzegi sekcji */}
<div
className="absolute inset-0 pointer-events-none"
style={{
WebkitMaskImage:
"linear-gradient(to bottom, transparent 0%, black 3%, black 97%, transparent 100%)",
maskImage:
"linear-gradient(to bottom, transparent 0%, black 3%, black 97%, transparent 100%)",
}}
/>
</div>
);
}
function RiskFreeSection() {
const items = [
'mini-audyt doświadczeń i danych',
'mapa punktów styku',
'insighty do poprawy spójności i skuteczności',
'1 eksperyment do wdrożenia natychmiast',
];
// nagłówek — miękko, jak w "Sytuacjach"
const headingV: Variants = {
hidden: { opacity: 0, y: 16, filter: 'blur(6px)' },
show: {
opacity: 1, y: 0, filter: 'blur(0px)',
transition: { duration: 0.55, ease: 'easeOut' },
},
};
// siatka — kontroluje kaskadę kart
const gridV: Variants = {
hidden: {},
show: {
transition: { delayChildren: 0.12, staggerChildren: 0.14 },
},
};
return (
<section id="JakDzialamy" className="relative py-24">
<RiskFreeBackground />
{/* Nagłówek */}
<motion.div
variants={headingV}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.55 }}
>
<Header
title="Zobacz, jak to działa bez ryzyka"
gradient="coolSoft"
className={sytuacjeFont.className}
/>
<div className="max-w-6xl mx-auto px-6">
<motion.h3
initial={{ opacity: 0, y: 10, filter: 'blur(4px)' }}
whileInView={{ opacity: 1, y: 0, filter: 'blur(0px)' }}
viewport={{ once: true, amount: 0.7 }}
transition={{ duration: 0.5, ease: 'easeOut' }}
className="text-2xl font-semibold"
>
Brand Daily Test
</motion.h3>
<motion.p
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.7 }}
transition={{ duration: 0.5, ease: 'easeOut', delay: 0.06 }}
className="text-lg text-neutral-700 max-w-2xl mt-3"
>
W 7 dni pokażemy Ci realne luki, szanse i jeden test do wdrożenia od ręki.
</motion.p>
</div>
</motion.div>
{/* Karty chłodna paleta + shine + kolejność */}
<motion.div
variants={gridV}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.35 }}
className="max-w-6xl mx-auto px-6 grid md:grid-cols-2 gap-5 mt-10"
>
{items.map((t, i) => (
<RiskFreeCard key={i} index={i} text={t} />
))}
</motion.div>
{/* CTA pod siatką */}
<motion.p
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.6 }}
transition={{ duration: 0.5, ease: 'easeOut', delay: 0.1 }}
className="max-w-6xl mx-auto px-6 mt-12 text-lg"
>
<a href="#Kontakt" className="inline-flex items-center gap-2 underline underline-offset-4">
Efekty zanim podejmiesz decyzję o dalszej współpracy.
<svg width="18" height="18" viewBox="0 0 24 24" className="-mb-[1px]">
<path d="M5 12h14M13 5l7 7-7 7" stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</a>
</motion.p>
</section>
);
}
function RiskFreeCard({ index, text }: { index: number; text: string }) {
const fromLeft = index % 2 === 0;
const cardV: Variants = {
hidden: {
opacity: 0,
y: 14,
x: fromLeft ? -8 : 8,
scale: 0.985,
filter: 'blur(6px)',
},
show: {
opacity: 1,
y: 0,
x: 0,
scale: 1,
filter: 'blur(0px)',
transition: { duration: 0.55, ease: 'easeOut' },
},
};
return (
<motion.article
variants={cardV}
className="relative overflow-hidden rounded-none bg-white ring-1 ring-black/8 hover:ring-black/10 transition
shadow-none hover:shadow-[0_10px_28px_rgba(16,24,40,0.08)]"
>
{/* cienka chłodna linia jak w „Sytuacjach” */}
<div className="absolute inset-x-0 top-0 h-[2px] bg-gradient-to-r from-sky-300 via-blue-400 to-indigo-500" />
{/* delikatny pattern + shine-once */}
<div className="absolute inset-0 pointer-events-none opacity-[0.04]
[background-image:radial-gradient(#0ea5e9_1px,transparent_1px)]
[background-size:16px_16px]" />
<motion.span
initial={{ x: '-25%', opacity: 0 }}
whileInView={{ x: '120%', opacity: 0.35 }}
viewport={{ once: true, amount: 0.9 }}
transition={{ duration: 0.9, ease: [0.22, 1, 0.36, 1] }}
className="pointer-events-none absolute left-0 right-0 top-1/2 -translate-y-1/2 h-8 rounded
bg-gradient-to-r from-white/0 via-white/70 to-white/0"
aria-hidden
/>
<div className="p-5">
<div className="flex items-start gap-4">
{/* chłodny punkt — zostaje kolorystyka z „Sytuacji” */}
<span className="mt-[2px] inline-block h-3 w-3 rounded-full bg-gradient-to-br from-sky-300 to-indigo-500" />
<p className="text-[1.02rem] leading-relaxed">
{text}
</p>
</div>
</div>
</motion.article>
);
}
function RiskFreeBackground() {
const ref = React.useRef<HTMLDivElement | null>(null);
const { scrollYProgress } = useScroll({ target: ref, offset: ['start end', 'end start'] });
const prefersReduced = useReducedMotion();
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => setMounted(true), []);
const y = useTransform(scrollYProgress, [0, 1], prefersReduced ? [0, 0] : [8, -8]);
const x = useTransform(scrollYProgress, [0, 1], prefersReduced ? [0, 0] : [-6, 6]);
return (
<div ref={ref} aria-hidden className="absolute inset-0 -z-10 overflow-visible">
<div className="absolute inset-0"
style={{ background: 'linear-gradient(to bottom, #fafbfd 0%, #f2f6fb 34%, #eaf1f9 66%, #f7f9fd 100%)' }} />
<motion.div
suppressHydrationWarning
className="absolute inset-0 pointer-events-none"
style={{
x: mounted ? x : 0,
y: mounted ? y : 0,
background: [
'radial-gradient(560px 280px at 18% 12%, rgba(255,255,255,.42), transparent 68%)',
'radial-gradient(640px 320px at 80% 18%, rgba(255,255,255,.38), transparent 70%)',
'radial-gradient(700px 360px at 72% 82%, rgba(255,255,255,.32), transparent 70%)',
].join(','),
}}
/>
<div className="absolute inset-x-0 -top-8 h-8 pointer-events-none"
style={{ background: 'linear-gradient(to bottom, #ffffff, rgba(255,255,255,0))' }} />
<div className="absolute inset-x-0 -bottom-8 h-8 pointer-events-none"
style={{ background: 'linear-gradient(to top, #ffffff, rgba(255,255,255,0))' }} />
<div className="absolute inset-0 pointer-events-none"
style={{
WebkitMaskImage: 'linear-gradient(to bottom, transparent 0%, black 2.5%, black 97.5%, transparent 100%)',
maskImage: 'linear-gradient(to bottom, transparent 0%, black 2.5%, black 97.5%, transparent 100%)',
}}
/>
</div>
);
}
function PomoglismySection({
caseStudies,
openIndex,
setOpenIndex,
}: {
caseStudies: { title: string; short: string; details: string[] }[];
openIndex: number | null;
setOpenIndex: (i: number | null) => void;
}) {
const helpedItems: string[] = [
'Ponad 20 firmom farmaceutycznym',
'Ponad 20 firmom FMCG',
'Ponad 15 firmom B2B',
'10 firmom technologicznym i startupom',
'4 bankom i 3 firmom ubezpieczeniowym',
'5 instytucjom edukacyjnym',
'Firmom z branży rozrywkowej, modowej, przemysłowej, wydawniczej, rolniczej i wielu innych',
'Globalnym centralom koncernów z USA, Kanady, Niemiec, Wielkiej Brytanii, Hiszpanii, Węgier i Polski',
];
// ten sam feeling co „Dlaczego”
const titleV: Variants = {
hidden: { opacity: 0, y: 22, filter: 'blur(6px)' },
show: { opacity: 1, y: 0, filter: 'blur(0px)', transition: { duration: 1.0, ease: [0.22, 1, 0.36, 1] } },
};
const listV: Variants = {
hidden: { opacity: 0, y: 10, filter: 'blur(4px)' },
show: {
opacity: 1, y: 0, filter: 'blur(0px)',
transition: { duration: 0.55, ease: 'easeOut', when: 'beforeChildren', delayChildren: 0.06, staggerChildren: 0.1 },
},
};
const rowV: Variants = {
hidden: { opacity: 0, y: 8 },
show: { opacity: 1, y: 0, transition: { duration: 0.45, ease: 'easeOut' } },
};
return (
<section id="PomogliśmyCaseStudies" className="relative py-24">
{/* <FlowThread className="hidden md:block" /> */}
{/* Tytuł w ciepłej tonacji + separator jak w „Dlaczego” */}
<header className="max-w-6xl mx-auto px-6">
<motion.h2
variants={titleV}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.95 }}
className={`${headingFont.className}
inline-block text-[clamp(2rem,6vw,3.2rem)] font-extrabold tracking-[-0.02em]
text-transparent bg-clip-text
bg-[linear-gradient(90deg,#F28241_0%,#CC2E47_100%)]`}
>
Pomogliśmy
</motion.h2>
{/* <FancySeparator className="mt-5 max-w-4xl" /> */}
</header>
{/* Panel z wypunktowaniem dwie kolumny od md */}
<div className="max-w-6xl mx-auto px-6 mt-10">
<motion.div
variants={listV}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.6, margin: '-10% 0px -10% 0px' }}
className="relative overflow-hidden rounded-none bg-white ring-1 ring-black/10"
>
{/* cienka ciepła linia u góry + bardzo subtelny pattern, spójny z „Dlaczego” */}
<div className="absolute inset-x-0 top-0 h-[2px] bg-gradient-to-r from-amber-400 via-rose-400 to-fuchsia-500" />
<div className="absolute inset-0 opacity-[0.03] pointer-events-none
[background-image:radial-gradient(#fb7185_1px,transparent_1px)]
[background-size:18px_18px]" />
<ol className="relative grid gap-y-3 py-6 px-5 md:grid-cols-2 md:gap-x-8 md:py-7">
{helpedItems.map((txt, i) => (
<motion.li key={i} variants={rowV} className="relative pl-7 py-2">
{/* kropka-gradient z jednorazowym pulsem */}
<motion.span
initial={{ scale: 0, boxShadow: '0 0 0 0 rgba(244,114,182,0)' }}
whileInView={{
scale: 1,
boxShadow: ['0 0 0 0 rgba(244,114,182,0.45)', '0 0 0 12px rgba(244,114,182,0)'],
}}
viewport={{ once: true, amount: 0.9 }}
transition={{ duration: 0.6, ease: 'easeOut' }}
className="absolute left-0 top-[0.9rem] h-3 w-3 rounded-full bg-gradient-to-r from-amber-400 to-fuchsia-500"
aria-hidden
/>
{/* shimmer przez wiersz */}
<motion.span
initial={{ x: '-20%', opacity: 0 }}
whileInView={{ x: '120%', opacity: 0.28 }}
viewport={{ once: true, amount: 0.9 }}
transition={{ duration: 0.85, ease: [0.22, 1, 0.36, 1] }}
className="pointer-events-none absolute left-0 right-0 top-1/2 -translate-y-1/2 h-6 rounded
bg-gradient-to-r from-amber-200/0 via-amber-200/60 to-rose-200/0"
/>
<span className="relative text-[1.05rem] md:text-[1.1rem] leading-relaxed">{txt}</span>
</motion.li>
))}
</ol>
</motion.div>
</div>
</section>
);
}
// mały helper — darmowe zdjęcia Unsplash (no-copyright)
const CASE_IMAGES: Record<string, string> = {
"Firma kosmetyczna":
"https://images.unsplash.com/photo-1522335789203-aabd1fc54bc9?q=80&w=1600&auto=format&fit=crop",
"Firma paliwowa":
"/visuals/fuel-station.jpg",
"Firma technologiczna":
"https://images.unsplash.com/photo-1522071820081-009f0129c71c?q=80&w=1600&auto=format&fit=crop",
};
const genericImage =
"https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?q=80&w=1600&auto=format&fit=crop";
const getCaseImage = (title: string, override?: string) =>
override ?? CASE_IMAGES[title] ?? genericImage;
function CaseStudiesSection({
caseStudies,
}: {
caseStudies: { title: string; short: string; details: string[]; image?: string }[];
}) {
// lokalny stan: tylko jedna karta otwarta w tej sekcji
const [openCard, setOpenCard] = React.useState<number | null>(null);
const headingV: Variants = {
hidden: { opacity: 0, y: 16, filter: "blur(6px)" },
show: { opacity: 1, y: 0, filter: "blur(0px)", transition: { duration: 0.55, ease: "easeOut" } },
};
const gridV: Variants = {
hidden: {},
show: { transition: { delayChildren: 0.2, staggerChildren: 0.18 } },
};
const cardV: Variants = {
hidden: { opacity: 0, y: 12, filter: "blur(4px)", scale: 0.985 },
show: { opacity: 1, y: 0, filter: "blur(0px)", scale: 1, transition: { duration: 0.45, ease: "easeOut" } },
};
return (
<section id="CaseStudies" className="relative py-24">
<RiskFreeBackground />
{/* Nagłówek */}
<motion.div variants={headingV} initial="hidden" whileInView="show" viewport={{ once: true, amount: 0.55 }}>
<Header title="Case Studies" gradient="coolSoft" className={sytuacjeFont.className} />
<div className="max-w-6xl mx-auto px-6">
<motion.p
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.7 }}
transition={{ duration: 0.5, ease: "easeOut", delay: 0.06 }}
className="text-lg text-neutral-700 max-w-2xl"
>
Krótko i konkretnie przykłady wdrożeń, które przełożyły się na wynik.
</motion.p>
</div>
</motion.div>
{/* 3 kolumny na dużych, 1 kolumna poniżej */}
<motion.div
variants={gridV}
initial="hidden"
whileInView="show"
viewport={{ once: true, amount: 0.35 }}
className="max-w-6xl mx-auto px-6 grid grid-cols-1 lg:grid-cols-3 gap-5 mt-10 items-start"
>
{caseStudies.map((study, i) => (
<CSCard
key={i}
index={i}
isOpen={openCard === i}
onToggle={() => setOpenCard(prev => (prev === i ? null : i))} // tylko kliknięty kafelek
title={study.title}
short={study.short}
details={study.details}
imageSrc={getCaseImage(study.title, study.image)}
variants={cardV}
/>
))}
</motion.div>
</section>
);
}
/* ---------------- KARTA ---------------- */
type CSCardProps = {
index: number;
isOpen: boolean;
onToggle: () => void;
title: string;
short: string;
details: string[];
imageSrc?: string;
variants: Variants;
};
function CSCard({
index,
isOpen,
onToggle,
title,
short,
details,
imageSrc,
variants,
}: CSCardProps) {
return (
<motion.article
layout
variants={variants}
className="self-start bg-white ring-1 ring-black/10 rounded-none overflow-hidden flex flex-col"
>
{imageSrc && (
<img
src={imageSrc}
alt=""
className="block w-full h-48 object-cover"
loading="lazy"
/>
)}
<div className="p-5 flex flex-col min-h-[200px] lg:min-h-[220px]">
<div className="flex items-start gap-3">
{/* kropka idealne koło, nie zdeformuje się */}
<span className="shrink-0 mt-[6px] inline-block w-2.5 h-2.5 rounded-full bg-gradient-to-br from-sky-400 to-indigo-500" />
<div>
<h4 className="text-lg font-semibold">{title}</h4>
<p className="text-neutral-700 mt-1">{short}</p>
</div>
</div>
<button
onClick={onToggle}
aria-expanded={isOpen}
aria-controls={`cs-details-${index}`}
className="mt-auto underline underline-offset-4 text-left cursor-pointer"
>
{isOpen ? "Zwiń" : "Pokaż szczegóły"}
</button>
<AnimatePresence initial={false} mode="wait">
{isOpen && (
<motion.ul
id={`cs-details-${index}`}
key={`open-${index}`}
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.35, ease: "easeOut" }}
className="mt-3 list-disc pl-6 text-sm text-neutral-800 space-y-2"
>
{details.map((d, idx) => (
<li key={idx}>{d}</li>
))}
</motion.ul>
)}
</AnimatePresence>
</div>
</motion.article>
);
}