mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-01 05:27:39 +00:00
561 lines
25 KiB
TypeScript
561 lines
25 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { motion, useAnimation, Variants } from "framer-motion";
|
|
import { useInView } from "react-intersection-observer";
|
|
import { easeInOut } from "framer-motion";
|
|
import { useTranslation } from "react-i18next";
|
|
import { useNavigate } from "react-router-dom";
|
|
import Cookies from "js-cookie";
|
|
import { ArrowRight, BookOpenText, ShieldCheck } from "lucide-react";
|
|
import {
|
|
useTenantConfig,
|
|
resolveModuleConfig,
|
|
} from "@/layout/components/TenantConfig";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@/shared/common/ui/dialog";
|
|
|
|
const HeroSection = () => {
|
|
const controls = useAnimation();
|
|
const { t } = useTranslation();
|
|
const navigate = useNavigate();
|
|
const { config: tenantConfig } = useTenantConfig();
|
|
const moduleConfig = resolveModuleConfig(tenantConfig);
|
|
const primary = tenantConfig.primaryColor || "#18AA9D";
|
|
const secondary = tenantConfig.secondaryColor || primary;
|
|
const [ref, inView] = useInView({
|
|
threshold: 0.1,
|
|
triggerOnce: true,
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (inView) {
|
|
controls.start("visible");
|
|
} else {
|
|
controls.start("hidden");
|
|
}
|
|
}, [controls, inView]);
|
|
|
|
const containerVariants: Variants = {
|
|
hidden: { opacity: 0 },
|
|
visible: {
|
|
opacity: 1,
|
|
transition: {
|
|
staggerChildren: 0.15,
|
|
delayChildren: 0.2,
|
|
},
|
|
},
|
|
};
|
|
|
|
const itemVariants: Variants = {
|
|
hidden: { y: 30, opacity: 0 },
|
|
visible: {
|
|
y: 0,
|
|
opacity: 1,
|
|
transition: {
|
|
duration: 0.6,
|
|
ease: easeInOut,
|
|
},
|
|
},
|
|
};
|
|
|
|
const isTokenPresent = Boolean(Cookies.get("auth-token"));
|
|
const heroTitle =
|
|
tenantConfig?.organizationName || t("landingPage.smartOffice");
|
|
const welcomeMessage =
|
|
tenantConfig?.welcomeMessage || t("landingPage.initiativeDescription");
|
|
const organizationInitials =
|
|
heroTitle
|
|
?.split(/[\s\-–—]+/)
|
|
.filter(Boolean)
|
|
.map((word) => word.charAt(0))
|
|
.join("")
|
|
.toUpperCase() || "SO";
|
|
|
|
const cardVariants: Variants = {
|
|
hidden: { scale: 0.85, opacity: 0, rotateX: 10 },
|
|
visible: {
|
|
scale: 1,
|
|
opacity: 1,
|
|
rotateX: 0,
|
|
transition: {
|
|
delay: 0.4,
|
|
duration: 0.8,
|
|
type: "spring",
|
|
stiffness: 100,
|
|
damping: 15,
|
|
},
|
|
},
|
|
};
|
|
|
|
const floatingElementVariants: Variants = {
|
|
float: {
|
|
y: [0, -20, 0],
|
|
transition: {
|
|
duration: 4,
|
|
repeat: Infinity,
|
|
ease: "easeInOut", // ✅ correct literal type
|
|
},
|
|
},
|
|
};
|
|
|
|
return (
|
|
<section className="relative bg-gradient-to-br from-white via-blue-50/30 to-primary-50/50 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900 overflow-x-hidden min-h-screen flex items-start pt-8">
|
|
{/* Enhanced Background */}
|
|
<div className="absolute inset-0 overflow-hidden">
|
|
{/* Gradient Orbs */}
|
|
<div className="absolute -top-40 -left-40 w-80 h-80 bg-gradient-to-r from-primary to-primary-500 rounded-full mix-blend-multiply filter blur-3xl opacity-15 animate-orb-slow dark:opacity-10"></div>
|
|
<div className="absolute -top-20 -right-20 w-96 h-96 bg-gradient-to-r from-primary-500 to-primary rounded-full mix-blend-multiply filter blur-3xl opacity-10 animate-orb-medium dark:opacity-5"></div>
|
|
<div className="absolute -bottom-40 left-1/3 w-72 h-72 bg-gradient-to-r from-primary-700 to-primary-500 rounded-full mix-blend-multiply filter blur-3xl opacity-20 animate-orb-fast dark:opacity-10"></div>
|
|
|
|
{/* Grid Pattern */}
|
|
<div className="absolute inset-0 bg-[linear-gradient(rgba(24,170,157,0.03)_1px,transparent_1px),linear-gradient(90deg,rgba(24,170,157,0.03)_1px,transparent_1px)] bg-[size:60px_60px] [mask-image:radial-gradient(ellipse_80%_50%_at_50%_50%,black,transparent)] dark:bg-[linear-gradient(rgba(24,170,157,0.05)_1px,transparent_1px),linear-gradient(90deg,rgba(24,170,157,0.05)_1px,transparent_1px)]"></div>
|
|
</div>
|
|
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10 pt-12 lg:pt-20">
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 lg:gap-20 items-start">
|
|
{/* Enhanced Content */}
|
|
<motion.div
|
|
ref={ref}
|
|
initial="hidden"
|
|
animate={controls}
|
|
variants={containerVariants}
|
|
className="space-y-8 lg:space-y-10"
|
|
>
|
|
{!isTokenPresent && (
|
|
<motion.div
|
|
variants={itemVariants}
|
|
className="inline-flex items-center px-4 py-2 rounded-full bg-gradient-to-r from-primary/10 to-primary-500/10 border border-primary/20 text-primary text-sm font-medium mb-4"
|
|
>
|
|
<span className="relative flex h-2 w-2 mr-2">
|
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span>
|
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-primary"></span>
|
|
</span>
|
|
{"Online"}
|
|
</motion.div>
|
|
)}
|
|
|
|
<motion.h1
|
|
variants={itemVariants}
|
|
className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold tracking-tight text-gray-900 dark:text-white leading-tight"
|
|
>
|
|
<span className="block bg-gradient-to-r from-gray-900 to-gray-700 dark:from-white dark:to-gray-300 bg-clip-text text-transparent">
|
|
{tenantConfig?.organizationName || t("landingPage.smartOffice")}
|
|
</span>
|
|
<span className="block text-transparent bg-clip-text bg-gradient-to-r from-primary via-primary-500 to-primary-500">
|
|
{t("landingPage.platformSystem")}
|
|
</span>
|
|
</motion.h1>
|
|
|
|
<motion.p
|
|
variants={itemVariants}
|
|
className="text-lg md:text-xl text-gray-600 dark:text-gray-300 max-w-lg leading-relaxed"
|
|
>
|
|
{welcomeMessage}
|
|
</motion.p>
|
|
|
|
<motion.div variants={itemVariants}>
|
|
<Dialog>
|
|
<DialogTrigger asChild>
|
|
<button className="group inline-flex items-center gap-3 rounded-full border border-primary/25 bg-white/75 px-5 py-3 text-sm font-semibold text-primary shadow-sm shadow-primary/10 backdrop-blur-md transition-all duration-300 hover:-translate-y-0.5 hover:border-primary/45 hover:bg-primary/10 hover:shadow-lg hover:shadow-primary/15 focus:outline-none focus:ring-2 focus:ring-primary/40 focus:ring-offset-2 dark:bg-gray-900/60 dark:hover:bg-primary/15 dark:focus:ring-offset-gray-900">
|
|
<span className="flex size-9 items-center justify-center rounded-full bg-primary/10 text-primary transition-colors duration-300 group-hover:bg-primary group-hover:text-white">
|
|
<BookOpenText className="size-4" />
|
|
</span>
|
|
<span>{t("newssection.readMore")}</span>
|
|
<ArrowRight className="size-4 transition-transform duration-300 group-hover:translate-x-1" />
|
|
</button>
|
|
</DialogTrigger>
|
|
<DialogContent className="max-h-[86vh] max-w-3xl overflow-hidden rounded-2xl border border-primary/15 bg-white/95 p-0 shadow-2xl shadow-primary/10 backdrop-blur-xl dark:border-primary/20 dark:bg-gray-900/95">
|
|
<div className="h-1.5 bg-gradient-to-r from-primary via-primary-500 to-primary" />
|
|
<DialogHeader className="relative gap-0 px-6 pb-5 pt-6 text-left sm:px-8">
|
|
<div className="absolute right-8 top-8 hidden h-24 w-24 rounded-full bg-primary/10 blur-2xl sm:block" />
|
|
<div className="relative flex items-start gap-4">
|
|
{tenantConfig?.logo ? (
|
|
<div className="relative group inline-flex shrink-0">
|
|
<div className="absolute inset-0 rounded-2xl bg-gradient-to-r from-primary/20 to-secondary/20 blur-xl opacity-0 transition-all duration-500 scale-110 group-hover:opacity-100" />
|
|
<motion.div
|
|
whileHover={{ scale: 1.04 }}
|
|
whileTap={{ scale: 0.97 }}
|
|
transition={{
|
|
type: "spring",
|
|
stiffness: 220,
|
|
damping: 16,
|
|
}}
|
|
className="relative flex h-16 w-24 items-center justify-center overflow-hidden rounded-2xl border-none bg-white/5 shadow-md dark:bg-slate-900/40"
|
|
title={heroTitle}
|
|
>
|
|
<motion.img
|
|
src={tenantConfig.logo}
|
|
alt={`${heroTitle} logo`}
|
|
className="h-full w-full object-contain drop-shadow-lg transition-transform duration-300 group-hover:scale-105"
|
|
onError={(e) => {
|
|
const fallback =
|
|
e.currentTarget.parentElement?.querySelector(
|
|
".modal-logo-fallback",
|
|
);
|
|
|
|
if (fallback) {
|
|
fallback.classList.remove("hidden");
|
|
fallback.classList.add("flex");
|
|
}
|
|
|
|
e.currentTarget.remove();
|
|
}}
|
|
/>
|
|
<div className="modal-logo-fallback absolute inset-0 hidden items-center justify-center rounded-2xl bg-gradient-to-br from-primary to-primary-500 text-xl font-bold text-white">
|
|
{organizationInitials || "SO"}
|
|
</div>
|
|
<div className="absolute inset-0 -translate-x-full bg-gradient-to-r from-transparent via-white/15 to-transparent transition-transform duration-1000 group-hover:translate-x-full" />
|
|
</motion.div>
|
|
</div>
|
|
) : (
|
|
<div className="relative shrink-0">
|
|
<div className="absolute inset-0 rounded-2xl bg-primary/20 blur-xl scale-110" />
|
|
<div className="relative flex h-16 w-24 items-center justify-center overflow-hidden rounded-2xl bg-gradient-to-br from-primary to-primary-500 text-xl font-bold text-white shadow-lg shadow-primary/25 ring-1 ring-white/20">
|
|
<span className="drop-shadow-sm">
|
|
{organizationInitials || "SO"}
|
|
</span>
|
|
<div className="absolute inset-x-0 top-0 h-1/2 bg-white/15" />
|
|
</div>
|
|
</div>
|
|
)}
|
|
<div className="min-w-0 pt-1">
|
|
<DialogTitle className="text-2xl font-bold leading-tight text-gray-950 dark:text-white sm:text-3xl">
|
|
{heroTitle}
|
|
</DialogTitle>
|
|
<DialogDescription className="sr-only">
|
|
{welcomeMessage}
|
|
</DialogDescription>
|
|
<div className="mt-3 h-1 w-20 rounded-full bg-gradient-to-r from-primary to-primary-500" />
|
|
</div>
|
|
</div>
|
|
</DialogHeader>
|
|
<div className="border-t border-gray-100 bg-gradient-to-b from-gray-50/80 to-white px-6 py-6 dark:border-gray-800 dark:from-gray-950/40 dark:to-gray-900 sm:px-8">
|
|
<div className="rounded-xl border border-gray-100 bg-white p-5 text-base leading-8 text-gray-700 shadow-sm dark:border-gray-800 dark:bg-gray-900/80 dark:text-gray-300 sm:p-6 sm:text-lg">
|
|
<p className="whitespace-pre-line">{welcomeMessage}</p>
|
|
</div>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
variants={itemVariants}
|
|
className="flex flex-col sm:flex-row flex-wrap gap-4 pt-2"
|
|
>
|
|
{moduleConfig.complaint && (
|
|
<motion.button
|
|
type="button"
|
|
whileHover={{
|
|
scale: 1.05,
|
|
boxShadow: `0 20px 40px ${primary}4d`,
|
|
}}
|
|
whileTap={{ scale: 0.95 }}
|
|
onClick={() => navigate("/complaints")}
|
|
className="group relative px-8 py-4 rounded-xl bg-gradient-to-r from-primary to-primary-500 text-white font-semibold text-lg shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1 overflow-hidden"
|
|
>
|
|
<div className="absolute inset-0 bg-gradient-to-r from-white/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
|
<span className="relative flex items-center justify-center">
|
|
{t("complaint.fayda.submitComplaint")}
|
|
<ShieldCheck className="w-5 h-5 ml-2" />
|
|
</span>
|
|
</motion.button>
|
|
)}
|
|
{!isTokenPresent && (
|
|
<motion.a
|
|
whileHover={{
|
|
scale: 1.05,
|
|
boxShadow: `0 20px 40px ${primary}4d`,
|
|
}}
|
|
whileTap={{ scale: 0.95 }}
|
|
href="/login"
|
|
className="group relative px-8 py-4 rounded-xl bg-gradient-to-r from-primary to-primary-500 text-white font-semibold text-lg shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1 overflow-hidden"
|
|
>
|
|
<div className="absolute inset-0 bg-gradient-to-r from-white/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
|
<span className="relative flex items-center justify-center">
|
|
{t("landingPage.signIn")}
|
|
<svg
|
|
className="ml-2 w-4 h-4 group-hover:translate-x-1 transition-transform"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M13 7l5 5m0 0l-5 5m5-5H6"
|
|
/>
|
|
</svg>
|
|
</span>
|
|
</motion.a>
|
|
)}
|
|
<motion.a
|
|
whileHover={{
|
|
scale: 1.05,
|
|
backgroundColor: "rgba(24, 170, 157, 0.08)",
|
|
}}
|
|
whileTap={{ scale: 0.95 }}
|
|
href="#features"
|
|
className="group px-8 py-4 rounded-xl border-2 border-primary text-primary font-semibold text-lg hover:shadow-lg transition-all duration-300 flex items-center justify-center"
|
|
>
|
|
{t("landingPage.learnMore")}
|
|
<svg
|
|
className="ml-2 w-4 h-4 group-hover:translate-y-0.5 transition-transform"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M19 14l-7 7m0 0l-7-7m7 7V3"
|
|
/>
|
|
</svg>
|
|
</motion.a>
|
|
</motion.div>
|
|
|
|
{/* Stats */}
|
|
<motion.div
|
|
variants={itemVariants}
|
|
className="flex flex-wrap gap-8 pt-5 pb-10"
|
|
>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
|
95%
|
|
</div>
|
|
<div className="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
|
{t("landingPage.uptime")}
|
|
</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
|
25+
|
|
</div>
|
|
<div className="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
|
{t("landingPage.bureaus")}
|
|
</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
|
24/7
|
|
</div>
|
|
<div className="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
|
{t("landingPage.support")}
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
</motion.div>
|
|
|
|
{/* Enhanced Card */}
|
|
<motion.div
|
|
initial="hidden"
|
|
animate={controls}
|
|
variants={cardVariants}
|
|
className="relative"
|
|
>
|
|
<div className="absolute -inset-4 bg-gradient-to-r from-primary to-primary-500 rounded-3xl opacity-20 blur-xl animate-pulse-slow"></div>
|
|
<div className="absolute -inset-2 bg-gradient-to-r from-primary to-primary-500 rounded-2xl opacity-10 blur-lg"></div>
|
|
|
|
<motion.div
|
|
whileHover={{ y: -5, rotateX: 5 }}
|
|
transition={{ type: "spring", stiffness: 300, damping: 20 }}
|
|
className="relative h-full bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm rounded-2xl shadow-2xl border border-white/20 dark:border-gray-700 overflow-hidden"
|
|
>
|
|
{/* Card Header */}
|
|
<div className="absolute top-0 left-0 right-0 h-2 bg-gradient-to-r from-primary to-primary-500"></div>
|
|
|
|
<div className="p-8 lg:p-10 h-full flex flex-col items-center justify-center bg-gradient-to-br from-white/90 to-gray-50/80 dark:from-gray-800/90 dark:to-gray-700/80">
|
|
<div className="text-center space-y-6">
|
|
{/* Logo/Brand */}
|
|
<div className="mb-6">
|
|
<div
|
|
className="w-16 h-16 mx-auto mb-4 rounded-2xl shadow-lg flex items-center justify-center"
|
|
style={{
|
|
background: `linear-gradient(135deg, ${primary}, ${secondary})`,
|
|
}}
|
|
>
|
|
<span className="text-white font-bold text-xl">SO</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="text-4xl lg:text-5xl font-bold mb-2 tracking-tight text-slate-900 dark:text-white">
|
|
{t("landingPage.sops")}
|
|
</div>
|
|
|
|
<div className="text-lg font-semibold text-gray-800 dark:text-gray-200">
|
|
{tenantConfig.appName}
|
|
</div>
|
|
|
|
<div className="text-gray-600 dark:text-gray-400 max-w-md leading-relaxed">
|
|
{t("landingPage.revolutionDescription")}
|
|
</div>
|
|
|
|
{/* Features List */}
|
|
<div className="grid grid-cols-2 gap-4 pt-4">
|
|
{[
|
|
"features1",
|
|
"features2",
|
|
"features3",
|
|
"features4",
|
|
"features5",
|
|
"features6",
|
|
].map((feature, index) => (
|
|
<motion.div
|
|
key={feature}
|
|
initial={{ opacity: 0, x: -10 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ delay: 0.8 + index * 0.1 }}
|
|
className="flex items-center text-sm text-gray-600 dark:text-gray-400"
|
|
>
|
|
<div
|
|
className="w-2 h-2 rounded-full mr-2"
|
|
style={{ backgroundColor: primary }}
|
|
></div>
|
|
{t(`landingPage.${feature}`)}
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="pt-6">
|
|
<motion.div
|
|
whileHover={{ scale: 1.05 }}
|
|
className="inline-flex items-center px-6 py-3 rounded-full font-semibold backdrop-blur-sm"
|
|
style={{
|
|
background: `linear-gradient(to right, ${primary}1a, ${secondary}1a)`,
|
|
color: primary,
|
|
border: `1px solid ${primary}33`,
|
|
}}
|
|
>
|
|
<span className="relative flex h-3 w-3 mr-3">
|
|
<span
|
|
className="animate-ping absolute inline-flex h-full w-full rounded-full opacity-75"
|
|
style={{ backgroundColor: primary }}
|
|
></span>
|
|
<span
|
|
className="relative inline-flex rounded-full h-3 w-3"
|
|
style={{ backgroundColor: primary }}
|
|
></span>
|
|
</span>
|
|
{t("landingPage.liveDemo")}
|
|
</motion.div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
</motion.div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Enhanced Floating Elements */}
|
|
<motion.div
|
|
variants={floatingElementVariants}
|
|
animate="float"
|
|
className="hidden lg:block absolute bottom-20 left-20"
|
|
>
|
|
<div className="w-12 h-12 rounded-2xl bg-gradient-to-br from-primary/20 to-primary-500/20 border border-primary/10 backdrop-blur-sm rotate-45"></div>
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
variants={floatingElementVariants}
|
|
animate="float"
|
|
transition={{ delay: 1 }}
|
|
className="hidden lg:block absolute top-32 right-32"
|
|
>
|
|
<div className="w-16 h-16 rounded-full bg-gradient-to-br from-primary-500/15 to-primary/15 border border-primary-500/10 backdrop-blur-sm"></div>
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
variants={floatingElementVariants}
|
|
animate="float"
|
|
transition={{ delay: 2 }}
|
|
className="hidden lg:block absolute top-1/2 left-1/4"
|
|
>
|
|
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-primary-700/10 to-primary-500/10 border border-primary-700/10 backdrop-blur-sm rotate-12"></div>
|
|
</motion.div>
|
|
|
|
{/* Scroll Indicator */}
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
transition={{ delay: 2 }}
|
|
className="absolute bottom-8 left-1/2 transform -translate-x-1/2 hidden lg:block"
|
|
>
|
|
<motion.div
|
|
animate={{ y: [0, 10, 0] }}
|
|
transition={{ duration: 2, repeat: Infinity }}
|
|
className="w-6 h-10 border-2 border-gray-300 rounded-full flex justify-center"
|
|
>
|
|
<motion.div
|
|
animate={{ y: [0, 12, 0] }}
|
|
transition={{ duration: 2, repeat: Infinity }}
|
|
className="w-1 h-3 bg-gray-400 rounded-full mt-2"
|
|
></motion.div>
|
|
</motion.div>
|
|
</motion.div>
|
|
|
|
<style>
|
|
{`
|
|
@keyframes orb-slow {
|
|
0%, 100% {
|
|
transform: translate(0px, 0px) scale(1);
|
|
}
|
|
33% {
|
|
transform: translate(40px, -60px) scale(1.1);
|
|
}
|
|
66% {
|
|
transform: translate(-30px, 30px) scale(0.9);
|
|
}
|
|
}
|
|
@keyframes orb-medium {
|
|
0%, 100% {
|
|
transform: translate(0px, 0px) scale(1);
|
|
}
|
|
33% {
|
|
transform: translate(-50px, 40px) scale(1.05);
|
|
}
|
|
66% {
|
|
transform: translate(20px, -20px) scale(0.95);
|
|
}
|
|
}
|
|
@keyframes orb-fast {
|
|
0%, 100% {
|
|
transform: translate(0px, 0px) scale(1);
|
|
}
|
|
50% {
|
|
transform: translate(20px, -40px) scale(1.08);
|
|
}
|
|
}
|
|
.animate-orb-slow {
|
|
animation: orb-slow 15s infinite ease-in-out;
|
|
}
|
|
.animate-orb-medium {
|
|
animation: orb-medium 12s infinite ease-in-out;
|
|
}
|
|
.animate-orb-fast {
|
|
animation: orb-fast 10s infinite ease-in-out;
|
|
}
|
|
@keyframes pulse-slow {
|
|
0%, 100% {
|
|
opacity: 0.2;
|
|
}
|
|
50% {
|
|
opacity: 0.3;
|
|
}
|
|
}
|
|
.animate-pulse-slow {
|
|
animation: pulse-slow 4s infinite ease-in-out;
|
|
}
|
|
`}
|
|
</style>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default HeroSection;
|