wersja pokazaowa 26.08.2025

This commit is contained in:
2025-08-26 08:42:36 +02:00
parent b663af6767
commit 534a1f62ab
6 changed files with 659 additions and 158 deletions

View File

@@ -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 przez <strong className="text-cyan-800">wartość, użyteczność i autentyczność</strong>.
<span className="font-semibold text-gray-800"> 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 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,121 +378,142 @@ export default function Home() {
{/* Dlaczego integracja marki to konieczność */}
<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 */}
<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" />
{/* Karta treści */}
<motion.div
initial={{ opacity: 0, scale: 0.98 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="bg-white border border-cyan-100 rounded-none p-8 md:p-10 text-gray-800 space-y-6 shadow"
>
{/* 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>
</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 przez <strong className="text-cyan-800">wartość, użyteczność i autentyczność</strong>.
<span className="font-semibold text-gray-800"> 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>
</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"
/>
<div>
<p>Marka nie żyje w kampaniach. Żyje w:</p>
{/* podpunkty (poziom 2) */}
<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) => (
<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>{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 32% klientów</span> porzuca markę po jednej
złej interakcji, nawet jeśli wcześniej byli zadowoleni.
</p>
</div>
</motion.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?"
{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"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</motion.div>
</section>
<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 }}
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" />
{/* === A === */}
{uiVariant === "A" && (
<motion.div
initial={{ opacity: 0, scale: 0.98 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="bg-white border border-cyan-100 rounded-none p-8 md:p-10 text-gray-800 space-y-6 shadow"
>
{/* 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>{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>{why.p2}</p>
</div>
{/* Punkt nadrzędny + podpunkty */}
<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" />
<div>
<p>{why.bulletsIntro}</p>
<ul className="mt-2 space-y-2">
{why.bullets.map((txt, i) => (
<li key={i} className="grid grid-cols-[12px_1fr] gap-3 items-start">
<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>{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 */}
<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" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</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 }} />
</>
);
}

View File

@@ -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;

BIN
out.rar Normal file

Binary file not shown.

BIN
out.zip

Binary file not shown.

10
package-lock.json generated
View File

@@ -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",

View File

@@ -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"