-
Notifications
You must be signed in to change notification settings - Fork 15
Q #11
Description
import React, { useEffect, useRef, useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
// Single-file React component for a futuristic SABSUS landing page
// Uses Tailwind CSS and Framer Motion. Default export a component.
export default function SABSUSGreenApple() {
const [lang, setLang] = useState("ru");
const [mouse, setMouse] = useState({ x: 0, y: 0 });
const [assembled, setAssembled] = useState(false);
const [flip, setFlip] = useState(false);
const containerRef = useRef(null);
// copy strings
const copy = {
ru: {
title: "GREEN APPLE",
subtitle:
"Новая эра интеллектуальных систем SABSUS — глубина, безопасность и масштабируемость.",
cta: "Запустить свою систему",
contact: "Почта: skl-viktoriya@yandex.ru",
},
en: {
title: "GREEN APPLE",
subtitle:
"A new era of intelligent systems — depth, security and scalable futures.",
cta: "Launch your system",
contact: "Email: skl-viktoriya@yandex.ru",
},
};
// mouse parallax handler
useEffect(() => {
const handleMove = (e) => {
const rect = containerRef.current?.getBoundingClientRect();
if (!rect) return;
const x = (e.clientX - rect.left) / rect.width - 0.5; // -0.5..0.5
const y = (e.clientY - rect.top) / rect.height - 0.5;
setMouse({ x, y });
};
window.addEventListener("mousemove", handleMove);
return () => window.removeEventListener("mousemove", handleMove);
}, []);
// scroll flip detection
useEffect(() => {
let lastY = window.scrollY;
const onScroll = () => {
const dy = window.scrollY - lastY;
if (Math.abs(dy) > 40) {
setFlip((f) => !f);
lastY = window.scrollY;
}
};
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
// assemble shards after mount
useEffect(() => {
const t = setTimeout(() => setAssembled(true), 1200);
return () => clearTimeout(t);
}, []);
// generate rectangular 'abstracts'
const abstracts = Array.from({ length: 14 }).map((_, i) => ({
id: i,
w: 120 + (i % 4) * 40,
h: 40 + (i % 3) * 20,
top: Math.round(Math.random() * 70),
left: Math.round(Math.random() * 70),
rot: (Math.random() - 0.5) * 60,
glow: Math.random() > 0.6,
}));
// shards that will assemble into the outlined text using an SVG mask
const shardsCount = 24;
const shards = Array.from({ length: shardsCount }).map((_, i) => {
const rnd = (n) => Math.floor(Math.random() * n);
return {
id: i,
x0: rnd(1600) - 800,
y0: rnd(400) - 200,
x: 60 + rnd(680),
y: 30 + rnd(140),
rx: 2 + rnd(20),
ry: 2 + rnd(10),
dur: 0.9 + Math.random() * 1.2,
delay: Math.random() * 0.9,
};
});
return (
<div
ref={containerRef}
className="min-h-screen w-full bg-[radial-gradient(ellipse_at_top_right,#001219,#000814)] text-white overflow-hidden relative"
style={{
WebkitFontSmoothing: "antialiased",
MozOsxFontSmoothing: "grayscale",
}}
>
{/* Top bar with language switch */}
<button
onClick={() => setLang(lang === "ru" ? "en" : "ru")}
className="px-3 py-1 rounded-xl bg-white/6 backdrop-blur-sm border border-white/10 text-sm"
>
{lang === "ru" ? "EN" : "RU"}
{/* Depth layers: background glow, particles */}
<div
aria-hidden
className="absolute inset-0 -z-10 pointer-events-none"
style={{
background:
"linear-gradient(120deg, rgba(16,185,129,0.08), rgba(79,70,229,0.06) 35%, rgba(14,165,233,0.04) 70%)",
mixBlendMode: "screen",
}}
/>
{/* Abstract rectangles that react to mouse and flip on scroll */}
{abstracts.map((a, idx) => {
const moveX = mouse.x * (30 + idx * 6);
const moveY = mouse.y * (18 + idx * 4);
const rotate = mouse.x * (a.rot / 1.5);
const scale = 1 + Math.abs(mouse.x) * 0.08;
return (
<motion.div
key={a.id}
className={`absolute rounded-2xl shadow-2xl/30 z-0 ${a.glow ? "filter blur-sm" : ""}`}
initial={{ opacity: 0 }}
animate={{
x: `${a.left}vw`,
y: `${a.top}vh`,
rotateY: flip ? 180 : 0,
opacity: 0.9,
scale,
}}
transition={{ type: "spring", stiffness: 40 + idx * 5, damping: 12 }}
style={{
width: a.w,
height: a.h,
background: `linear-gradient(135deg, rgba(16,185,129,${0.08 + idx * 0.01}), rgba(79,70,229,${0.04 + idx * 0.005}))`,
transform: `translate3d(${moveX}px, ${moveY}px, ${-idx * 6}px) rotate(${rotate}deg)`,
border: "1px solid rgba(255,255,255,0.04)",
boxShadow: "0 10px 40px rgba(2,6,23,0.6)",
backdropFilter: "blur(6px)",
}}
/>
);
})}
{/* Center stage */}
<main className="min-h-screen flex items-center justify-center relative z-20 px-6">
<div className="max-w-5xl w-full grid grid-cols-1 md:grid-cols-3 gap-6 items-center">
<div className="col-span-1 md:col-span-3 flex flex-col items-center text-center">
{/* Big outline text made from shards inside an SVG mask */}
<div className="relative w-full flex justify-center mb-6 pointer-events-none">
<svg viewBox="0 0 1000 220" className="w-full max-w-4xl h-40 md:h-56">
<defs>
<mask id="text-mask">
<rect width="100%" height="100%" fill="black" />
<text
x="50%"
y="55%"
textAnchor="middle"
fontFamily="Inter, Roboto, system-ui, sans-serif"
fontWeight="800"
fontSize="120"
fill="white"
style={{ letterSpacing: 6 }}
>
{copy[lang].title}
</text>
</mask>
{/* Outline stroke for the title */}
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="6" result="coloredBlur" />
<feMerge>
<feMergeNode in="coloredBlur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
{/* Outline text */}
<text
x="50%"
y="55%"
textAnchor="middle"
fontFamily="Inter, Roboto, system-ui, sans-serif"
fontWeight="800"
fontSize="120"
fill="none"
stroke="url(#grad)"
strokeWidth="1.8"
style={{ letterSpacing: 6, paintOrder: "stroke" }}
>
{copy[lang].title}
</text>
{/* Subtle gradient definition for stroke */}
<linearGradient id="grad" x1="0" x2="1">
<stop offset="0%" stopColor="#5EEAD4" />
<stop offset="50%" stopColor="#60A5FA" />
<stop offset="100%" stopColor="#7C3AED" />
</linearGradient>
{/* shards group clipped by text mask - they animate from dispersed to form the text body */}
<g mask="url(#text-mask)">
{shards.map((s) => (
<motion.rect
key={s.id}
x={s.x0}
y={s.y0}
rx={s.rx}
ry={s.ry}
width={40 + (s.id % 5) * 8}
height={10 + (s.id % 3) * 6}
initial={{ x: s.x0, y: s.y0, opacity: 0 }}
animate={
assembled
? { x: s.x, y: s.y, opacity: 1, rotate: (s.id % 2 ? 6 : -6) }
: { x: s.x0, y: s.y0, opacity: 0 }
}
transition={{ duration: s.dur, delay: s.delay, ease: "circOut" }}
style={{
fill: `rgba(255,255,255,${0.9 - (s.id % 7) * 0.06})`,
filter: "drop-shadow(0 6px 16px rgba(0,0,0,0.6))",
}}
/>
))}
{/* After assembled, a subtle floating animation applied to group */}
<motion.g
animate={assembled ? { y: [0, -8, 0] } : { y: 0 }}
transition={{ duration: 6, repeat: Infinity, ease: "easeInOut" }}
/>
</g>
</svg>
</div>
{/* Subtitle and CTA */}
<p className="max-w-2xl text-sm md:text-base opacity-80 mb-6">
{copy[lang].subtitle}
</p>
<div className="flex gap-4 items-center">
<motion.button
whileHover={{ y: -4, scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className="px-8 py-3 rounded-full bg-gradient-to-r from-green-400/30 to-indigo-500/30 border border-white/10 backdrop-blur-md text-sm md:text-base"
onClick={() => alert((lang === "ru" ? "Запуск..." : "Launching...") + "\n" + copy[lang].contact)}
>
{copy[lang].cta}
</motion.button>
<a
href={`mailto:skl-viktoriya@yandex.ru`}
className="text-xs md:text-sm opacity-70 underline underline-offset-2"
>
{copy[lang].contact}
</a>
</div>
</div>
</div>
</main>
{/* Footer decorative elements */}
<footer className="absolute bottom-6 left-6 right-6 flex justify-between items-center text-xs opacity-60 z-30">
<div>© SABSUS — {new Date().getFullYear()}</div>
<div>Made for the future • Depth-driven UX</div>
</footer>
{/* Extra: subtle animated grid to give ZD space depth */}
<svg className="pointer-events-none absolute inset-0 -z-20" width="100%" height="100%">
<defs>
<linearGradient id="gridGrad" x1="0" x2="1">
<stop offset="0%" stopColor="#063F3B" stopOpacity="0.06" />
<stop offset="100%" stopColor="#1E293B" stopOpacity="0.02" />
</linearGradient>
</defs>
<rect width="100%" height="100%" fill="url(#gridGrad)" />
</svg>
</div>
);
}