wersja pokazaowa 26.08.2025
This commit is contained in:
678
app/page.tsx
678
app/page.tsx
@@ -1,6 +1,12 @@
|
||||
'use client'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import { motion, AnimatePresence, useScroll, useTransform, useMotionTemplate, useTime, MotionValue, useSpring } from "framer-motion";
|
||||
|
||||
import { useState, useCallback, useEffect } from "react"
|
||||
import { Palette, Sparkles, Gauge, FlaskConical, TrendingUp, Users, Brain, CheckCircle2, Zap, CircleDot, XCircle } from "lucide-react";
|
||||
// na górze pliku (masz już useEffect), dodaj:
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
|
||||
|
||||
export default function Home() {
|
||||
|
||||
@@ -8,6 +14,10 @@ export default function Home() {
|
||||
|
||||
const [activeSection, setActiveSection] = useState<string>("PrimeCode");
|
||||
|
||||
const [uiVariant, setUiVariant] = useState<"A" | "B" | "C">("A");
|
||||
// A = Vibrant, B = Editorial, C = Dark
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const sections = document.querySelectorAll("section[id]");
|
||||
const observer = new IntersectionObserver(
|
||||
@@ -79,6 +89,42 @@ export default function Home() {
|
||||
}
|
||||
]
|
||||
|
||||
// --- Wspólna treść dla wszystkich wariantów sekcji "DlaczegoIntegracji" ---
|
||||
const why = {
|
||||
p1: (
|
||||
<>
|
||||
<strong className="text-cyan-800">76% konsumentów oczekuje</strong>, że marka będzie
|
||||
<strong className="text-cyan-800"> przewidywać ich potrzeby</strong>, nie tylko reagować
|
||||
<span className="text-gray-500"> (Salesforce, 2023)</span>.
|
||||
</>
|
||||
),
|
||||
p2: (
|
||||
<>
|
||||
Pokolenie Z <strong className="text-cyan-800">nie buduje lojalności przez reklamy</strong>.
|
||||
Buduje ją przez <strong className="text-cyan-800">wartość, użyteczność i autentyczność</strong>.
|
||||
<span className="font-semibold text-gray-800"> Aż 65% młodych konsumentów</span> deklaruje,
|
||||
że lojalność wobec marki zależy od spójnych i realnych doświadczeń
|
||||
<span className="text-gray-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-cyan-800">traci moc</strong>.
|
||||
<br />
|
||||
<strong className="text-cyan-800">I traci klientów.</strong>
|
||||
<span className="text-gray-500"> Według badania PwC aż 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ę) ===
|
||||
@@ -180,47 +226,10 @@ export default function Home() {
|
||||
|
||||
|
||||
|
||||
<div className="relative min-h-screen text-gray-900 overflow-hidden">
|
||||
<div className="relative isolate min-h-screen text-gray-900 overflow-hidden">
|
||||
|
||||
{/* ---- ESTETYCZNE, WIELOWARSTWOWE TŁO ---- */}
|
||||
<div className="fixed inset-0 -z-10 overflow-hidden">
|
||||
{/* Gradient bazowy */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-cyan-50 via-white to-blue-100" />
|
||||
<Background variant={uiVariant} />
|
||||
|
||||
{/* Winieta dla lepszej głębi */}
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,transparent_70%,rgba(0,0,0,0.05)_100%)] pointer-events-none" />
|
||||
|
||||
{/* Duży, miękki blob lewy-górny */}
|
||||
<motion.div
|
||||
animate={{ scale: [1, 1.05, 1] }}
|
||||
transition={{ repeat: Infinity, duration: 15, ease: "easeInOut" }}
|
||||
className="absolute top-[-5%] left-[-15%] w-[900px] h-[900px]
|
||||
bg-gradient-to-tr from-cyan-200 via-blue-200 to-blue-300
|
||||
opacity-30 rounded-full blur-3xl"
|
||||
/>
|
||||
|
||||
{/* Duży, miękki blob prawy-dolny */}
|
||||
<motion.div
|
||||
animate={{ scale: [1, 1.07, 1] }}
|
||||
transition={{ repeat: Infinity, duration: 18, ease: "easeInOut" }}
|
||||
className="absolute bottom-[-5%] right-[-15%] w-[900px] h-[900px]
|
||||
bg-gradient-to-tr from-blue-200 via-cyan-100 to-blue-300
|
||||
opacity-25 rounded-full blur-3xl"
|
||||
/>
|
||||
|
||||
{/* Centralny blob dla balansu */}
|
||||
<motion.div
|
||||
animate={{ scale: [1, 1.08, 1] }}
|
||||
transition={{ repeat: Infinity, duration: 20, ease: "easeInOut" }}
|
||||
className="absolute top-[45%] left-[50%] translate-x-[-50%] w-[500px] h-[500px]
|
||||
bg-gradient-to-tr from-cyan-100 via-blue-100 to-blue-200
|
||||
opacity-15 rounded-full blur-2xl"
|
||||
/>
|
||||
|
||||
{/* Tekstura (grain) */}
|
||||
<div className="absolute inset-0 opacity-[0.04] mix-blend-overlay"
|
||||
style={{ backgroundImage: "url('/textures/noise.png')", backgroundSize: "200px 200px" }} />
|
||||
</div>
|
||||
|
||||
{/* Sticky Navbar */}
|
||||
<nav className="fixed top-0 left-0 w-full z-50 bg-white/70 backdrop-blur-md shadow-sm border-b border-cyan-100">
|
||||
@@ -369,13 +378,20 @@ export default function Home() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{/* Dlaczego integracja marki to konieczność */}
|
||||
{uiVariant === "C" ? (
|
||||
// 👉 wariant C (timeline) w całości
|
||||
<DlaczegoIntegracji_Timeline why={why} />
|
||||
) : (
|
||||
// 👉 warianty A i B – zostają bez zmian stylistycznych
|
||||
<section
|
||||
id="DlaczegoIntegracji"
|
||||
className="relative flex flex-col justify-center items-center min-h-screen py-20 overflow-hidden"
|
||||
>
|
||||
<div className="relative max-w-4xl mx-auto px-6">
|
||||
{/* Nagłówek */}
|
||||
<div className="relative max-w-5xl mx-auto px-6">
|
||||
{/* Nagłówek wspólny */}
|
||||
<motion.h2
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
@@ -387,7 +403,8 @@ export default function Home() {
|
||||
</motion.h2>
|
||||
<div className="mx-auto mb-10 h-1 w-20 bg-cyan-600" />
|
||||
|
||||
{/* Karta treści */}
|
||||
{/* === A === */}
|
||||
{uiVariant === "A" && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.98 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
@@ -397,78 +414,88 @@ export default function Home() {
|
||||
>
|
||||
{/* Akapit 1 */}
|
||||
<div className="grid grid-cols-[16px_1fr] gap-3 items-start">
|
||||
<span
|
||||
className="mt-1 block w-2.5 h-2.5 rounded-full bg-cyan-600 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<p>
|
||||
<strong className="text-cyan-800">76% konsumentów oczekuje</strong>, że marka będzie
|
||||
<strong className="text-cyan-800"> przewidywać ich potrzeby</strong>, nie tylko reagować
|
||||
<span className="text-gray-500"> (Salesforce, 2023)</span>.
|
||||
</p>
|
||||
<span className="mt-1 block w-2.5 h-2.5 rounded-full bg-cyan-600 shrink-0" aria-hidden="true" />
|
||||
<p>{why.p1}</p>
|
||||
</div>
|
||||
|
||||
{/* Akapit 2 */}
|
||||
<div className="grid grid-cols-[16px_1fr] gap-3 items-start">
|
||||
<span
|
||||
className="mt-1 block w-2.5 h-2.5 rounded-full bg-cyan-600 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<p>
|
||||
Pokolenie Z <strong className="text-cyan-800">nie buduje lojalności przez reklamy</strong>.
|
||||
Buduje ją przez <strong className="text-cyan-800">wartość, użyteczność i autentyczność</strong>.
|
||||
<span className="font-semibold text-gray-800"> Aż 65% młodych konsumentów</span> deklaruje,
|
||||
że lojalność wobec marki zależy od spójnych i realnych doświadczeń
|
||||
<span className="text-gray-500"> (McKinsey, 2023)</span>.
|
||||
</p>
|
||||
<span className="mt-1 block w-2.5 h-2.5 rounded-full bg-cyan-600 shrink-0" aria-hidden="true" />
|
||||
<p>{why.p2}</p>
|
||||
</div>
|
||||
|
||||
{/* Punkt nadrzędny + podpunkty */}
|
||||
<div className="grid grid-cols-[16px_1fr] gap-3 items-start">
|
||||
{/* kropka poziomu 1 */}
|
||||
<span
|
||||
className="mt-1 block w-2.5 h-2.5 rounded-full bg-cyan-600 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="mt-1 block w-2.5 h-2.5 rounded-full bg-cyan-600 shrink-0" aria-hidden="true" />
|
||||
<div>
|
||||
<p>Marka nie żyje w kampaniach. Żyje w:</p>
|
||||
|
||||
{/* podpunkty (poziom 2) */}
|
||||
<p>{why.bulletsIntro}</p>
|
||||
<ul className="mt-2 space-y-2">
|
||||
{[
|
||||
'chatbotach, landingach, mailach i pushach,',
|
||||
'procesach sprzedażowych i onboardingowych,',
|
||||
'sposobie, w jaki działa Twój model cenowy, UX i obsługa klienta.',
|
||||
].map((txt, i) => (
|
||||
{why.bullets.map((txt, i) => (
|
||||
<li key={i} className="grid grid-cols-[12px_1fr] gap-3 items-start">
|
||||
{/* mniejsza kropka poziomu 2 */}
|
||||
<span
|
||||
className="mt-1 block w-2 h-2 rounded-full bg-cyan-500 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="mt-1 block w-2 h-2 rounded-full bg-cyan-500 shrink-0" aria-hidden="true" />
|
||||
<span>{txt}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Akapit 4 */}
|
||||
<div className="grid grid-cols-[16px_1fr] gap-3 items-start">
|
||||
<span
|
||||
className="mt-1 block w-2.5 h-2.5 rounded-full bg-cyan-600 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<p>
|
||||
Jeśli to wszystko nie gra razem, marka <strong className="text-cyan-800">traci moc</strong>.
|
||||
<br />
|
||||
<strong className="text-cyan-800">I traci klientów.</strong>
|
||||
<span className="text-gray-500"> Według badania PwC aż 32% klientów</span> porzuca markę po jednej
|
||||
złej interakcji, nawet jeśli wcześniej byli zadowoleni.
|
||||
</p>
|
||||
<span className="mt-1 block w-2.5 h-2.5 rounded-full bg-cyan-600 shrink-0" aria-hidden="true" />
|
||||
<p>{why.p3}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* === B (Editorial) === */}
|
||||
{uiVariant === "B" && (
|
||||
<div className="space-y-6">
|
||||
{/* Dwa callouty */}
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{[why.p1, why.p2].map((node, i) => (
|
||||
<motion.article
|
||||
key={i}
|
||||
initial={{ opacity: 0, y: 14 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: i * 0.05 }}
|
||||
className="relative bg-white/90 border border-stone-200 shadow-sm p-6"
|
||||
>
|
||||
<span className="absolute left-0 top-0 h-full w-1 bg-cyan-600" aria-hidden="true" />
|
||||
<p className="text-stone-800">{node}</p>
|
||||
</motion.article>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Lewa: „żyje w:”, Prawa: PwC */}
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<motion.article
|
||||
initial={{ opacity: 0, y: 14 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="bg-white border border-cyan-100 p-6 shadow"
|
||||
>
|
||||
<p className="font-medium text-cyan-800">{why.bulletsIntro}</p>
|
||||
<ul className="mt-3 space-y-2 text-gray-800">
|
||||
{why.bullets.map((txt, i) => (
|
||||
<li key={i} className="flex gap-3">
|
||||
<span className="mt-2 w-2 h-2 rounded-full bg-cyan-500 shrink-0" />
|
||||
<span>{txt}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</motion.article>
|
||||
<motion.article
|
||||
initial={{ opacity: 0, y: 14 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.05 }}
|
||||
className="bg-gradient-to-br from-cyan-50 to-blue-50 border border-cyan-100 p-6 shadow"
|
||||
>
|
||||
<p className="text-gray-800">{why.p3}</p>
|
||||
</motion.article>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Strzałka do kolejnej sekcji */}
|
||||
@@ -484,6 +511,9 @@ export default function Home() {
|
||||
</svg>
|
||||
</motion.div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1087,6 +1117,9 @@ export default function Home() {
|
||||
<a href="tel:+48500133609" className="text-sm text-cyan-700 hover:underline">Zadzwoń</a>
|
||||
</div>
|
||||
|
||||
{/* STYLE SWITCHER – pływający panel */}
|
||||
<StyleSwitcher value={uiVariant} onChange={setUiVariant} />
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1246,5 +1279,458 @@ function Tile({
|
||||
);
|
||||
}
|
||||
|
||||
// KOMPONENT PRZEŁĄCZNIKA (wrzuć pod innymi komponentami w tym pliku)
|
||||
function StyleSwitcher({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: "A" | "B" | "C";
|
||||
onChange: (v: "A" | "B" | "C") => void;
|
||||
}) {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
useEffect(() => setMounted(true), []);
|
||||
if (!mounted) return null;
|
||||
|
||||
return createPortal(
|
||||
<div className="fixed bottom-24 right-4 sm:right-6 z-[9999] pointer-events-auto">
|
||||
<div className="bg-white/95 backdrop-blur-md border border-cyan-100 shadow-xl rounded-xl px-3 py-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Palette className="w-4 h-4 text-cyan-700" />
|
||||
<span className="text-[11px] uppercase tracking-wide text-gray-600">Wariant</span>
|
||||
</div>
|
||||
<div className="mt-2 grid grid-cols-3 gap-2">
|
||||
{(["A", "B", "C"] as const).map((v) => (
|
||||
<button
|
||||
key={v}
|
||||
onClick={() => onChange(v)}
|
||||
className={[
|
||||
"px-3 py-1.5 rounded-md text-xs font-semibold ring-1 transition",
|
||||
value === v
|
||||
? "bg-cyan-600 text-white ring-cyan-600"
|
||||
: "bg-white text-gray-700 ring-gray-300 hover:bg-gray-50",
|
||||
].join(" ")}
|
||||
aria-pressed={value === v}
|
||||
>
|
||||
{v}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function DotList({ items, className = "" }: { items: string[]; className?: string }) {
|
||||
return (
|
||||
<ul className={["mt-3 space-y-2", className].join(" ")}>
|
||||
{items.map((txt, i) => (
|
||||
<li key={i} className="grid grid-cols-[12px_1fr] gap-3 items-start">
|
||||
{/* ta sama turkusowa kropka co w innych wypunktowaniach */}
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="mt-[0.35rem] block w-2 h-2 rounded-full bg-cyan-600"
|
||||
/>
|
||||
<span className="text-gray-800">{txt}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function DlaczegoIntegracji_Timeline({
|
||||
why,
|
||||
}: {
|
||||
why: {
|
||||
p1: React.ReactNode;
|
||||
p2: React.ReactNode;
|
||||
bulletsIntro: string;
|
||||
bullets: string[];
|
||||
p3: React.ReactNode;
|
||||
};
|
||||
}) {
|
||||
return (
|
||||
<section
|
||||
id="DlaczegoIntegracji"
|
||||
className="relative flex flex-col justify-center items-center min-h-screen py-20 overflow-hidden"
|
||||
>
|
||||
<div className="relative max-w-6xl mx-auto px-6 w-full">
|
||||
{/* Nagłówek */}
|
||||
<motion.h2
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="text-4xl font-bold mb-4 text-center text-cyan-800"
|
||||
>
|
||||
Dlaczego integracja marki to konieczność?
|
||||
</motion.h2>
|
||||
<div className="mx-auto mb-10 h-1 w-20 bg-cyan-600" />
|
||||
|
||||
{/* Oś czasu */}
|
||||
<div className="relative">
|
||||
{/* pionowa linia */}
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="hidden md:block absolute left-1/2 top-0 -translate-x-1/2 w-px h-full bg-gradient-to-b from-cyan-200 via-cyan-200/70 to-blue-200"
|
||||
/>
|
||||
|
||||
{/* 01 (lewa) */}
|
||||
<div className="md:grid md:grid-cols-[1fr_56px_1fr] md:items-center md:gap-8 mb-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -24 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="bg-white/85 backdrop-blur-md border border-cyan-100 shadow-md rounded-xl p-6 ring-1 ring-white/40"
|
||||
>
|
||||
<p className="text-gray-800">{why.p1}</p>
|
||||
</motion.div>
|
||||
<div className="hidden md:flex items-center justify-center">
|
||||
<div className="relative w-14 h-14">
|
||||
<div className="absolute inset-0 rounded-full bg-white shadow border border-cyan-100" />
|
||||
<div className="absolute inset-1 rounded-full bg-gradient-to-b from-cyan-50 to-blue-50" />
|
||||
<div className="relative w-14 h-14 flex items-center justify-center font-bold text-cyan-700">01</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="md:block hidden" />
|
||||
</div>
|
||||
|
||||
{/* 02 (prawa) */}
|
||||
<div className="md:grid md:grid-cols-[1fr_56px_1fr] md:items-center md:gap-8 mb-10">
|
||||
<div className="md:block hidden" />
|
||||
<div className="hidden md:flex items-center justify-center">
|
||||
<div className="relative w-14 h-14">
|
||||
<div className="absolute inset-0 rounded-full bg-white shadow border border-cyan-100" />
|
||||
<div className="absolute inset-1 rounded-full bg-gradient-to-b from-cyan-50 to-blue-50" />
|
||||
<div className="relative w-14 h-14 flex items-center justify-center font-bold text-cyan-700">02</div>
|
||||
</div>
|
||||
</div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 24 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="bg-white/85 backdrop-blur-md border border-cyan-100 shadow-md rounded-xl p-6 ring-1 ring-white/40"
|
||||
>
|
||||
<p className="text-gray-800">{why.p2}</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* 03 (lewa) – chipy */}
|
||||
<div className="md:grid md:grid-cols-[1fr_56px_1fr] md:items-center md:gap-8 mb-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -24 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="bg-white/85 backdrop-blur-md border border-cyan-100 shadow-md rounded-xl p-6 ring-1 ring-white/40"
|
||||
>
|
||||
<p className="text-gray-800 mb-3">
|
||||
<strong className="text-cyan-800">{why.bulletsIntro}</strong>
|
||||
</p>
|
||||
<DotList items={[
|
||||
"chatbotach, landingach, mailach i pushach,",
|
||||
"procesach sprzedażowych i onboardingowych,",
|
||||
"sposobie, w jaki działa Twój model cenowy, UX i obsługa klienta.",
|
||||
]} />
|
||||
|
||||
|
||||
</motion.div>
|
||||
<div className="hidden md:flex items-center justify-center">
|
||||
<div className="relative w-14 h-14">
|
||||
<div className="absolute inset-0 rounded-full bg-white shadow border border-cyan-100" />
|
||||
<div className="absolute inset-1 rounded-full bg-gradient-to-b from-cyan-50 to-blue-50" />
|
||||
<div className="relative w-14 h-14 flex items-center justify-center font-bold text-cyan-700">03</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="md:block hidden" />
|
||||
</div>
|
||||
|
||||
{/* 04 (prawa) – callout */}
|
||||
<div className="md:grid md:grid-cols-[1fr_56px_1fr] md:items-center md:gap-8">
|
||||
<div className="md:block hidden" />
|
||||
<div className="hidden md:flex items-center justify-center">
|
||||
<div className="relative w-14 h-14">
|
||||
<div className="absolute inset-0 rounded-full bg-white shadow border border-cyan-100" />
|
||||
<div className="absolute inset-1 rounded-full bg-gradient-to-b from-cyan-50 to-blue-50" />
|
||||
<div className="relative w-14 h-14 flex items-center justify-center font-bold text-cyan-700">04</div>
|
||||
</div>
|
||||
</div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 24 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="rounded-xl p-6 bg-gradient-to-br from-white/90 to-cyan-50/70 border border-cyan-100 shadow-md"
|
||||
>
|
||||
<p className="text-gray-800">{why.p3}</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Strzałka do kolejnej sekcji */}
|
||||
<motion.div
|
||||
onClick={() => document.getElementById('Sytuacje')?.scrollIntoView({ behavior: 'smooth' })}
|
||||
animate={{ y: [0, 15, 0] }}
|
||||
transition={{ repeat: Infinity, duration: 2, ease: 'easeInOut' }}
|
||||
className="absolute bottom-10 z-10 text-cyan-700 cursor-pointer"
|
||||
aria-label="Przewiń do sekcji: Czy znasz te sytuacje?"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</motion.div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function Background({ variant }: { variant: "A" | "B" | "C" }) {
|
||||
const [reduce, setReduce] = useState(false);
|
||||
useEffect(() => {
|
||||
setReduce(window.matchMedia("(prefers-reduced-motion: reduce)").matches);
|
||||
}, []);
|
||||
return (
|
||||
<div className="fixed inset-0 -z-10 overflow-hidden pointer-events-none">
|
||||
{/* WARSTWA BAZOWA – jak na początku strony */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-cyan-50 via-white to-blue-100" />
|
||||
|
||||
{/* === A — Aurora (oryginalna kolorystyka) === */}
|
||||
{variant === "A" && (
|
||||
<>
|
||||
<motion.div
|
||||
aria-hidden
|
||||
className="absolute -top-20 -left-40 w-[900px] h-[900px] rounded-full blur-3xl
|
||||
bg-gradient-to-tr from-cyan-200 via-blue-200 to-blue-300 opacity-30"
|
||||
animate={
|
||||
reduce ? {} : { x: [-10, 20, -10], y: [0, 20, 0], scale: [1, 1.05, 1] }
|
||||
}
|
||||
transition={{ duration: 20, repeat: Infinity, ease: "easeInOut" }}
|
||||
/>
|
||||
<motion.div
|
||||
aria-hidden
|
||||
className="absolute -bottom-32 -right-32 w-[900px] h-[900px] rounded-full blur-3xl
|
||||
bg-gradient-to-tr from-blue-200 via-cyan-100 to-blue-300 opacity-25"
|
||||
animate={
|
||||
reduce ? {} : { x: [10, -20, 10], y: [0, -15, 0], scale: [1, 1.07, 1] }
|
||||
}
|
||||
transition={{ duration: 22, repeat: Infinity, ease: "easeInOut" }}
|
||||
/>
|
||||
<motion.div
|
||||
aria-hidden
|
||||
className="absolute top-[45%] left-1/2 -translate-x-1/2 w-[520px] h-[520px] rounded-full blur-2xl
|
||||
bg-gradient-to-tr from-cyan-100 via-blue-100 to-blue-200 opacity-20"
|
||||
animate={reduce ? {} : { scale: [1, 1.08, 1] }}
|
||||
transition={{ duration: 24, repeat: Infinity, ease: "easeInOut" }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* === B — Editorial (pasy + shine) === */}
|
||||
{variant === "B" && (
|
||||
<>
|
||||
<div
|
||||
aria-hidden
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(1200px 600px at 50% -200px, rgba(59,130,246,.08), transparent 60%)",
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
aria-hidden
|
||||
className="absolute inset-0 opacity-[0.06]"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"repeating-linear-gradient(135deg, rgba(14,165,233,.14) 0, rgba(14,165,233,.14) 1px, transparent 1px, transparent 14px)",
|
||||
}}
|
||||
/>
|
||||
{!reduce && (
|
||||
<motion.div
|
||||
aria-hidden
|
||||
className="absolute inset-y-0 -left-1/3 w-1/3 bg-gradient-to-r from-transparent via-white/35 to-transparent"
|
||||
animate={{ x: ["-120%", "120%"] }}
|
||||
transition={{ duration: 22, repeat: Infinity, ease: "linear" }}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* === C — Kinetic Grid + SCROLL-REACTIVE AURORA === */}
|
||||
{variant === "C" && (
|
||||
<>
|
||||
<ScrollToneBackground />
|
||||
<ScrollToneDecor reduce={reduce} />
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{/* Ziarno (delikatne) */}
|
||||
<div
|
||||
aria-hidden
|
||||
className="absolute inset-0 opacity-[0.04]"
|
||||
style={{
|
||||
backgroundImage: "url('/textures/noise.png')",
|
||||
backgroundSize: "200px 200px",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
type ToneOpts = { wobbleSpeed?: number; wobbleAmpDeg?: number };
|
||||
|
||||
// PASTELOWE, JESZCZE JAŚNIEJSZE ODCIENIE
|
||||
// start: bardzo jasny błękit, end: bardzo jasny morelowy
|
||||
const START = { h: 208, s: 78, l: 96 }; // hsl(208 78% 96%)
|
||||
const END = { h: 24 + 360, s: 82, l: 94 }; // 24° + 360° => unikamy zieleni
|
||||
|
||||
function useBrandTone({ wobbleSpeed = 0.001, wobbleAmpDeg = 0.2 }: ToneOpts = {}) {
|
||||
const { scrollYProgress } = useScroll();
|
||||
const time = useTime() as MotionValue<number>;
|
||||
|
||||
// SINGLE SWEEP: 0 -> 1 tylko raz
|
||||
const phase = useSpring(scrollYProgress, { stiffness: 35, damping: 22, mass: 0.9 });
|
||||
|
||||
// H, S, L płynnie z START -> END (hue idzie po zegarze 208 -> 384)
|
||||
const hBase = useTransform(phase, [0, 1], [START.h, END.h]);
|
||||
const sBase = useTransform(phase, [0, 1], [START.s, END.s]);
|
||||
const lBase = useTransform(phase, [0, 1], [START.l, END.l]);
|
||||
|
||||
// Bardzo subtelny „oddech” hue, żeby tło żyło, ale bez mrygania
|
||||
const wobbleRaw = useTransform(time, t => Math.sin(t * wobbleSpeed * 1000) * wobbleAmpDeg);
|
||||
const wobble = useSpring(wobbleRaw, { stiffness: 60, damping: 20 });
|
||||
|
||||
// top = baza + leciutki wobble (ułamki stopnia)
|
||||
const hTop = useTransform([hBase, wobble] as MotionValue<number>[], (vals: number[]) => {
|
||||
const [h, w] = vals as [number, number];
|
||||
return h + w; // hue może przekraczać 360° – CSS to normalizuje
|
||||
});
|
||||
const sTop = sBase;
|
||||
const lTop = lBase;
|
||||
|
||||
// dolna warstwa: odrobinkę mniej nasycona i ciut ciemniejsza (wciąż pastel)
|
||||
const hBot = useTransform(hTop, v => v - 4);
|
||||
const sBot = useTransform(sTop, v => Math.max(65, v - 8));
|
||||
const lBot = useTransform(lTop, v => Math.max(88, v - 4));
|
||||
|
||||
// dwa środki – zawsze kilka odcieni, ale „kolor przewodni” zmienia się tylko raz
|
||||
const mix2 = (a: MotionValue<number>, b: MotionValue<number>, wa: number) =>
|
||||
useTransform([a, b] as MotionValue<number>[], (vals: number[]) => {
|
||||
const [x, y] = vals as [number, number];
|
||||
return x * wa + y * (1 - wa);
|
||||
});
|
||||
|
||||
const hMid1 = mix2(hTop, hBot, 0.65);
|
||||
const sMid1 = mix2(sTop, sBot, 0.65);
|
||||
const lMid1 = mix2(lTop, lBot, 0.65);
|
||||
|
||||
const hMid2 = useTransform([hTop, hBot, wobble] as MotionValue<number>[], (vals: number[]) => {
|
||||
const [a, b, w] = vals as [number, number, number];
|
||||
return a * 0.35 + b * 0.65 + w * 0.15;
|
||||
});
|
||||
const sMid2 = mix2(sTop, sBot, 0.35);
|
||||
const lMid2 = mix2(lTop, lBot, 0.35);
|
||||
|
||||
// kolory jako CSS-zmienne (bez migotania)
|
||||
const c1 = useMotionTemplate`hsl(${hTop} ${sTop}% ${lTop}%)`;
|
||||
const c2 = useMotionTemplate`hsl(${hMid1} ${sMid1}% ${lMid1}%)`;
|
||||
const c3 = useMotionTemplate`hsl(${hMid2} ${sMid2}% ${lMid2}%)`;
|
||||
const c4 = useMotionTemplate`hsl(${hBot} ${sBot}% ${lBot}%)`;
|
||||
|
||||
const accent = useMotionTemplate`hsl(${hBot} ${sBot}% calc(${lBot} - 10%))`;
|
||||
const accentSoft = useMotionTemplate`hsl(${hBot} ${sBot}% calc(${lBot} - 10%) / .45)`;
|
||||
const gridTint = useMotionTemplate`hsl(${hTop} ${sTop}% 46% / .16)`;
|
||||
|
||||
// ⬇⬇⬇ DODAJ scrollYProgress do return
|
||||
return { scrollYProgress, c1, c2, c3, c4, accent, accentSoft, gridTint };
|
||||
}
|
||||
|
||||
function ScrollToneBackground() {
|
||||
const { c1, c2, c3, c4 } = useBrandTone();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
aria-hidden
|
||||
className="absolute inset-0 z-[1] pointer-events-none"
|
||||
style={
|
||||
{
|
||||
["--c1" as any]: c1,
|
||||
["--c2" as any]: c2,
|
||||
["--c3" as any]: c3,
|
||||
["--c4" as any]: c4,
|
||||
background:
|
||||
"linear-gradient(180deg, var(--c1) 0%, var(--c2) 45%, var(--c3) 75%, var(--c4) 100%)",
|
||||
transform: "translateZ(0)",
|
||||
contain: "paint",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ScrollToneDecor({ reduce = false }: { reduce?: boolean }) {
|
||||
const { scrollYProgress, accent, accentSoft, gridTint } = useBrandTone();
|
||||
const time = useTime() as MotionValue<number>;
|
||||
|
||||
const x1 = useTransform(scrollYProgress, [0, 1], [15, 70]);
|
||||
const y1 = useTransform(scrollYProgress, [0, 1], [18, 34]);
|
||||
const x2 = useTransform(scrollYProgress, [0, 1], [85, 28]);
|
||||
const y2 = useTransform(scrollYProgress, [0, 1], [78, 62]);
|
||||
|
||||
const wobRaw = useTransform(time, t => Math.sin(t * 0.0016) * 4);
|
||||
const wob = useSpring(wobRaw, { stiffness: 60, damping: 18 });
|
||||
|
||||
const orb1 = useMotionTemplate`
|
||||
radial-gradient(880px 880px at calc(${x1}% + ${wob}) ${y1}%,
|
||||
${accentSoft}, transparent 60%)`;
|
||||
const orb2 = useMotionTemplate`
|
||||
radial-gradient(1040px 1040px at ${x2}% calc(${y2}% + ${wob}),
|
||||
${accentSoft}, transparent 58%)`;
|
||||
|
||||
const shineX = useSpring(
|
||||
useTransform(scrollYProgress, [0, 1], ["-130%", "130%"]),
|
||||
{ stiffness: 60, damping: 20 }
|
||||
);
|
||||
const shineBg = useMotionTemplate`linear-gradient(90deg, transparent, ${accentSoft}, transparent)`;
|
||||
|
||||
const spin = useTransform(time, t => (t * 0.012) % 360);
|
||||
const rings = useMotionTemplate`
|
||||
conic-gradient(from 0deg,
|
||||
transparent 0deg, ${accentSoft} 24deg, transparent 48deg,
|
||||
${accentSoft} 72deg, transparent 96deg, ${accentSoft} 120deg, transparent 360deg)`;
|
||||
|
||||
const gridImg = useMotionTemplate`
|
||||
repeating-linear-gradient(0deg, transparent, transparent 23px, ${gridTint} 24px),
|
||||
repeating-linear-gradient(90deg, transparent, transparent 23px, ${gridTint} 24px)`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<motion.div aria-hidden className="absolute inset-0 z-[2] pointer-events-none" style={{ backgroundImage: orb1 }} />
|
||||
<motion.div aria-hidden className="absolute inset-0 z-[2] pointer-events-none" style={{ backgroundImage: orb2 }} />
|
||||
|
||||
{!reduce && (
|
||||
<motion.div
|
||||
aria-hidden
|
||||
className="absolute top-0 bottom-0 -left-1/3 w-1/3 z-[3] pointer-events-none"
|
||||
style={{ x: shineX, background: shineBg, opacity: 0.5 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<motion.div aria-hidden className="absolute inset-0 z-[2] pointer-events-none" style={{ background: rings, opacity: 0.08, rotate: spin }} />
|
||||
<motion.div aria-hidden className="absolute inset-0 z-[2] opacity-[0.09] pointer-events-none" style={{ backgroundImage: gridImg }} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
output: 'export',
|
||||
// next.config.ts
|
||||
const nextConfig = {
|
||||
output: "export",
|
||||
images: { unoptimized: true },
|
||||
trailingSlash: true,
|
||||
eslint: { ignoreDuringBuilds: true }, // ← nie blokuj buildu na lint
|
||||
// typescript: { ignoreBuildErrors: true }, // ← użyj tylko awaryjnie
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"framer-motion": "^12.23.11",
|
||||
"lucide-react": "^0.541.0",
|
||||
"next": "15.4.4",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0"
|
||||
@@ -4520,6 +4521,15 @@
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.541.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.541.0.tgz",
|
||||
"integrity": "sha512-s0Vircsu5WaGv2KoJZ5+SoxiAJ3UXV5KqEM3eIFDHaHkcLIFdIWgXtZ412+Gh02UsdS7Was+jvEpBvPCWQISlg==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.17",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"framer-motion": "^12.23.11",
|
||||
"lucide-react": "^0.541.0",
|
||||
"next": "15.4.4",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0"
|
||||
|
||||
Reference in New Issue
Block a user