From 1b77e0c85c7c5792d4f128b006b7ae0cc5c07641 Mon Sep 17 00:00:00 2001 From: Siddh2024 Date: Mon, 15 Jun 2026 21:37:54 +0530 Subject: [PATCH 1/3] feat: revamp landing page for modern UI/UX (#251) - Split monolithic landing page into section components - Add smooth scroll animations and micro-interactions - Implement orbit animations, BYOM pipeline, services showcase - Improve typography hierarchy and spacing - Fix auth forms with proper titles and branding - Remove redundant 'Back to Home' button from signup - Fully responsive across mobile, tablet, and desktop - Mobile hamburger menu support --- .../src/components/AuthShell.jsx | 29 +- apps/web-dashboard/src/index.css | 10 +- .../src/pages/LandingPage/index.jsx | 1528 ++++++--- .../src/pages/LandingPage/style.css | 2855 ++++++++++++++--- apps/web-dashboard/src/pages/Login.jsx | 6 +- apps/web-dashboard/src/pages/Signup.jsx | 22 +- 6 files changed, 3385 insertions(+), 1065 deletions(-) diff --git a/apps/web-dashboard/src/components/AuthShell.jsx b/apps/web-dashboard/src/components/AuthShell.jsx index 708b16817..1559c2bfe 100644 --- a/apps/web-dashboard/src/components/AuthShell.jsx +++ b/apps/web-dashboard/src/components/AuthShell.jsx @@ -17,18 +17,25 @@ function AuthShell({
+
+
U
+ urBackend +
+
- {onModeClick ? ( - - ) : ( - {modeLabel} + {modeLabel && ( + onModeClick ? ( + + ) : ( + {modeLabel} + ) )} {title ?

{title}

: null} {subtitle ?

{subtitle}

: null} diff --git a/apps/web-dashboard/src/index.css b/apps/web-dashboard/src/index.css index 26aa88a01..18f846901 100644 --- a/apps/web-dashboard/src/index.css +++ b/apps/web-dashboard/src/index.css @@ -812,11 +812,11 @@ select option { .auth-form-card { width: 100%; - padding: 0; - border: none; - border-radius: 0; - background: transparent; - box-shadow: none; + padding: 2rem; + border: 1px solid rgba(255,255,255,0.06); + border-radius: 16px; + background: rgba(255,255,255,0.02); + box-shadow: 0 24px 48px -12px rgba(0,0,0,0.4); } .auth-form-card__brand { diff --git a/apps/web-dashboard/src/pages/LandingPage/index.jsx b/apps/web-dashboard/src/pages/LandingPage/index.jsx index 55094e8f9..2662cfc33 100644 --- a/apps/web-dashboard/src/pages/LandingPage/index.jsx +++ b/apps/web-dashboard/src/pages/LandingPage/index.jsx @@ -1,10 +1,10 @@ -import { useState, useEffect, useRef, useCallback } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { useAuth } from '../../context/AuthContext'; +import api from '../../utils/api'; import BackToTop from '../../components/Layout/BackToTop'; import { Database, - Shield, HardDrive, ArrowRight, CheckCircle, @@ -24,11 +24,13 @@ import { Code, Check, Plus, - Cloud, - RefreshCw, - Server + Github, + Mail, + UserRound, + Eye, + EyeOff } from 'lucide-react'; -import { motion as Motion, AnimatePresence, useScroll, useSpring, useTransform } from 'framer-motion'; +import { motion as Motion, AnimatePresence } from 'framer-motion'; import Footer from '../../components/Layout/Footer'; import './style.css'; @@ -46,82 +48,477 @@ const HERO_CLICK_STEPS = [ { name: 'role', type: 'String', required: false }, ]; -const INITIAL_FEATURES = [ +const COMPATIBLE_TECHNOLOGIES = [ + // React { - id: 'database', - title: 'Managed NoSQL Database', - icon: 'Database', - tag: 'DATABASE LAYER', - desc: 'High-performance document storage powered by MongoDB. Scale from 10 to 10M records without managing servers.', - color: 'teal', - accentColor: '#00f5d4', - type: 'list', - bullets: ['Strict Type Validation', 'Auto-generated API Endpoints', 'Real-time Indexing'] + id: 'react', + label: 'React', + ring: 'inner', + cls: 'int-react', + type: 'Official SDK', + badge: '@urbackend/react', + desc: 'Connect your React applications seamlessly with our official React SDK. Equip your components with hooks, authentication providers, and state utilities.', + color: '#61DAFB', + svg: ( + + + + + + + + + ), + code: `import { UrProvider, useUrCollection } from '@urbackend/react';\n\n// 1. Wrap your app\nfunction App() {\n return (\n \n \n \n );\n}\n\n// 2. Use reactive database hooks!\nfunction TodoList() {\n const { documents, loading } = useUrCollection('todos');\n return (\n
    \n {documents.map(todo =>
  • {todo.title}
  • )}\n
\n );\n}` }, + // Next.js { - id: 'auth', - title: 'Secure Auth', - icon: 'Shield', - tag: 'IDENTITY & ACCESS', - desc: 'Full authentication flow with JWTs, BCrypt hashing, and session management built-in.', - color: 'amber', - accentColor: '#FFBD2E', - type: 'auth-info', - bullets: [] + id: 'nextjs', + label: 'Next.js', + ring: 'inner', + cls: 'int-next', + type: 'React Framework', + badge: 'SDK + Server Actions', + desc: 'Build server-rendered React applications. Works beautifully with Next.js Server Components, Middleware auth checks, and Server Actions.', + color: '#ffffff', + svg: ( + + + + + + + + + + + + + + + + ), + code: `// Get server-side data in Server Components\nimport { createServerClient } from '@urbackend/sdk/server';\n\nexport default async function DashboardPage() {\n const ur = createServerClient({\n endpoint: 'https://api.ub.bitbros.in',\n apiKey: process.env.UR_PRIVATE_KEY\n });\n\n const { data: posts } = await ur.db('posts').find();\n\n return (\n
\n {posts.map(post =>

{post.title}

)}\n
\n );\n}` }, + // Flutter { - id: 'storage', - title: 'Global Storage', - icon: 'HardDrive', - tag: 'ASSET DELIVERY', - desc: 'Upload and serve media assets via global CDN. Supports images, documents, and videos.', - color: 'blue', - accentColor: '#409EFF', - type: 'simple', - bullets: [] + id: 'flutter', + label: 'Flutter', + ring: 'inner', + cls: 'int-flutter', + type: 'Mobile Framework', + badge: 'urbackend_sdk (Dart)', + desc: 'Build gorgeous, high-performance mobile apps. Sync local collections, authenticate users, and manage media uploads natively in Dart.', + color: '#02569B', + svg: ( + + + + + ), + code: `import 'package:urbackend_sdk/urbackend_sdk.dart';\n\nvoid main() async {\n // Initialize urBackend client\n final client = UrClient(\n endpoint: 'https://api.ub.bitbros.in',\n projectId: 'proj_123',\n );\n\n // Authenticate user\n final session = await client.auth.signIn(\n email: 'user@example.com', \n password: 'secure_password'\n );\n\n print('Access Token: \${session.accessToken}');\n}` + }, + // Vue + { + id: 'vue', + label: 'Vue.js', + ring: 'inner', + cls: 'int-vue', + type: 'Frontend Framework', + badge: '@urbackend/sdk (ESM)', + desc: 'Create highly responsive web experiences. Integrates natively with Vue 3 composition APIs, reactive ref/computed state, and Pinia stores.', + color: '#42B883', + svg: ( + + + + + ), + code: `\n\n` + }, + // Webflow + { + id: 'webflow', + label: 'Webflow', + ring: 'inner', + cls: 'int-webflow', + type: 'Low-Code Web', + badge: 'Custom Script CDN', + desc: 'Add custom dynamic databases, member logic, and contact forms to Webflow designs without writing a single line of backend server code.', + color: '#4353FF', + svg: ( + + + + ), + code: `\n\n\n` + }, + // React Native + { + id: 'react-native', + label: 'React Native', + ring: 'mid', + cls: 'int-react-native', + type: 'Mobile Framework', + badge: '@urbackend/react-native', + desc: 'Write beautiful native iOS and Android apps using React and Javascript. Supports secure AsyncStorage token persistence out-of-the-box.', + color: '#61DAFB', + svg: ( + + + + + + + + + ), + code: `import { UrProvider } from '@urbackend/react';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\n\n// Initialize with Native Storage for sessions\nfunction App() {\n return (\n \n \n \n );\n}` + }, + // Dart + { + id: 'dart', + label: 'Dart', + ring: 'mid', + cls: 'int-dart', + type: 'Programming Language', + badge: 'REST API Wrapper', + desc: 'Build CLI utilities, server-side tools, or Flutter extensions using raw Dart. Fast JSON processing and asynchronous networking.', + color: '#00D2B8', + svg: ( + + + + + + ), + code: `import 'package:http/http.dart' as http;\nimport 'dart:convert';\n\nvoid main() async {\n final url = Uri.parse('https://api.ub.bitbros.in/v1/db/analytics');\n final response = await http.post(\n url,\n headers: { 'x-api-key': 'ur_sec_dart' },\n body: jsonEncode({ 'metric': 'system_boot', 'value': 1 }),\n );\n print(jsonDecode(response.body));\n}` + }, + // Nuxt + { + id: 'nuxt', + label: 'Nuxt', + ring: 'mid', + cls: 'int-nuxt', + type: 'Vue Framework', + badge: 'Nuxt Module', + desc: 'Build fullstack server-rendered Vue 3 applications. Fully integrated with Nuxt server routes and asyncData fetching.', + color: '#00DC82', + svg: ( + + + + ), + code: `// server/api/users.ts\nimport { createClient } from '@urbackend/sdk';\n\nexport default defineEventHandler(async (event) => {\n const ur = createClient({\n endpoint: 'https://api.ub.bitbros.in',\n apiKey: process.env.UR_API_KEY\n });\n \n const users = await ur.collection('users').find();\n return { success: true, users };\n});` + }, + // Python + { + id: 'python', + label: 'Python', + ring: 'mid', + cls: 'int-python', + type: 'Official SDK', + badge: 'urbackend-python', + desc: 'For script automation, machine learning pipelines, and backend processing. A simple, robust requests-based client wrapper.', + color: '#FFD43B', + svg: ( + + + + + ), + code: `from urbackend import UrClient\n\n# Initialize Python Client\nclient = UrClient(\n endpoint="https://api.ub.bitbros.in",\n api_key="ur_sec_python_999"\n)\n\n# Insert documents in MongoDB\nnew_item = client.db("inventory").insert({\n "sku": "ITEM_XYZ",\n "quantity": 150,\n "location": "Warehouse A"\n})\n\nprint(f"Created doc with ID: {new_item['_id']}")` }, + // TypeScript { - id: 'architecture', - title: 'Robust Node.js Architecture', - icon: 'Cpu', - tag: 'RUNTIME CORE', - desc: 'Built on lightweight Express.js. We isolate your project to ensure consistent performance and security.', - color: 'red', - accentColor: '#FF5F56', - type: 'tags', - bullets: ['JWT Authentication', 'Lightweight'] + id: 'typescript', + label: 'TypeScript', + ring: 'mid', + cls: 'int-typescript', + type: 'Official SDK', + badge: '@urbackend/sdk (Typed)', + desc: 'Lightweight core package compiled for JS/TS. Enjoy autocomplete, model schema inference, and full compiler support.', + color: '#3178C6', + svg: ( + + + + + ), + code: `import { UrClient, Document } from '@urbackend/sdk';\n\ninterface Order extends Document {\n item: string;\n total: number;\n}\n\nconst client = new UrClient({ endpoint: 'https://api.ub.bitbros.in' });\n\n// Auto-inferred typing on query responses\nconst { data } = await client.db('orders').findById('ord_999');\nconsole.log(data.item.toUpperCase());` + }, + // Swift + { + id: 'swift', + label: 'Swift', + ring: 'mid', + cls: 'int-swift', + type: 'Native iOS', + badge: 'REST API Endpoint', + desc: 'Integrate native Apple ecosystems. Fast and optimized REST routing designed for modern iOS, macOS, watchOS, or visionOS Apps.', + color: '#F05138', + svg: ( + + + + + ), + code: `import Foundation\n\nstruct User: Codable {\n let email: String\n}\n\nfunc fetchUser(userId: String) {\n let url = URL(string: "https://api.ub.bitbros.in/v1/db/users/\\(userId)")!\n var request = URLRequest(url: url)\n request.setValue("ur_sec_swift_key", forHTTPHeaderField: "x-api-key")\n \n URLSession.shared.dataTask(with: request) { data, _, _ in\n if let data = data {\n let user = try? JSONDecoder().decode(User.self, from: data)\n print("User: \\(user?.email ?? "")")\n }\n }.resume()\n}` + }, + // Kotlin + { + id: 'kotlin', + label: 'Kotlin', + ring: 'outer', + cls: 'int-kotlin', + type: 'Native Android', + badge: 'REST API Client', + desc: 'Build powerful Android applications. Fetch collections, authorize users, and write logs natively inside Kotlin-based repositories.', + color: '#7F52FF', + svg: ( + + + + ), + code: `import okhttp3.OkHttpClient\nimport okhttp3.Request\n\nval client = OkHttpClient()\n\nfun loadData() {\n val request = Request.Builder()\n .url("https://api.ub.bitbros.in/v1/db/products")\n .addHeader("x-api-key", "ur_sec_kotlin")\n .build()\n\n client.newCall(request).execute().use { response ->\n println(response.body?.string())\n }\n}` + }, + // Go + { + id: 'go', + label: 'Go', + ring: 'outer', + cls: 'int-go', + type: 'Backend Language', + badge: 'Net/HTTP API Calls', + desc: 'Connect performant microservices. urBackend gives Go services immediate read/write access to MongoDB instances over SSL endpoints.', + color: '#00ADD8', + svg: ( + + + + + ), + code: `package main\n\nimport (\n "bytes"\n "fmt"\n "net/http"\n)\n\nfunc main() {\n jsonData := []byte(\`{"device_id": "sensor_01", "temp": 24.5}\`)\n req, _ := http.NewRequest("POST", "https://api.ub.bitbros.in/v1/db/telemetry", bytes.NewBuffer(jsonData))\n req.Header.Set("x-api-key", "ur_sec_golang")\n req.Header.Set("Content-Type", "application/json")\n\n client := &http.Client{}\n resp, _ := client.Do(req)\n fmt.Println("Status Code:", resp.StatusCode)\n}` + }, + // Rust + { + id: 'rust', + label: 'Rust', + ring: 'outer', + cls: 'int-rust', + type: 'Systems Language', + badge: 'Async REST Crates', + desc: 'Integrate memory-safe microservices with urBackend. Fully asynchronous request bodies with low connection latency.', + color: '#FF6F30', + svg: ( + + + + + + ), + code: `use serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), reqwest::Error> {\n let client = reqwest::Client::new();\n let body = json!({ "user_id": "usr_99", "status": "active" });\n\n let res = client.post("https://api.ub.bitbros.in/v1/db/sessions")\n .header("x-api-key", "ur_sec_rust")\n .json(&body)\n .send()\n .await?;\n\n println!("Response status: {}", res.status());\n Ok(())\n}` + }, + // FastAPI + { + id: 'fastapi', + label: 'FastAPI', + ring: 'outer', + cls: 'int-fastapi', + type: 'Python Framework', + badge: 'HTTP Client Callouts', + desc: 'Combine local python microservices with urBackend datastores. Offload authentication and file storage flows with ease.', + color: '#009485', + svg: ( + + + + ), + code: `from fastapi import FastAPI, Depends\nimport httpx\n\napp = FastAPI()\n\n@app.get("/sync-data")\nasync def sync_data():\n async with httpx.AsyncClient() as client:\n res = await client.post(\n "https://api.ub.bitbros.in/v1/db/syncs",\n headers={"x-api-key": "ur_sec_fastapi"},\n json={"status": "completed"}\n )\n return res.json()` + }, + // Node.js + { + id: 'node', + label: 'Node.js', + ring: 'outer', + cls: 'int-node', + type: 'Core SDK', + badge: '@urbackend/sdk (ESM/CJS)', + desc: 'Perform server-side orchestration. Manage document indexing, trigger email deliveries, and query buckets directly from Node.js scripts.', + color: '#339933', + svg: ( + + + + ), + code: `const { UrClient } = require('@urbackend/sdk');\n\n// Initialize with admin privileges\nconst ur = new UrClient({\n endpoint: 'https://api.ub.bitbros.in',\n apiKey: process.env.UR_ADMIN_KEY\n});\n\nasync function sendNewsletter() {\n const result = await ur.mail.send({\n template: 'newsletter',\n to: 'subscribers@domain.com',\n data: { week: '24' }\n });\n console.log('Emails dispatched:', result.success);\n}` } ]; -const MOCK_EXTRA_FEATURES = [ +const APP_SERVICES = [ { - id: 'cdn', - title: 'Edge Caching & CDN', - icon: 'Cloud', - tag: 'EDGE NETWORK', - desc: 'Ultra-low latency static and dynamic content delivery closer to your users globally.', - color: 'purple', - accentColor: '#a855f7', - type: 'list', - bullets: ['Global Edge Network', 'Smart Cache Routing', 'Instant Invalidation'] + id: 'auth', + title: 'User Authentication', + badge: 'IDENTITY', + desc: 'Pre-configured secure login/signup flows, session handling with JWTs, and third-party login providers (GitHub, Google) with zero setup.', + icon: UserRound, + color: '#00f5d4', + visual: ( +
+
+
+
+
+
+
+
+ Sign in to App +
+
+ user@domain.com +
+
+ +
+
+
+
G
+
+
+
+ + + +
+
+
+ ) + }, + { + id: 'db', + title: 'JSON Document Database', + badge: 'DATABASE', + desc: 'Direct database mutations and queries right from your client application. Automated validation schemas protect integrity at high speed.', + icon: Database, + color: '#FFBD2E', + visual: ( +
+
+ const user = await db.find('users'); +
+ {"{"} +
+ "id": "usr_9x", + "active": true, + "role": "member" +
+ {"}"} +
+
+
+ ) + }, + { + id: 'storage', + title: 'Secure File Storage', + badge: 'STORAGE', + desc: 'Upload, manage, and deliver media assets like avatars, documents, and videos directly. Integrated with bucket systems and global delivery networks.', + icon: HardDrive, + color: '#409EFF', + visual: ( +
+
+
+ +
+ avatar_profile.png +
+ Uploading (54%)... + Complete • 1.4 MB +
+
+
+
+
+
+
+
+ ) + }, + { + id: 'mail', + title: 'Transactional Mailer', + badge: 'COMMUNICATION', + desc: 'Pre-wired email dispatchers for user verification, password recovery, and system notifications. Customize layouts using pre-made email templates.', + icon: Mail, + color: '#00e676', + visual: ( +
+
+
+ + verify@urbackend.com +
+
+
Welcome! Please verify email
+
Verify Account
+
+
+ ● Sending... + ✔ Sent successfully +
+
+
+ ) + }, + { + id: 'security', + title: 'Row-Level Security', + badge: 'SECURITY', + desc: 'Fine-grained read/write security configurations. Restrict access directly inside public-api based on document ownership and user authentication tokens.', + icon: Lock, + color: '#a855f7', + visual: ( +
+
RLS ENFORCED
+
+
policy "owner-write-only"
+
allow write: if owner == auth.id
+
allow read: if public
+
+
+ ) + }, + { + id: 'realtime', + title: 'Realtime Broadcast', + badge: 'REALTIME', + desc: 'Listen to database mutations instantly or broadcast custom events across clients using low-latency WebSocket connections.', + icon: Activity, + color: '#FF5F56', + visual: ( +
+
+
+ + Client A +
+
+
+
+
+ + Client B +
+
CONNECTED
+
+
+ ) } ]; -const renderFeatureIcon = (iconName) => { - switch (iconName) { - case 'Database': return ; - case 'Shield': return ; - case 'HardDrive': return ; - case 'Cpu': return ; - case 'Cloud': return ; - case 'RefreshCw': return ; - case 'Server': return ; - default: return ; - } -}; - function LandingPage() { - const { isAuthenticated } = useAuth(); + const { isAuthenticated, login } = useAuth(); const navigate = useNavigate(); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isNavVisible, setIsNavVisible] = useState(true); @@ -129,86 +526,37 @@ function LandingPage() { const [scrolled, setScrolled] = useState(false); const [openFaqIndex, setOpenFaqIndex] = useState(null); - const heroTimersRef = useRef([]); - const [collectionName, setCollectionName] = useState(''); - const [heroFields, setHeroFields] = useState([]); - const [isBuildingUi, setIsBuildingUi] = useState(false); - const [showDeploying, setShowDeploying] = useState(false); - const [showEndpoints, setShowEndpoints] = useState(false); - const [activeEndpoints, setActiveEndpoints] = useState([]); - - const timelineRef = useRef(null); - const { scrollYProgress } = useScroll({ - target: timelineRef, - offset: ["start end", "end end"] - }); - - const scaleY = useSpring(scrollYProgress, { - damping: 30, - stiffness: 100, - restDelta: 0.001 - }); - - const spineColor = useTransform( - scaleY, - [0, 0.20, 0.28, 0.53, 0.61, 0.86, 0.94, 1.0], - [ - "#00f5d4", "#00f5d4", - "#FFBD2E", "#FFBD2E", - "#409EFF", "#409EFF", - "#FF5F56", "#FF5F56" - ] - ); - - const spineGlow = useTransform( - scaleY, - [0, 0.20, 0.28, 0.53, 0.61, 0.86, 0.94, 1.0], - [ - "0 0 15px rgba(0, 245, 212, 0.6)", "0 0 15px rgba(0, 245, 212, 0.6)", - "0 0 15px rgba(255, 189, 46, 0.6)", "0 0 15px rgba(255, 189, 46, 0.6)", - "0 0 15px rgba(64, 158, 255, 0.6)", "0 0 15px rgba(64, 158, 255, 0.6)", - "0 0 15px rgba(255, 95, 86, 0.6)", "0 0 15px rgba(255, 95, 86, 0.6)" - ] - ); + const [authMode, setAuthMode] = useState('login'); + const [authForm, setAuthForm] = useState({ name: '', email: '', password: '' }); + const [showPassword, setShowPassword] = useState(false); + const [authLoading, setAuthLoading] = useState(false); + const [hoveredTech, setHoveredTech] = useState(null); - const [features, setFeatures] = useState(INITIAL_FEATURES); - const [isLoadingMore, setIsLoadingMore] = useState(false); - const [hasMore, setHasMore] = useState(false); - const [hoveredCardId, setHoveredCardId] = useState(null); - const [activeCardId, setActiveCardId] = useState(null); - - const observerTriggerRef = useRef(null); + const [studioStep, setStudioStep] = useState(6); + const [replayKey, setReplayKey] = useState(0); useEffect(() => { - if (!hasMore) return; - - const observer = new IntersectionObserver((entries) => { - const first = entries[0]; - if (first.isIntersecting && !isLoadingMore) { - setIsLoadingMore(true); - setTimeout(() => { - setFeatures(prev => [...prev, ...MOCK_EXTRA_FEATURES]); - setIsLoadingMore(false); - setHasMore(false); - }, 1200); + if (replayKey === 0) return; + + let active = true; + const runAnimation = async () => { + setStudioStep(0); + const delays = [600, 600, 600, 700, 900, 800]; + for (let i = 0; i < delays.length; i++) { + await new Promise((resolve) => setTimeout(resolve, delays[i])); + if (!active) return; + setStudioStep(i + 1); } - }, { - root: null, - rootMargin: '150px', - threshold: 0.1 - }); - - const currentTrigger = observerTriggerRef.current; - if (currentTrigger) { - observer.observe(currentTrigger); - } - + }; + runAnimation(); return () => { - if (currentTrigger) { - observer.unobserve(currentTrigger); - } + active = false; }; - }, [isLoadingMore, hasMore]); + }, [replayKey]); + + const triggerStudioReplay = () => { + setReplayKey((prev) => prev + 1); + }; @@ -250,52 +598,42 @@ function LandingPage() { return () => window.removeEventListener('scroll', handleScroll); }, []); - const clearHeroTimers = () => { - heroTimersRef.current.forEach(clearTimeout); - heroTimersRef.current = []; - }; - - const runHeroDemo = useCallback(() => { - clearHeroTimers(); - - // Wrap initial state updates in setTimeout to avoid synchronous setState inside useEffect - setTimeout(() => { - setCollectionName(''); - setHeroFields([]); - setIsBuildingUi(true); - setShowDeploying(false); - setShowEndpoints(false); - setActiveEndpoints([]); - }, 0); - - heroTimersRef.current.push(setTimeout(() => setCollectionName('users'), 400)); - heroTimersRef.current.push(setTimeout(() => setHeroFields([HERO_CLICK_STEPS[0]]), 900)); - heroTimersRef.current.push(setTimeout(() => setHeroFields([HERO_CLICK_STEPS[0], HERO_CLICK_STEPS[1]]), 1400)); - heroTimersRef.current.push(setTimeout(() => setHeroFields(HERO_CLICK_STEPS), 1900)); - heroTimersRef.current.push(setTimeout(() => setIsBuildingUi(false), 2200)); - heroTimersRef.current.push(setTimeout(() => setShowDeploying(true), 2500)); - heroTimersRef.current.push(setTimeout(() => { - setShowDeploying(false); - setShowEndpoints(true); - HERO_ENDPOINTS.forEach((_, index) => { - const timer = setTimeout(() => { - setActiveEndpoints(prev => [...prev, index]); - }, index * 160); - heroTimersRef.current.push(timer); - }); - }, 3600)); - }, []); - - useEffect(() => { - runHeroDemo(); - return () => clearHeroTimers(); - }, [runHeroDemo]); - const toggleFaq = (index) => { setOpenFaqIndex(openFaqIndex === index ? null : index); }; + const handleAuthChange = (e) => { + const { name, value } = e.target; + setAuthForm(prev => ({ ...prev, [name]: value })); + }; + + const handleAuthSubmit = async (e) => { + e.preventDefault(); + setAuthLoading(true); + try { + if (authMode === 'signup') { + const resp = await api.post('/api/auth/register', authForm); + if (resp.data?.success) { + navigate('/login'); + } + } else { + const resp = await api.post('/api/auth/login', { email: authForm.email, password: authForm.password }); + if (resp.data?.success) { + login(resp.data.user); + navigate('/dashboard'); + } + } + } catch (err) { + console.error('Auth error:', err); + } finally { + setAuthLoading(false); + } + }; + const handleGithubSignIn = () => { + setAuthLoading(true); + window.location.href = `${import.meta.env.VITE_PUBLIC_API_URL}/api/userAuth/github/start?x-api-key=${import.meta.env.VITE_PUBLIC_API_KEY}`; + }; return (
@@ -382,226 +720,538 @@ function LandingPage() {
-
-
- v0.10.0 — Now in Public Beta -
- - - Your MongoDB. Instant APIs. Zero boilerplate. - - - - Point urBackend at your Atlas cluster and get auth, storage, and REST APIs in 60 seconds. - - - - - Start Building - - - Documentation - - - - -
-
- - - -
-
- - urBackend Studio +
+
+
+
+ v0.10.0 — Now in Public Beta
- + + + Supabase for NoSQL. +
+ Bring your MongoDB, get instant APIs. +
+ + + urBackend is an open-source BaaS that turns your MongoDB into a production-ready backend + with REST APIs, authentication, file storage, and email — zero backend code. + + + +
+
+
Your MongoDB
+
Atlas or self-hosted
+
+
+
+
+
+
+
urBackend
+
BaaS engine
+
+
+
+
+
+
+
Instant APIs
+
Auth, Storage, Email
+
+
-
-
-
- - Collection Builder - UI Mode + +
+
+ +
-
-
- -
{collectionName || 'users'}
+ +
+ + +
+ or
-
- NAME - TYPE - REQ + + {authMode === 'signup' && ( +
+ +
+ + +
+
+ )} + +
+ +
+ + +
-
- {heroFields.map((field, index) => ( - + +
+ + +
-
- - {isBuildingUi && Auto-clicking...} + {showPassword ? : } + +
-
-
-
-
- -
- - {showDeploying && ( - - - Deploying... - + {authMode === 'login' && ( + + Forgot password? + )} - -
+ + +
+ +
+
-
-
- - Generated APIs - endpoints -
-
- {showEndpoints ? HERO_ENDPOINTS.map((endpoint, index) => ( - - {endpoint.method} - {endpoint.path} - {endpoint.status} - - )) : ( -
- -

Waiting for UI actions...

+ {/* Client App Services Showcase Section */} +
+
+
+
+ + Client SDK Services +

Built-in Powers for Your App Users

+

+ Don't waste time rebuilding boilerplate. urBackend provides premium, secure client-facing services out of the box. +

+
+ +
+ {APP_SERVICES.map((service, index) => { + const IconComponent = service.icon; + return ( + +
+
+
+ +
+ {service.badge}
- )} -
-
+
+

{service.title}

+

{service.desc}

+
+
+ {service.visual} +
+ + ); + })}
- +
+
-
+ + Infrastructure

Bring Your Own Infrastructure

Connect your existing MongoDB or buckets and get instant APIs without vendor lock-in.

-
- -
-
- - atlas://cluster0 + + + +
+
+ +
+
Your Database
+
MongoDB Atlas / Self-Hosted
+
+
+
+
+
+
+
+
-
-
+
+
+ +
+
urBackend Engine
+
BaaS Middleware Layer
+
-
- urBackend +
+
+
+
+
+
-
-
+
+
+ +
+
Instant APIs
+
REST · Auth · Storage · Email
+
-
- - REST APIs + + + {/* Interactive Window showing collection building to instant endpoints */} + setReplayKey((prev) => prev === 0 ? 1 : prev)} + transition={{ duration: 0.6 }} + > +
+
+ + + +
+
+ + urBackend Studio +
+
-
- -
-
-
-
- +
+
+
+ + Collection Builder + UI Mode +
+
+
+ +
users
+
+
+ NAME + TYPE + REQ +
+
+
= 1 ? 'visible' : ''}`}> + name + String + +
+
= 2 ? 'visible' : ''}`}> + email + String + +
+
= 3 ? 'visible' : ''}`}> + role + String + +
+
+
+ +
+
+
+
+
+
+
+
+
-

BYO Database

-

+

+
+ + Generated APIs + endpoints +
+
= 5 ? 'visible' : ''}`}> +
+ GET + /api/users + 200 OK +
+
+ POST + /api/users + 201 Created +
+
+ GET + /api/users/:id + 200 OK +
+
+ PUT + /api/users/:id + 200 OK +
+
+ DELETE + /api/users/:id + 200 OK +
+
+
+
+ + +
+ +
+
+ +
+

BYO Database

+

Connect your self-hosted MongoDB or Atlas cluster. We provide the instant API layer, auth, and validation schema, while you keep full ownership of the data.

-
- MongoDB Atlas - Self-Hosted +
+ MongoDB Atlas + Self-Hosted
-
- -
-
-
- -
-

BYO Storage

+ + + +
+
+
-

+

BYO Storage

+

Link your Supabase Storage, AWS S3, or Cloudflare R2 buckets. We handle upload tokens, permissions, and CDN delivery automatically.

-
- Supabase - AWS S3 - Cloudflare R2 +
+ Supabase + AWS S3 + Cloudflare R2
-
+
-
-

Integrates with your favorite stack

-
-
Flutter
-
React
-
Vue
-
Next.js
-
Webflow
+
+
+ + Ecosystem +

Compatible with your favorite stack

+

Connect from any framework or platform. Enjoy official client SDKs, clean typed models, or direct REST connections.

+
+ +
+
+
+ {/* Central Sun */} +
+
+ urBackend +
+
+
+ + {/* Cosmic Orbit Dust */} +
+
+
+
+
+
+
+ + {/* Inner Orbit */} +
+ {COMPATIBLE_TECHNOLOGIES.filter(t => t.ring === 'inner').map((item, i, arr) => ( +
setHoveredTech(item)} + onMouseLeave={() => setHoveredTech(null)} + > +
+
+ {item.svg} + {item.label} +
+
+ ))} + + {/* Mid Orbit */} +
+ {COMPATIBLE_TECHNOLOGIES.filter(t => t.ring === 'mid').map((item, i, arr) => ( +
setHoveredTech(item)} + onMouseLeave={() => setHoveredTech(null)} + > +
+
+ {item.svg} + {item.label} +
+
+ ))} + + {/* Outer Orbit */} +
+ {COMPATIBLE_TECHNOLOGIES.filter(t => t.ring === 'outer').map((item, i, arr) => ( +
setHoveredTech(item)} + onMouseLeave={() => setHoveredTech(null)} + > +
+
+ {item.svg} + {item.label} +
+
+ ))} +
+
+
@@ -642,158 +1292,6 @@ function LandingPage() {
-
-
-
-

Complete Backend Suite

-

Enterprise-grade tools packaged for individual developers.

-
- -
-
- -
- -
- {features.map((feature, index) => { - const isEven = index % 2 === 0; - const isHighlighted = hoveredCardId === feature.id || activeCardId === feature.id; - - return ( - setActiveCardId(feature.id)} - transition={{ duration: 0.6, ease: "easeOut" }} - > - {/* Central Line Node */} -
-
-
- - {/* Card Column wrapper */} -
-
setHoveredCardId(feature.id)} - onMouseLeave={() => setHoveredCardId(null)} - > -
{feature.tag}
-
- {renderFeatureIcon(feature.icon)} -
-

{feature.title}

-

{feature.desc}

- - {/* Render different block elements based on feature card template requirements */} - {feature.type === 'list' && ( -
    - {feature.bullets.map((bullet, bIdx) => ( -
  • - {bullet} -
  • - ))} -
- )} - - {feature.type === 'auth-info' && ( -
-
- {feature.id === 'webhooks' ? : } - {feature.id === 'webhooks' ? 'Event Filtering' : 'Encrypted Passwords'} -
-
- {feature.id === 'webhooks' ? : } - {feature.id === 'webhooks' ? '30-second Auto Retries' : 'Role Based Access'} -
-
- )} - - {feature.type === 'tags' && ( -
- {feature.bullets.map((tag, tIdx) => ( - {tag} - ))} -
- )} -
-
- - {/* Opposite Column: Tag Metadata / Big Step Label */} -
-
- {feature.tag} - 0{index + 1} -
-
- - ); - })} -
- - {/* Infinite Scroll Load Trigger / Simulated Loading indicator */} -
- {isLoadingMore && ( - -
- - - -
- Assembling extra platform architectures... -
- )} - {!hasMore && ( -
- Complete Enterprise Features Assembled • MIT Core Live -
- )} -
-
-
diff --git a/apps/web-dashboard/src/pages/LandingPage/style.css b/apps/web-dashboard/src/pages/LandingPage/style.css index e6e91e017..4ced93479 100644 --- a/apps/web-dashboard/src/pages/LandingPage/style.css +++ b/apps/web-dashboard/src/pages/LandingPage/style.css @@ -252,6 +252,20 @@ filter: drop-shadow(0 0 10px rgba(0, 245, 212, 0.3)); } +.section-badge { + display: inline-block; + padding: 0.35rem 0.9rem; + background: rgba(0,245,212,0.06); + border: 1px solid rgba(0,245,212,0.12); + border-radius: 20px; + font-size: 0.72rem; + font-weight: 600; + color: #00f5d4; + letter-spacing: 0.08em; + text-transform: uppercase; + margin-bottom: 1rem; +} + .section-title { font-size: 2.5rem; font-weight: 700; @@ -318,21 +332,354 @@ transform: translateY(-2px); } -/* HERO */ +.hero-ctas { + display: flex; + gap: 1rem; + flex-wrap: wrap; + justify-content: center; + z-index: 10; + position: relative; +} + +.btn-hero-login { + padding: 0.85rem 2rem; + font-size: 1.05rem; + border-radius: 99px; + font-weight: 600; + text-decoration: none; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid rgba(255,255,255,0.15); + color: #fff; + background: rgba(255,255,255,0.05); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: inset 0 1px 0 rgba(255,255,255,0.05); +} + +.btn-hero-login:hover { + background: rgba(255,255,255,0.1); + border-color: rgba(255,255,255,0.3); + transform: translateY(-2px); +} + +.btn-hero-ghost { + padding: 0.85rem 1.5rem; + font-size: 1rem; + border-radius: 99px; + font-weight: 500; + text-decoration: none; + display: flex; + align-items: center; + justify-content: center; + color: #888; + transition: all 0.3s ease; +} + +.btn-hero-ghost:hover { + color: #fff; + transform: translateY(-2px); +} + +/* HERO SPLIT LAYOUT */ .hero-section { position: relative; - padding-top: 2rem; - padding-bottom: 4rem; + padding: 2rem 1.5rem 4rem; + z-index: 1; + min-height: calc(100vh - 80px); + display: flex; + align-items: center; + justify-content: center; + margin-top: var(--nav-height, 80px); +} + +@media(max-width: 1024px) { + .hero-section { + min-height: auto; + padding: 6rem 1.5rem 4rem; + } +} + +.hero-split { + display: grid; + grid-template-columns: 1fr 420px; + gap: 3rem; + align-items: center; + max-width: 1200px; + width: 100%; + position: relative; z-index: 1; +} + +.hero-split-left { + display: flex; + flex-direction: column; + align-items: flex-start; + text-align: left; +} + +.hero-split-left .status-pill { + margin-left: 0; +} + +.hero-split-left .hero-heading { + margin-left: 0; + margin-right: 0; + text-align: left; + font-size: clamp(2rem, 3.5vw, 3rem); +} + +.hero-split-left .hero-sub { + margin-left: 0; + margin-right: 0; + text-align: left; + max-width: 520px; +} + +.hero-split-left .hero-flow { + margin-top: 2rem; + justify-content: flex-start; + max-width: 550px; + padding: 0; +} + +/* HERO AUTH CARD */ +.hero-split-right { + width: 100%; +} + +.hero-auth-card { + background: rgba(255,255,255,0.03); + border: 1px solid rgba(255,255,255,0.08); + border-radius: 16px; + padding: 1.75rem; + box-shadow: 0 24px 48px -12px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.05); + backdrop-filter: blur(12px); +} + +.hero-auth-tabs { + display: flex; + background: rgba(255,255,255,0.04); + border-radius: 10px; + padding: 3px; + margin-bottom: 1.5rem; + border: 1px solid rgba(255,255,255,0.06); +} + +.hero-auth-tab { + flex: 1; + padding: 0.6rem 1rem; + border: none; + border-radius: 8px; + background: transparent; + color: #888; + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; +} + +.hero-auth-tab.active { + background: rgba(0, 245, 212, 0.1); + color: #00f5d4; + box-shadow: 0 0 12px rgba(0, 245, 212, 0.08); +} + +.hero-auth-form { display: flex; flex-direction: column; + gap: 0.9rem; +} + +.hero-auth-github { + width: 100%; + padding: 0.7rem 1rem; + border-radius: 10px; + border: 1px solid rgba(255,255,255,0.12); + background: rgba(255,255,255,0.04); + color: #fff; + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + display: flex; align-items: center; + justify-content: center; + gap: 8px; + transition: all 0.2s ease; +} + +.hero-auth-github:hover { + background: rgba(255,255,255,0.08); + border-color: rgba(255,255,255,0.25); +} + +.hero-auth-divider { + position: relative; text-align: center; - min-height: calc(100vh - 80px); + color: #555; + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.15em; + margin: 0.1rem 0; +} + +.hero-auth-divider::before, +.hero-auth-divider::after { + content: ''; + position: absolute; + top: 50%; + width: calc(50% - 1.5rem); + height: 1px; + background: rgba(255,255,255,0.08); +} + +.hero-auth-divider::before { left: 0; } +.hero-auth-divider::after { right: 0; } + +.hero-auth-field { + display: flex; + flex-direction: column; + gap: 0.35rem; +} + +.hero-auth-field label { + font-size: 0.8rem; + color: #aaa; + font-weight: 600; +} + +.hero-auth-input-wrap { + position: relative; + display: flex; + align-items: center; +} + +.hero-auth-input-wrap > svg { + position: absolute; + left: 12px; + color: #666; + pointer-events: none; +} + +.hero-auth-input-wrap input { + width: 100%; + padding: 0.7rem 2.4rem 0.7rem 2.3rem; + border-radius: 10px; + border: 1px solid rgba(255,255,255,0.1); + background: rgba(0,0,0,0.3); + color: #fff; + font-size: 0.9rem; + outline: none; + transition: all 0.2s ease; +} + +.hero-auth-input-wrap input:focus { + border-color: rgba(0, 245, 212, 0.4); + box-shadow: 0 0 0 3px rgba(0, 245, 212, 0.06); +} + +.hero-auth-input-wrap input::placeholder { + color: #555; +} + +.hero-auth-toggle { + position: absolute; + right: 8px; + width: 30px; + height: 30px; + display: flex; + align-items: center; justify-content: center; - margin-top: var(--nav-height, 80px); + border: none; + border-radius: 6px; + background: transparent; + color: #666; + cursor: pointer; + transition: all 0.2s ease; +} + +.hero-auth-toggle:hover { + background: rgba(255,255,255,0.05); + color: #fff; } +.hero-auth-forgot { + font-size: 0.8rem; + color: #00f5d4; + text-decoration: none; + font-weight: 500; + align-self: flex-end; + margin-top: -0.3rem; +} + +.hero-auth-forgot:hover { + text-decoration: underline; +} + +.hero-auth-submit { + width: 100%; + padding: 0.75rem 1rem; + border-radius: 10px; + border: none; + background: linear-gradient(135deg, #00f5d4 0%, #00c9a7 100%); + color: #000; + font-size: 0.95rem; + font-weight: 700; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 20px rgba(0, 245, 212, 0.2); + margin-top: 0.2rem; +} + +.hero-auth-submit:hover { + transform: translateY(-1px); + box-shadow: 0 6px 24px rgba(0, 245, 212, 0.3); +} + +.hero-auth-submit:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + +@media(max-width: 1024px) { + .hero-split { + grid-template-columns: 1fr; + gap: 2rem; + } + + .hero-split-left { + align-items: center; + text-align: center; + } + + .hero-split-left .status-pill { + margin-left: auto; + } + + .hero-split-left .hero-heading { + text-align: center; + } + + .hero-split-left .hero-sub { + text-align: center; + margin-left: auto; + margin-right: auto; + } + + .hero-split-left .hero-flow { + justify-content: center; + } + + .hero-split-right { + max-width: 420px; + margin: 0 auto; + width: 100%; + } +} + +/* HERO */ + .status-pill { background: rgba(255, 255, 255, 0.04); border: 1px solid rgba(255, 255, 255, 0.12); @@ -404,23 +751,130 @@ margin-right: auto; } -/* SIMPLE FLOW DIAGRAM */ -.stack-container { - margin-top: 2rem; +/* HERO FLOW DIAGRAM */ +.hero-flow { + margin-top: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 0; + z-index: 10; position: relative; width: 100%; - max-width: 900px; + max-width: 700px; + padding: 0 1rem; +} + +.hf-step { display: flex; flex-direction: column; align-items: center; - gap: 2rem; - margin-left: auto; - margin-right: auto; + gap: 0.5rem; + padding: 1.25rem 1.5rem; + background: rgba(255,255,255,0.02); + border: 1px solid rgba(255,255,255,0.08); + border-radius: 14px; + transition: all 0.4s ease; + flex: 1; + min-width: 0; } -.simple-flow { - display: flex; - align-items: center; +.hf-step:hover { + transform: translateY(-3px); + border-color: rgba(255,255,255,0.2); + background: rgba(255,255,255,0.04); + box-shadow: 0 12px 32px rgba(0,0,0,0.3); +} + +.hf-highlight { + border-color: rgba(0, 245, 212, 0.25); + background: rgba(0, 245, 212, 0.04); + box-shadow: 0 0 20px rgba(0, 245, 212, 0.06); +} + +.hf-highlight:hover { + border-color: rgba(0, 245, 212, 0.4); + background: rgba(0, 245, 212, 0.06); + box-shadow: 0 12px 32px rgba(0, 245, 212, 0.1); +} + +.hf-icon { + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255,255,255,0.04); + border: 1px solid rgba(255,255,255,0.1); + border-radius: 12px; + color: #00f5d4; +} + +.hf-highlight .hf-icon { + background: rgba(0, 245, 212, 0.1); + border-color: rgba(0, 245, 212, 0.25); + color: #00f5d4; + box-shadow: 0 0 15px rgba(0, 245, 212, 0.15); +} + +.hf-label { + font-size: 0.9rem; + font-weight: 600; + color: #fff; + white-space: nowrap; +} + +.hf-desc { + font-size: 0.75rem; + color: #666; + white-space: nowrap; +} + +.hf-connector { + width: 60px; + height: 2px; + background: rgba(255,255,255,0.08); + position: relative; + flex-shrink: 0; +} + +.hf-dot { + position: absolute; + left: 0; + top: 50%; + width: 8px; + height: 8px; + background: #00f5d4; + border-radius: 50%; + transform: translateY(-50%); + box-shadow: 0 0 10px rgba(0, 245, 212, 0.6); + animation: hfDotMove 2s ease-in-out infinite; +} + +@keyframes hfDotMove { + 0% { left: 0; opacity: 0; } + 10% { opacity: 1; } + 90% { opacity: 1; } + 100% { left: calc(100% - 8px); opacity: 0; } +} + +/* SIMPLE FLOW DIAGRAM */ +.stack-container { + margin-top: 2rem; + position: relative; + width: 100%; + max-width: 900px; + display: flex; + flex-direction: column; + align-items: center; + gap: 2rem; + margin-left: auto; + margin-right: auto; +} + +.simple-flow { + display: flex; + align-items: center; justify-content: center; gap: 1.5rem; flex-wrap: wrap; @@ -580,550 +1034,599 @@ } /* LANDING HERO INTERACTIVE WINDOW */ -.hero-interactive-window { - margin-top: 2rem; +/* DEMO CONSOLE */ +.demo-wrapper { width: 100%; - max-width: 1100px; - border: 1px solid rgba(255, 255, 255, 0.1); + background: #000; + border: 1px solid rgba(255, 255, 255, 0.05); border-radius: 16px; + box-shadow: 0 40px 80px -20px rgba(0, 0, 0, 0.9), 0 0 0 1px rgba(255,255,255,0.01) inset; overflow: hidden; - background: rgba(255, 255, 255, 0.03); - backdrop-filter: blur(8px); - box-shadow: 0 24px 64px rgba(0, 0, 0, 0.45); + animation: fadeInUp 1.2s cubic-bezier(0.2, 0.8, 0.2, 1); + position: relative; } -.lh-header { - height: 46px; +.demo-header { + background: #09090b; + padding: 12px 16px; display: flex; align-items: center; - justify-content: space-between; - padding: 0 0.8rem; - border-bottom: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(0, 0, 0, 0.35); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + gap: 1rem; } -.lh-dots { +.url-bar { + flex: 1; + background: #000; + color: #a1a1aa; + font-family: 'JetBrains Mono', monospace; + padding: 6px 12px; + border-radius: 6px; + font-size: 0.8125rem; display: flex; - gap: 0.35rem; -} - -.lh-dot { - width: 10px; - height: 10px; - border-radius: 50%; -} - -.lh-dot-red { background: #ff5f57; } -.lh-dot-yellow { background: #febc2e; } -.lh-dot-green { background: #28c840; } - -.lh-title { - display: inline-flex; + justify-content: space-between; align-items: center; - gap: 0.35rem; - color: #d4d4d8; - font-size: 0.86rem; + border: 1px solid rgba(255, 255, 255, 0.08); } -.lh-replay { - border: 1px solid rgba(255, 255, 255, 0.15); - background: rgba(255, 255, 255, 0.05); - color: #fff; - border-radius: 8px; - padding: 0.3rem 0.55rem; - cursor: pointer; +.demo-content { + padding: 0; + display: flex; + flex-direction: column; + background: #000; } -.lh-content { - display: grid; - grid-template-columns: 1fr 120px 1fr; - min-height: 420px; -} +@media(min-width: 768px) { + .demo-content { + flex-direction: row; + min-height: 420px; + } -.lh-pane { - border-right: 1px solid rgba(255, 255, 255, 0.06); -} + .demo-sidebar { + width: 240px; + border-right: 1px solid rgba(255, 255, 255, 0.08); + } -.lh-pane-right { - border-right: none; + .demo-main { + flex: 1; + padding: 1.5rem; + } } -.lh-pane-header { +.run-btn { + position: absolute; + top: 1.5rem; + right: 1.5rem; + background: #00f5d4; + color: #000; + border: none; + padding: 8px 16px; + border-radius: 6px; + font-weight: 600; + font-size: 0.85rem; + cursor: pointer; display: flex; align-items: center; - gap: 0.5rem; - padding: 0.7rem 0.85rem; - border-bottom: 1px solid rgba(255, 255, 255, 0.06); - color: #e4e4e7; - font-size: 0.85rem; + gap: 6px; + z-index: 10; + box-shadow: 0 0 15px rgba(0, 245, 212, 0.4), inset 0 1px 1px rgba(255, 255, 255, 0.4); + transition: all 0.3s; } -.lh-pane-label { - margin-left: auto; - color: #71717a; - font-size: 0.78rem; +.run-btn:hover { + box-shadow: 0 0 25px rgba(0, 245, 212, 0.7), inset 0 1px 1px rgba(255, 255, 255, 0.5); + transform: translateY(-1px); } -.lh-builder { - padding: 0.95rem; - display: flex; - flex-direction: column; - gap: 0.75rem; +.demo-sidebar { + background: linear-gradient(to right, rgba(255, 255, 255, 0.02) 0%, transparent 100%); + border-right: 1px solid rgba(255, 255, 255, 0.05); } -.lh-group { +.demo-nav-item { + padding: 0.75rem 1rem; display: flex; - flex-direction: column; - gap: 0.35rem; + align-items: center; + gap: 0.75rem; + color: #888; + cursor: pointer; + transition: all 0.2s ease; + border-radius: 6px; + margin-bottom: 4px; + font-size: 0.9rem; + font-weight: 500; } -.lh-group label { - font-size: 0.74rem; - color: #71717a; - letter-spacing: 0.05em; - text-transform: uppercase; - font-weight: 700; +.demo-nav-item:hover { + background: rgba(255, 255, 255, 0.03); + color: #fff; } -.lh-input { - border: 1px solid rgba(255, 255, 255, 0.12); - border-radius: 8px; - padding: 0.55rem 0.6rem; - color: #e4e4e7; - background: rgba(255, 255, 255, 0.03); - min-height: 34px; - text-align: left; +.demo-nav-item.active { + color: #00f5d4; + background: rgba(0, 245, 212, 0.08); + border-left: 2px solid #00f5d4; + padding-left: 14px; } -.lh-head-row { - display: grid; - grid-template-columns: 1.4fr 0.8fr 0.4fr; - gap: 0.4rem; - font-size: 0.72rem; - color: #71717a; - letter-spacing: 0.05em; - text-transform: uppercase; - font-weight: 700; - padding: 0 0.2rem; - text-align: left; +/* Loading Spinner */ +.loading-spinner { + width: 16px; + height: 16px; + border: 2px solid rgba(0, 245, 212, 0.2); + border-top-color: #00f5d4; + border-radius: 50%; + animation: spin 0.8s linear infinite; } -.lh-table { - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 10px; - background: rgba(0, 0, 0, 0.2); - overflow: hidden; +@keyframes spin { + to { transform: rotate(360deg); } } -.lh-row { - display: grid; - grid-template-columns: 1.4fr 0.8fr 0.4fr; - gap: 0.4rem; - align-items: center; - padding: 0.55rem 0.6rem; - border-bottom: 1px solid rgba(255, 255, 255, 0.06); - text-align: left; +.dot { + width: 12px; + height: 12px; + border-radius: 50%; + box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.3), 0 0 4px rgba(0,0,0,0.5); } -.lh-row:last-child { - border-bottom: none; +/* HOW IT WORKS */ +.step-card { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 2.5rem; + background: linear-gradient(145deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.01) 100%); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 16px; + position: relative; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 10px 30px -10px rgba(0,0,0,0.5); + transition: transform 0.3s, border-color 0.3s, box-shadow 0.3s; } -.lh-name { - color: #f4f4f5; - font-size: 0.84rem; +.step-card:hover { + transform: translateY(-4px); + border-color: rgba(0, 245, 212, 0.3); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 20px 40px -15px rgba(0, 245, 212, 0.1); } -.lh-type { - display: inline-flex; - justify-content: center; - padding: 0.15rem 0.4rem; - border-radius: 999px; - font-size: 0.72rem; - color: #d4d4d8; - border: 1px solid rgba(255, 255, 255, 0.2); - background: rgba(255, 255, 255, 0.04); - width: fit-content; -} - -.lh-req { - display: inline-flex; +.step-number { + width: 48px; + height: 48px; + background: linear-gradient(145deg, rgba(0, 245, 212, 0.2) 0%, rgba(0, 245, 212, 0.05) 100%); + border: 1px solid rgba(0, 245, 212, 0.3); + box-shadow: 0 0 15px rgba(0, 245, 212, 0.2); + border-radius: 50%; + display: flex; align-items: center; justify-content: center; - min-height: 20px; + font-weight: 700; + font-size: 1.2rem; + margin-bottom: 1.5rem; + color: #fff; } -.lh-req.on { color: #4ade80; } -.lh-req.off { color: #52525b; } -.lh-actions { + +.bento-icon { + width: 48px; + height: 48px; + background: rgba(255, 255, 255, 0.03); + border-radius: 10px; display: flex; align-items: center; - justify-content: space-between; + justify-content: center; + margin-bottom: 1.25rem; + color: #fff; + border: 1px solid rgba(255, 255, 255, 0.08); } -.lh-add-btn { - display: inline-flex; - align-items: center; - gap: 0.35rem; - border: 1px solid rgba(255, 255, 255, 0.18); - background: rgba(255, 255, 255, 0.04); - color: #e4e4e7; - border-radius: 8px; - font-size: 0.75rem; - padding: 0.38rem 0.55rem; +.bento-title { + font-size: 1.375rem; + font-weight: 600; + margin-bottom: 0.75rem; + letter-spacing: -0.015em; + color: #fff; } -.lh-live { - font-size: 0.75rem; - color: #00f5d4; +.bento-desc { + color: #a1a1aa; + font-size: 1.05rem; + line-height: 1.6; } -.lh-middle { - border-right: 1px solid rgba(255, 255, 255, 0.06); - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 1rem; +/* USE CASES & EXTRA CARDS */ +.use-case-card, .glass-card { + background: linear-gradient(145deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.01) 100%); + border: 1px solid rgba(255, 255, 255, 0.06); + padding: 2.5rem; + border-radius: 16px; + text-align: left; + transition: all 0.3s; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03), 0 10px 30px -10px rgba(0,0,0,0.5); position: relative; + overflow: hidden; } -.lh-line { - width: 90px; - height: 4px; - border-radius: 999px; - background: rgba(0, 245, 212, 0.2); - overflow: hidden; - box-shadow: 0 0 16px rgba(0, 245, 212, 0.25); +.use-case-card:hover, .glass-card:hover, .pricing-card:hover { + border-color: rgba(255, 255, 255, 0.15); + transform: translateY(-4px); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 20px 40px -15px rgba(0,0,0,0.6); } -.lh-pulse { - width: 30px; - height: 4px; - background: #00f5d4; - border-radius: 999px; - box-shadow: 0 0 14px #00f5d4; +.pricing-card { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } -.lh-deploying { +/* Integration - 3D Globe Section */ +.integration-section { + padding: 3rem 0 4rem; + background: #030303; + text-align: center; + position: relative; + overflow: hidden; + border-top: 1px solid rgba(255,255,255,0.04); +} +.integration-section::before { + content: ''; position: absolute; - top: 35%; - display: inline-flex; - gap: 0.3rem; - align-items: center; - padding: 0.35rem 0.6rem; - border-radius: 999px; - font-size: 0.75rem; - border: 1px solid rgba(0, 245, 212, 0.5); - color: #00f5d4; - background: rgba(0, 245, 212, 0.12); + inset: 0; + background: radial-gradient(ellipse 60% 50% at 50% 50%, rgba(0,245,212,0.03) 0%, transparent 70%); + pointer-events: none; } -.lh-engine { - width: 44px; - height: 44px; - border-radius: 12px; - border: 1px solid rgba(0, 245, 212, 0.5); - background: rgba(0, 245, 212, 0.12); - display: grid; - place-items: center; - color: #00f5d4; +.integration-inner { + max-width: 1200px; + margin: 0 auto; + padding: 0 1.5rem; + margin-bottom: 2rem; + position: relative; + z-index: 1; } -.lh-endpoints { - padding: 0.8rem; - display: grid; - gap: 0.55rem; +.integration-heading { + color: #888; + font-size: 0.9rem; + font-weight: 500; + letter-spacing: 0.1em; + text-transform: uppercase; + margin-top: 0.5rem; } -.lh-endpoint { - display: grid; - grid-template-columns: 66px 1fr auto; - gap: 0.55rem; +/* Orbit Stage - Solar System */ +.orbit-stage-wrapper { + position: relative; + width: 100%; + display: flex; + justify-content: center; align-items: center; - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 10px; - padding: 0.45rem 0.55rem; - color: #d4d4d8; - background: rgba(255, 255, 255, 0.02); + padding: 3rem 0; } -.lh-endpoint.active { - border-color: rgba(0, 245, 212, 0.55); - box-shadow: 0 0 0 1px rgba(0, 245, 212, 0.2) inset; +.orbit-stage { + position: relative; + width: 940px; + height: 450px; + display: flex; + align-items: center; + justify-content: center; + perspective: 1200px; + overflow: visible; + --radius-inner: 175px; + --radius-mid: 285px; + --radius-outer: 395px; } -.lh-method { - font-size: 0.72rem; - border-radius: 6px; - text-align: center; - padding: 0.2rem 0.35rem; - font-weight: 700; - color: #000; +@media (max-width: 768px) { + .orbit-stage { + width: 100%; + max-width: 500px; + height: 240px; + --radius-inner: 100px; + --radius-mid: 165px; + --radius-outer: 230px; + } } -.lh-method.get { background: #22c55e; } -.lh-method.post { background: #3b82f6; } -.lh-method.put { background: #f59e0b; } -.lh-method.delete { background: #ef4444; color: #fff; } - -.lh-endpoint code { - color: #e4e4e7; - font-size: 0.85rem; +@media (max-width: 480px) { + .orbit-stage { + height: 180px; + --radius-inner: 70px; + --radius-mid: 115px; + --radius-outer: 160px; + } } -.lh-status { - display: inline-flex; +.orbit-container { + position: absolute; + width: 940px; + height: 940px; + display: flex; align-items: center; - gap: 0.25rem; - color: #4ade80; - font-size: 0.75rem; + justify-content: center; + transform: rotateX(65deg) rotateY(-10deg); + transform-style: preserve-3d; } -.lh-empty { - min-height: 250px; - display: grid; - place-items: center; - color: #71717a; - text-align: center; +@media (max-width: 768px) { + .orbit-container { + width: 500px; + height: 500px; + } } -/* DEMO CONSOLE */ -.demo-wrapper { - width: 100%; - background: #000; - border: 1px solid rgba(255, 255, 255, 0.05); - border-radius: 16px; - box-shadow: 0 40px 80px -20px rgba(0, 0, 0, 0.9), 0 0 0 1px rgba(255,255,255,0.01) inset; - overflow: hidden; - animation: fadeInUp 1.2s cubic-bezier(0.2, 0.8, 0.2, 1); - position: relative; +@media (max-width: 480px) { + .orbit-container { + width: 360px; + height: 360px; + } } -.demo-header { - background: #09090b; - padding: 12px 16px; - display: flex; - align-items: center; - border-bottom: 1px solid rgba(255, 255, 255, 0.05); - gap: 1rem; -} -.url-bar { - flex: 1; - background: #000; - color: #a1a1aa; - font-family: 'JetBrains Mono', monospace; - padding: 6px 12px; - border-radius: 6px; - font-size: 0.8125rem; +.orbit-sun { + position: absolute; + width: 130px; + height: 130px; display: flex; - justify-content: space-between; align-items: center; - border: 1px solid rgba(255, 255, 255, 0.08); + justify-content: center; + z-index: 10; + pointer-events: none; + transform-style: preserve-3d; + transform: rotateY(10deg) rotateX(-65deg); } -.demo-content { - padding: 0; - display: flex; - flex-direction: column; - background: #000; +.orbit-sun-logo { + width: 80px; + height: 80px; + border-radius: 50%; + border: 2px solid rgba(0, 245, 212, 0.4); + box-shadow: 0 0 30px rgba(0, 245, 212, 0.4); + z-index: 2; + background: #09090b; + padding: 10px; } -@media(min-width: 768px) { - .demo-content { - flex-direction: row; - min-height: 420px; - } - - .demo-sidebar { - width: 240px; - border-right: 1px solid rgba(255, 255, 255, 0.08); - } +.orbit-sun-glow { + position: absolute; + width: 120px; + height: 120px; + border-radius: 50%; + background: radial-gradient(circle, rgba(0, 245, 212, 0.35) 0%, transparent 70%); + filter: blur(10px); + z-index: 1; + animation: sun-pulse 4s ease-in-out infinite alternate; +} - .demo-main { - flex: 1; - padding: 1.5rem; - } +@keyframes sun-pulse { + 0% { transform: scale(0.9); opacity: 0.7; } + 100% { transform: scale(1.15); opacity: 1; } } -.run-btn { +.orbit-sun-ring-dec { position: absolute; - top: 1.5rem; - right: 1.5rem; - background: #00f5d4; - color: #000; - border: none; - padding: 8px 16px; - border-radius: 6px; - font-weight: 600; - font-size: 0.85rem; - cursor: pointer; - display: flex; - align-items: center; - gap: 6px; - z-index: 10; - box-shadow: 0 0 15px rgba(0, 245, 212, 0.4), inset 0 1px 1px rgba(255, 255, 255, 0.4); - transition: all 0.3s; + border-radius: 50%; + border: 1px solid rgba(0, 245, 212, 0.15); + transform: rotateX(65deg) rotateY(-10deg); + pointer-events: none; } -.run-btn:hover { - box-shadow: 0 0 25px rgba(0, 245, 212, 0.7), inset 0 1px 1px rgba(255, 255, 255, 0.5); - transform: translateY(-1px); +.orbit-sun-ring-1 { + width: 140px; + height: 140px; + border: 1px dashed rgba(0, 245, 212, 0.2); + animation: spin-clockwise 20s linear infinite; } -.demo-sidebar { - background: linear-gradient(to right, rgba(255, 255, 255, 0.02) 0%, transparent 100%); - border-right: 1px solid rgba(255, 255, 255, 0.05); +.orbit-sun-ring-2 { + width: 185px; + height: 185px; + border: 1px dashed rgba(0, 245, 212, 0.09); + animation: spin-counter-clockwise 30s linear infinite; } -.demo-nav-item { - padding: 0.75rem 1rem; - display: flex; - align-items: center; - gap: 0.75rem; - color: #888; - cursor: pointer; - transition: all 0.2s ease; - border-radius: 6px; - margin-bottom: 4px; - font-size: 0.9rem; - font-weight: 500; +@keyframes spin-clockwise { + 0% { transform: rotateX(65deg) rotateY(-10deg) rotateZ(0deg); } + 100% { transform: rotateX(65deg) rotateY(-10deg) rotateZ(360deg); } } -.demo-nav-item:hover { - background: rgba(255, 255, 255, 0.03); - color: #fff; +@keyframes spin-counter-clockwise { + 0% { transform: rotateX(65deg) rotateY(-10deg) rotateZ(0deg); } + 100% { transform: rotateX(65deg) rotateY(-10deg) rotateZ(-360deg); } } -.demo-nav-item.active { - color: #00f5d4; - background: rgba(0, 245, 212, 0.08); - border-left: 2px solid #00f5d4; - padding-left: 14px; +.orbit-ring { + position: absolute; + border-radius: 50%; + border: 1.5px dashed rgba(255, 255, 255, 0.15); + pointer-events: none; + transform-style: preserve-3d; + width: calc(2 * var(--orbit-radius)); + height: calc(2 * var(--orbit-radius)); + box-shadow: inset 0 0 25px rgba(255, 255, 255, 0.02), 0 0 25px rgba(255, 255, 255, 0.02); } -/* Loading Spinner */ -.loading-spinner { - width: 16px; - height: 16px; - border: 2px solid rgba(0, 245, 212, 0.2); - border-top-color: #00f5d4; - border-radius: 50%; - animation: spin 0.8s linear infinite; +.orbit-ring-inner { + --orbit-radius: var(--radius-inner); } -@keyframes spin { - to { transform: rotate(360deg); } +.orbit-ring-mid { + --orbit-radius: var(--radius-mid); } -.dot { - width: 12px; - height: 12px; +.orbit-ring-outer { + --orbit-radius: var(--radius-outer); +} + +/* Cosmic Orbit Dust */ +.orbit-dust { + position: absolute; + left: 50%; + top: 50%; + width: 3px; + height: 3px; + background: var(--dust-color, #00f5d4); border-radius: 50%; - box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.3), 0 0 4px rgba(0,0,0,0.5); + box-shadow: 0 0 6px var(--dust-color, #00f5d4); + opacity: 0.35; + pointer-events: none; + transform-style: preserve-3d; + animation: orbitMove 24s linear infinite; } -/* HOW IT WORKS */ -.step-card { +.orbit-item-wrapper { + position: absolute; + left: 50%; + top: 50%; + width: 0; + height: 0; + transform-style: preserve-3d; + pointer-events: none; +} + +.orbit-logo-card { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); display: flex; - flex-direction: column; align-items: center; - text-align: center; - padding: 2.5rem; - background: linear-gradient(145deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.01) 100%); - border: 1px solid rgba(255, 255, 255, 0.06); - border-radius: 16px; - position: relative; - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 10px 30px -10px rgba(0,0,0,0.5); - transition: transform 0.3s, border-color 0.3s, box-shadow 0.3s; + gap: 8px; + padding: 0.45rem 0.95rem; + background: rgba(10, 10, 12, 0.65); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 100px; + font-weight: 600; + color: #a1a1aa; + white-space: nowrap; + user-select: none; + cursor: pointer; + pointer-events: auto; + transition: border-color 0.3s, color 0.3s, background 0.3s, box-shadow 0.3s, scale 0.3s; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.05); } -.step-card:hover { - transform: translateY(-4px); - border-color: rgba(0, 245, 212, 0.3); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 20px 40px -15px rgba(0, 245, 212, 0.1); +.orbit-logo-card svg { + display: block; + width: 16px; + height: 16px; + transition: transform 0.3s; } -.step-number { - width: 48px; - height: 48px; - background: linear-gradient(145deg, rgba(0, 245, 212, 0.2) 0%, rgba(0, 245, 212, 0.05) 100%); - border: 1px solid rgba(0, 245, 212, 0.3); - box-shadow: 0 0 15px rgba(0, 245, 212, 0.2); - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-weight: 700; - font-size: 1.2rem; - margin-bottom: 1.5rem; +.orbit-logo-text { + font-size: 0.78rem; + letter-spacing: -0.01em; +} + +/* Hover highlights */ +.orbit-logo-card:hover { color: #fff; + scale: 1.05; + background: rgba(15, 15, 20, 0.85); + border-color: var(--hover-color, #00f5d4); + box-shadow: 0 0 20px rgba(0, 0, 0, 0.6), 0 0 15px var(--hover-color, rgba(0, 245, 212, 0.2)); } +.orbit-logo-card:hover svg { + transform: scale(1.1); +} +.orbit-inner-wrapper { + --orbit-radius: var(--radius-inner); + animation: orbitMove 20s linear infinite; +} +.orbit-inner-wrapper .orbit-logo-card { + animation: billboardCancel 20s linear infinite; + animation-delay: inherit; +} -.bento-icon { - width: 48px; - height: 48px; - background: rgba(255, 255, 255, 0.03); - border-radius: 10px; - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 1.25rem; - color: #fff; - border: 1px solid rgba(255, 255, 255, 0.08); +.orbit-mid-wrapper { + --orbit-radius: var(--radius-mid); + animation: orbitMove 32s linear infinite; +} +.orbit-mid-wrapper .orbit-logo-card { + animation: billboardCancel 32s linear infinite; + animation-delay: inherit; } -.bento-title { - font-size: 1.375rem; - font-weight: 600; - margin-bottom: 0.75rem; - letter-spacing: -0.015em; - color: #fff; +.orbit-outer-wrapper { + --orbit-radius: var(--radius-outer); + animation: orbitMove 48s linear infinite; +} +.orbit-outer-wrapper .orbit-logo-card { + animation: billboardCancel 48s linear infinite; + animation-delay: inherit; } -.bento-desc { - color: #a1a1aa; - font-size: 1.05rem; - line-height: 1.6; +@keyframes orbitMove { + 0% { + transform: translate(-50%, -50%) rotateZ(0deg) translateX(var(--orbit-radius)); + } + 100% { + transform: translate(-50%, -50%) rotateZ(-360deg) translateX(var(--orbit-radius)); + } } -/* USE CASES & EXTRA CARDS */ -.use-case-card, .glass-card { - background: linear-gradient(145deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.01) 100%); - border: 1px solid rgba(255, 255, 255, 0.06); - padding: 2.5rem; - border-radius: 16px; - text-align: left; - transition: all 0.3s; - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03), 0 10px 30px -10px rgba(0,0,0,0.5); - position: relative; - overflow: hidden; +@keyframes billboardCancel { + 0% { + transform: translate(-50%, -50%) rotateZ(0deg) rotateY(10deg) rotateX(-65deg); + } + 100% { + transform: translate(-50%, -50%) rotateZ(360deg) rotateY(10deg) rotateX(-65deg); + } } -.use-case-card:hover, .glass-card:hover, .pricing-card:hover { - border-color: rgba(255, 255, 255, 0.15); - transform: translateY(-4px); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 20px 40px -15px rgba(0,0,0,0.6); +/* Laser connect beam */ +.orbit-connect-line { + position: absolute; + right: 0; + top: 50%; + height: 2px; + width: var(--orbit-radius); + transform: translateY(-50%); + background: linear-gradient(90deg, + rgba(0, 0, 0, 0) 0%, + rgba(255, 255, 255, 0.1) 20%, + var(--hover-color, #00f5d4) 80%, + var(--hover-color, #00f5d4) 100% + ); + box-shadow: 0 0 8px var(--hover-color, #00f5d4), 0 0 16px rgba(0, 245, 212, 0.2); + transform-origin: right center; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease; + z-index: 1; } -.pricing-card { - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +/* Active connection line when hovered */ +.orbit-item-wrapper.active .orbit-connect-line { + opacity: 1; } -.integration-section div { - transition: all 0.3s ease; +/* Data pulse animation on the connection line */ +.orbit-connect-line::after { + content: ''; + position: absolute; + top: 0; + right: 0; + height: 100%; + width: 40px; + background: linear-gradient(90deg, transparent, var(--hover-color, #00f5d4), #fff, var(--hover-color, #00f5d4), transparent); + filter: drop-shadow(0 0 4px var(--hover-color, #00f5d4)); + animation: beam-pulse 1.2s infinite linear; } -.integration-section div:hover { - opacity: 1 !important; - transform: translateY(-2px); - color: #00f5d4 !important; +@keyframes beam-pulse { + 0% { transform: translateX(0); } + 100% { transform: translateX(calc(-1 * var(--orbit-radius))); } } + + + .demo-main pre { scrollbar-width: thin; scrollbar-color: #333 transparent; @@ -1193,7 +1696,7 @@ /* MOBILE ADJUSTMENTS */ @media(max-width: 768px) { .hero-heading { - font-size: 2.8rem; + font-size: 2rem; } .nav-links { @@ -1208,24 +1711,33 @@ padding-top: 1rem; } - .lh-content { - grid-template-columns: 1fr; + .hero-flow { + flex-direction: column; + gap: 0; + max-width: 280px; } - .lh-middle { - border-top: 1px solid rgba(255, 255, 255, 0.06); - border-bottom: 1px solid rgba(255, 255, 255, 0.06); - border-right: none; - min-height: 92px; - flex-direction: row; + .hf-connector { + width: 2px; + height: 30px; } - .lh-line { - width: 140px; + .hf-dot { + top: 0; + left: 50%; + transform: translateX(-50%); + animation: hfDotMoveVertical 2s ease-in-out infinite; + } + + @keyframes hfDotMoveVertical { + 0% { top: 0; opacity: 0; } + 10% { opacity: 1; } + 90% { opacity: 1; } + 100% { top: calc(100% - 8px); opacity: 0; } } - .lh-pane { - border-right: none; + .hf-step { + width: 100%; } .cta-stats { @@ -1436,137 +1948,382 @@ /* BYOM Callout Section */ .byom-callout-section { - padding: 8rem 0; + padding: 7rem 0; background: #000; border-top: 1px solid rgba(255, 255, 255, 0.08); position: relative; overflow: hidden; } -.byom-connection-anim { +.byom-grid-bg { + position: absolute; + inset: 0; + background-image: + linear-gradient(rgba(255,255,255,0.015) 1px, transparent 1px), + linear-gradient(90deg, rgba(255,255,255,0.015) 1px, transparent 1px); + background-size: 60px 60px; + -webkit-mask-image: radial-gradient(ellipse 80% 60% at 50% 30%, black 30%, transparent 70%); + mask-image: radial-gradient(ellipse 80% 60% at 50% 30%, black 30%, transparent 70%); + pointer-events: none; +} + +/* Pipeline */ +.byom-pipeline { display: flex; align-items: center; justify-content: center; - gap: 1rem; - margin-bottom: 5rem; + gap: 0; + margin-bottom: 4.5rem; + padding: 2rem 0; } @media(max-width: 768px) { - .byom-connection-anim { + .byom-pipeline { flex-direction: column; - } - .byom-line { - width: 2px; - min-height: 40px; - } - .byom-dot { - top: 0; - left: 50%; - transform: translateX(-50%); - animation: byomFlowVertical 2s infinite linear; + gap: 0.5rem; + padding: 1rem 0; } } -.byom-node { +.byom-pipe-node { + position: relative; display: flex; flex-direction: column; align-items: center; - gap: 0.5rem; - padding: 1rem 1.5rem; - background: rgba(255,255,255,0.03); - border: 1px solid rgba(255,255,255,0.1); - border-radius: 12px; - color: #fff; - font-size: 0.85rem; - font-weight: 600; - box-shadow: 0 4px 20px rgba(0,0,0,0.5); + gap: 0.45rem; + padding: 1.6rem 2rem; + background: rgba(255,255,255,0.02); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 16px; + min-width: 170px; z-index: 2; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); } -.byom-node.byom-center { - border-color: rgba(0, 245, 212, 0.3); - background: rgba(0, 245, 212, 0.05); - box-shadow: 0 0 30px rgba(0, 245, 212, 0.15); +.byom-pipe-node:hover { + border-color: rgba(255,255,255,0.15); + background: rgba(255,255,255,0.04); + transform: translateY(-3px); } -.byom-line { - flex: 1; - min-width: 60px; - height: 2px; - background: rgba(255,255,255,0.1); - position: relative; - z-index: 1; +.byom-pipe-icon { + width: 52px; + height: 52px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 14px; + background: rgba(255,255,255,0.04); + border: 1px solid rgba(255,255,255,0.08); + color: #00f5d4; + transition: all 0.3s ease; } -.byom-dot { - position: absolute; - top: 50%; - left: 0; - width: 6px; - height: 6px; - background: #00f5d4; - border-radius: 50%; - transform: translateY(-50%); - box-shadow: 0 0 10px #00f5d4; - animation: byomFlow 2s infinite linear; +.byom-pipe-node:hover .byom-pipe-icon { + background: rgba(0,245,212,0.08); + border-color: rgba(0,245,212,0.2); + box-shadow: 0 0 20px rgba(0,245,212,0.1); } -@keyframes byomFlow { - 0% { left: 0%; opacity: 0; } - 10% { opacity: 1; } - 90% { opacity: 1; } - 100% { left: 100%; opacity: 0; } +.byom-pipe-label { + font-size: 0.9rem; + font-weight: 700; + color: #fff; + letter-spacing: -0.01em; } -@keyframes byomFlowVertical { - 0% { top: 0%; opacity: 0; } - 10% { opacity: 1; } - 90% { opacity: 1; } - 100% { top: 100%; opacity: 0; } +.byom-pipe-sub { + font-size: 0.7rem; + color: #666; + font-weight: 500; + text-align: center; + line-height: 1.3; } -/* OSS Strip */ -.oss-badge { - transition: all 0.3s ease; +.byom-pipe-glow { + position: absolute; + inset: -1px; + border-radius: 16px; + background: linear-gradient(135deg, rgba(0,245,212,0.06), transparent 50%, rgba(0,245,212,0.02)); + pointer-events: none; + opacity: 0; + transition: opacity 0.4s ease; } -.oss-badge:hover { - background: rgba(255,255,255,0.1) !important; - border-color: rgba(255,255,255,0.2) !important; - transform: translateY(-1px); +.byom-pipe-node:hover .byom-pipe-glow { + opacity: 1; } -/* TIMELINE SECTION */ -.timeline-container { - position: relative; - max-width: 1200px; - margin: 4rem auto 0 auto; - padding: 0 1.5rem; - min-height: 400px; +.byom-pipe-center { + border-color: rgba(0,245,212,0.15); + background: rgba(0,245,212,0.03); + box-shadow: 0 0 40px rgba(0,245,212,0.05), inset 0 1px 0 rgba(0,245,212,0.08); + animation: byomCenterPulse 3s ease-in-out infinite; } -/* Central vertical track */ -.timeline-track { +@keyframes byomCenterPulse { + 0%, 100% { box-shadow: 0 0 40px rgba(0,245,212,0.05), inset 0 1px 0 rgba(0,245,212,0.08); } + 50% { box-shadow: 0 0 60px rgba(0,245,212,0.1), inset 0 1px 0 rgba(0,245,212,0.12); } +} + +.byom-center-icon { + background: rgba(0,245,212,0.1); + border-color: rgba(0,245,212,0.25); + color: #00f5d4; + box-shadow: 0 0 20px rgba(0,245,212,0.15); +} + +.byom-pipe-ring { position: absolute; - top: 0; - bottom: 0; - left: 50%; - transform: translateX(-50%); - width: 2px; - background: rgba(255, 255, 255, 0.06); - z-index: 1; + inset: -3px; + border-radius: 18px; + border: 1px solid transparent; + background: linear-gradient(135deg, rgba(0,245,212,0.3), transparent 40%, transparent 60%, rgba(0,245,212,0.15)) border-box; + -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; pointer-events: none; + animation: byomRingRotate 4s linear infinite; } -.timeline-track-fill { - width: 100%; - height: 100%; - /* Driven dynamically in index.jsx inline styles to avoid rainbow override */ +@keyframes byomRingRotate { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } } -.timeline-items { - display: flex; - flex-direction: column; +.byom-pipe-connector { + flex: 1; + min-width: 80px; + max-width: 140px; + height: 2px; + background: linear-gradient(90deg, rgba(255,255,255,0.05), rgba(255,255,255,0.12), rgba(255,255,255,0.05)); + position: relative; + z-index: 1; + overflow: hidden; +} + +@media(max-width: 768px) { + .byom-pipe-connector { + min-width: 2px; + min-height: 40px; + max-width: 2px; + max-height: 60px; + background: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.12), rgba(255,255,255,0.05)); + } +} + +.byom-pipe-track { + position: absolute; + inset: 0; +} + +.byom-pipe-particle { + position: absolute; + top: 50%; + left: -4px; + width: 4px; + height: 4px; + background: #00f5d4; + border-radius: 50%; + box-shadow: 0 0 6px #00f5d4, 0 0 12px rgba(0,245,212,0.3); + transform: translateY(-50%); + animation: byomParticle 2.4s ease-in-out infinite; +} + +@media(max-width: 768px) { + .byom-pipe-particle { + top: -4px; + left: 50%; + transform: translateX(-50%); + animation: byomParticleVertical 2.4s ease-in-out infinite; + } +} + +@keyframes byomParticle { + 0% { left: -4px; opacity: 0; } + 15% { opacity: 1; } + 85% { opacity: 1; } + 100% { left: calc(100% + 4px); opacity: 0; } +} + +@keyframes byomParticleVertical { + 0% { top: -4px; opacity: 0; } + 15% { opacity: 1; } + 85% { opacity: 1; } + 100% { top: calc(100% + 4px); opacity: 0; } +} + +/* BYOM Cards */ +.byom-cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); + gap: 1.5rem; +} + +.byom-card { + position: relative; + padding: 2.5rem; + background: rgba(255,255,255,0.02); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 16px; + overflow: hidden; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +.byom-card:hover { + background: rgba(255,255,255,0.035); + border-color: rgba(255,255,255,0.12); + transform: translateY(-4px); + box-shadow: 0 24px 48px -12px rgba(0,0,0,0.6); +} + +.byom-card-border { + position: absolute; + inset: -1px; + border-radius: 16px; + background: linear-gradient(135deg, rgba(255,255,255,0.08), transparent 30%, transparent 70%, rgba(255,255,255,0.04)); + opacity: 0; + transition: opacity 0.5s ease; + pointer-events: none; +} + +.byom-card:hover .byom-card-border { + opacity: 1; +} + +.byom-card-icon { + width: 52px; + height: 52px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 14px; + margin-bottom: 1.25rem; + position: relative; + transition: all 0.3s ease; +} + +.byom-card-icon-db { + background: rgba(0,245,212,0.08); + color: #00f5d4; + border: 1px solid rgba(0,245,212,0.15); + box-shadow: 0 0 15px rgba(0,245,212,0.08); +} + +.byom-card:hover .byom-card-icon-db { + background: rgba(0,245,212,0.12); + box-shadow: 0 0 25px rgba(0,245,212,0.2); + transform: scale(1.05); +} + +.byom-card-icon-storage { + background: rgba(64,158,255,0.08); + color: #409EFF; + border: 1px solid rgba(64,158,255,0.15); + box-shadow: 0 0 15px rgba(64,158,255,0.08); +} + +.byom-card:hover .byom-card-icon-storage { + background: rgba(64,158,255,0.12); + box-shadow: 0 0 25px rgba(64,158,255,0.2); + transform: scale(1.05); +} + +.byom-card-title { + font-size: 1.35rem; + font-weight: 700; + color: #fff; + margin-bottom: 0.75rem; + letter-spacing: -0.01em; +} + +.byom-card-desc { + color: #a1a1aa; + line-height: 1.7; + font-size: 0.92rem; + margin-bottom: 1.5rem; +} + +.byom-card-tags { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.byom-card-tags span { + padding: 0.35rem 0.75rem; + background: rgba(255,255,255,0.04); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 20px; + font-size: 0.78rem; + font-weight: 500; + color: #999; + transition: all 0.3s ease; +} + +.byom-card:hover .byom-card-tags span { + background: rgba(255,255,255,0.06); + border-color: rgba(255,255,255,0.1); + color: #ccc; +} + +@media(max-width: 768px) { + .byom-cards { + grid-template-columns: 1fr; + } + + .byom-card { + padding: 2rem; + } + + .byom-pipe-node { + padding: 1.2rem 1.5rem; + min-width: 140px; + } +} + +/* OSS Strip */ +.oss-badge { + transition: all 0.3s ease; +} + +.oss-badge:hover { + background: rgba(255,255,255,0.1) !important; + border-color: rgba(255,255,255,0.2) !important; + transform: translateY(-1px); +} + +/* TIMELINE SECTION */ +.timeline-container { + position: relative; + max-width: 1200px; + margin: 4rem auto 0 auto; + padding: 0 1.5rem; + min-height: 400px; +} + +/* Central vertical track */ +.timeline-track { + position: absolute; + top: 0; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 2px; + background: rgba(255, 255, 255, 0.06); + z-index: 1; + pointer-events: none; +} + +.timeline-track-fill { + width: 100%; + height: 100%; + /* Driven dynamically in index.jsx inline styles to avoid rainbow override */ +} + +.timeline-items { + display: flex; + flex-direction: column; position: relative; z-index: 2; } @@ -1909,3 +2666,1071 @@ .card-accent-green .timeline-mobile-tag { color: #22c55e; } .card-accent-orange .timeline-mobile-tag { color: #f97316; } } + +/* Client App Services Showcase Section */ +.services-showcase-section { + padding: 7rem 0; + background: #000; + border-top: 1px solid rgba(255, 255, 255, 0.08); + position: relative; + overflow: hidden; +} + +.services-grid-bg { + position: absolute; + inset: 0; + background-image: + linear-gradient(rgba(255,255,255,0.01) 1px, transparent 1px), + linear-gradient(90deg, rgba(255,255,255,0.01) 1px, transparent 1px); + background-size: 80px 80px; + -webkit-mask-image: radial-gradient(ellipse 70% 60% at 50% 50%, black 40%, transparent 80%); + mask-image: radial-gradient(ellipse 70% 60% at 50% 50%, black 40%, transparent 80%); + pointer-events: none; +} + +.services-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 1.5rem; + position: relative; + z-index: 2; +} + +.services-header { + text-align: center; + margin-bottom: 4.5rem; +} + +.services-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); + gap: 2.5rem; + justify-content: center; +} + +.service-card { + position: relative; + display: flex; + flex-direction: column; + background: rgba(255, 255, 255, 0.015); + border: 1px solid rgba(255, 255, 255, 0.05); + border-radius: 20px; + padding: 2.25rem; + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); + overflow: hidden; +} + +.service-card-glow { + position: absolute; + inset: 0; + background: radial-gradient(circle at 50% 100%, var(--accent-glow) 0%, transparent 60%); + opacity: 0; + transition: opacity 0.4s ease; + pointer-events: none; +} + +.service-card:hover .service-card-glow { + opacity: 1; +} + +.service-card:hover { + transform: translateY(-6px); + border-color: var(--accent-color); + background: rgba(255, 255, 255, 0.035); + box-shadow: 0 20px 40px -15px rgba(0, 0, 0, 0.8), 0 0 25px -5px var(--accent-glow); +} + +.service-card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; +} + +.service-icon-wrap { + width: 44px; + height: 44px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 12px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.08); + color: var(--accent-color); + transition: all 0.3s ease; +} + +.service-card:hover .service-icon-wrap { + background: var(--accent-glow); + border-color: var(--accent-color); + transform: scale(1.08); +} + +.service-card-body { + flex-grow: 1; + margin-bottom: 1.5rem; +} + +.service-card-title { + font-size: 1.3rem; + font-weight: 600; + color: #fff; + margin-bottom: 0.75rem; + letter-spacing: -0.02em; +} + +.service-card-desc { + font-size: 0.925rem; + color: #a1a1aa; + line-height: 1.6; +} + +.service-card-visual-container { + height: 150px; + background: rgba(0, 0, 0, 0.45); + border-radius: 14px; + border: 1px solid rgba(255, 255, 255, 0.03); + overflow: hidden; + position: relative; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.4s ease; +} + +.service-card:hover .service-card-visual-container { + border-color: rgba(255, 255, 255, 0.08); + background: rgba(0, 0, 0, 0.6); +} + +/* Mini Visuals Inside Cards */ +.mini-visual { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; +} + +/* Authentication Visual */ +.auth-visual { + position: relative; +} + +.auth-visual .mini-auth-box { + width: 170px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 10px; + padding: 0.6rem 0.8rem; + box-shadow: 0 10px 25px -5px rgba(0,0,0,0.5); + transition: all 0.3s ease; + position: relative; +} + +.service-card:hover .auth-visual .mini-auth-box { + transform: translateY(-3px) scale(1.02); + border-color: rgba(0, 245, 212, 0.3); +} + +.mini-auth-header { + display: flex; + gap: 4px; + margin-bottom: 0.5rem; +} + +.mini-circle { + width: 6px; + height: 6px; + border-radius: 50%; +} + +.mini-circle.red { background: #ff5f56; } +.mini-circle.yellow { background: #ffbd2e; } +.mini-circle.green { background: #27c93f; } + +.mini-auth-body { + display: flex; + flex-direction: column; + gap: 0.4rem; +} + +.mini-auth-label { + font-size: 10px; + color: #fff; + font-weight: 500; +} + +.mini-auth-input { + height: 20px; + background: rgba(255,255,255,0.03); + border: 1px solid rgba(255,255,255,0.08); + border-radius: 4px; + display: flex; + align-items: center; + padding: 0 0.4rem; + gap: 0.3rem; +} + +.mini-dot { + width: 4px; + height: 4px; + border-radius: 50%; + background: #00f5d4; +} + +.placeholder { + font-size: 8px; + color: #666; +} + +.mini-auth-btn-row { + margin-top: 0.1rem; +} + +.mini-auth-btn-primary { + width: 100%; + height: 20px; + border-radius: 4px; + background: #00f5d4; + border: none; + font-size: 9px; + color: #000; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} + +.service-card:hover .mini-auth-btn-primary { + animation: auth-btn-click 1.6s ease forwards; +} + +@keyframes auth-btn-click { + 0% { + background: #00f5d4; + transform: scale(1); + } + 45% { + background: #00f5d4; + transform: scale(1); + } + 50% { + background: #00f5d4; + transform: scale(0.92); + box-shadow: 0 0 5px rgba(0, 245, 212, 0.4); + } + 58% { + background: #00e676; + transform: scale(1.02); + box-shadow: 0 0 15px rgba(0, 230, 118, 0.6); + } + 100% { + background: #00e676; + transform: scale(1); + box-shadow: 0 0 15px rgba(0, 230, 118, 0.6); + } +} + +.mini-auth-providers { + display: flex; + justify-content: center; + gap: 0.5rem; + margin-top: 0.2rem; +} + +.mini-provider-btn { + width: 20px; + height: 20px; + border-radius: 4px; + background: rgba(255,255,255,0.05); + border: 1px solid rgba(255,255,255,0.08); + display: flex; + align-items: center; + justify-content: center; + color: #aaa; +} + +.service-card:hover .mini-provider-btn { + border-color: rgba(255, 255, 255, 0.15); + color: #fff; +} + +.mini-cursor { + position: absolute; + bottom: 2px; + right: 15px; + opacity: 0; + transform: translate(0, 0); + transition: all 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94); + pointer-events: none; + z-index: 10; +} + +.service-card:hover .mini-cursor { + opacity: 1; + transform: translate(-30px, -28px); +} + +/* Database Visual */ +.db-visual .mini-db-code { + font-family: 'Courier New', Courier, monospace; + font-size: 11px; + color: #a1a1aa; + line-height: 1.45; + text-align: left; + width: 90%; + background: rgba(0,0,0,0.3); + border: 1px solid rgba(255,255,255,0.04); + border-radius: 8px; + padding: 0.8rem; + transition: all 0.3s ease; +} + +.service-card:hover .db-visual .mini-db-code { + border-color: rgba(255, 189, 46, 0.4); + box-shadow: 0 0 15px rgba(255, 189, 46, 0.15); +} + +.code-key { color: #c792ea; } +.code-var { color: #82b1ff; } +.code-func { color: #82b1ff; } +.code-str { color: #ecc48d; } +.code-json { + margin-top: 0.2rem; + opacity: 0.15; + transition: opacity 0.4s ease; +} + +.service-card:hover .code-json { + opacity: 1; + transition-delay: 0.3s; +} + +.json-bracket { color: #ffd700; } +.json-key { color: #80cbc4; } +.json-val { color: #f78c6c; } +.json-bool { color: #ff5370; } +.json-str { color: #c3e88d; } + +/* Storage Visual */ +.storage-visual .mini-storage-card { + width: 190px; + background: rgba(255, 255, 255, 0.02); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 10px; + padding: 0.75rem; + display: flex; + flex-direction: column; + gap: 0.6rem; + transition: all 0.3s ease; +} + +.service-card:hover .mini-storage-card { + border-color: rgba(64, 158, 255, 0.3); + transform: scale(1.02); +} + +.mini-storage-file { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.mini-storage-file .file-icon { + color: #409EFF; +} + +.mini-storage-file .file-details { + display: flex; + flex-direction: column; +} + +.mini-storage-file .file-name { + font-size: 10px; + font-weight: 500; + color: #fff; +} + +.file-status-container { + position: relative; + height: 12px; + overflow: hidden; + display: flex; + align-items: center; +} + +.file-size-uploading { + font-size: 8px; + color: #888; + position: absolute; + top: 0; + left: 0; + opacity: 1; + transition: opacity 0.3s ease, transform 0.3s ease; +} + +.file-size-complete { + font-size: 8px; + color: #00e676; + position: absolute; + top: 0; + left: 0; + opacity: 0; + transform: translateY(10px); + transition: opacity 0.3s ease, transform 0.3s ease; +} + +.service-card:hover .file-size-uploading { + opacity: 0; + transform: translateY(-10px); + transition-delay: 1.2s; +} + +.service-card:hover .file-size-complete { + opacity: 1; + transform: translateY(0); + transition-delay: 1.2s; +} + +.mini-progress-bar { + height: 4px; + background: rgba(255, 255, 255, 0.05); + border-radius: 2px; + overflow: hidden; +} + +.mini-progress-fill { + height: 100%; + width: 0%; + background: linear-gradient(90deg, #409EFF, #00f5d4); + border-radius: 2px; + transition: width 1.5s cubic-bezier(0.4, 0, 0.2, 1); +} + +.service-card:hover .mini-progress-fill { + width: 100%; +} + +/* Mail Visual */ +.mail-visual .mini-mail-envelope { + width: 180px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 10px; + overflow: hidden; + box-shadow: 0 10px 25px -5px rgba(0,0,0,0.5); + transition: all 0.3s ease; +} + +.service-card:hover .mini-mail-envelope { + border-color: rgba(0, 230, 118, 0.3); + transform: translateY(-3px); +} + +.mail-header { + background: rgba(255, 255, 255, 0.03); + border-bottom: 1px solid rgba(255, 255, 255, 0.06); + padding: 0.4rem 0.6rem; + display: flex; + align-items: center; + gap: 0.4rem; + font-size: 8px; + color: #888; +} + +.mail-badge-icon { + color: #00e676; +} + +.mail-content { + padding: 0.6rem 0.8rem; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.mail-title { + font-size: 10px; + font-weight: 500; + color: #fff; +} + +.mail-bar-btn { + height: 18px; + border-radius: 4px; + background: rgba(0, 230, 118, 0.15); + border: 1px solid rgba(0, 230, 118, 0.3); + font-size: 8px; + color: #00e676; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.3s ease; +} + +.service-card:hover .mail-bar-btn { + background: #00e676; + color: #000; + border-color: #00e676; + transition-delay: 0.8s; +} + +.mail-status-bar { + display: flex; + align-items: center; + padding: 0.3rem 0.6rem; + background: rgba(0, 0, 0, 0.25); + border-top: 1px solid rgba(255, 255, 255, 0.04); + height: 18px; + position: relative; +} + +.mail-status-sending { + font-size: 8px; + color: #ffbd2e; + position: absolute; + left: 8px; + opacity: 1; + transition: opacity 0.3s ease, transform 0.3s ease; +} + +.mail-status-sent { + font-size: 8px; + color: #00e676; + position: absolute; + left: 8px; + opacity: 0; + transform: translateY(10px); + transition: opacity 0.3s ease, transform 0.3s ease; +} + +.service-card:hover .mail-status-sending { + opacity: 0; + transform: translateY(-10px); + transition-delay: 0.8s; +} + +.service-card:hover .mail-status-sent { + opacity: 1; + transform: translateY(0); + transition-delay: 0.8s; +} + +/* Security Visual */ +.security-visual .policy-badge { + position: absolute; + top: 8px; + right: 8px; + padding: 0.2rem 0.4rem; + border-radius: 4px; + font-size: 7px; + font-weight: 700; + color: #a855f7; + background: rgba(168, 85, 247, 0.15); + border: 1px solid rgba(168, 85, 247, 0.3); + letter-spacing: 0.05em; +} + +.security-visual .policy-editor { + width: 90%; + font-family: 'Courier New', Courier, monospace; + font-size: 9.5px; + color: #888; + text-align: left; + line-height: 1.5; + transition: all 0.3s ease; +} + +.service-card:hover .policy-editor { + color: #b4b4b4; +} + +.policy-line { + white-space: nowrap; + transition: background-color 0.3s ease; +} + +.policy-line.indent { + padding-left: 10px; +} + +.service-card:hover .policy-line.indent { + background: rgba(168, 85, 247, 0.06); + border-radius: 2px; +} + +.policy-keyword { color: #e57373; } +.policy-action { color: #81c784; } + +/* Realtime Visual */ +.realtime-visual .mini-realtime-stage { + width: 190px; + display: flex; + align-items: center; + justify-content: space-between; + background: rgba(255, 255, 255, 0.02); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 10px; + padding: 0.6rem 0.75rem; + position: relative; + overflow: hidden; + height: 55px; + box-shadow: 0 10px 25px -5px rgba(0,0,0,0.5); + transition: all 0.3s ease; +} + +.service-card:hover .mini-realtime-stage { + border-color: rgba(255, 95, 86, 0.3); + box-shadow: 0 0 15px rgba(255, 95, 86, 0.15); +} + +.realtime-node { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.2rem; + font-size: 8px; + color: #666; + z-index: 2; + transition: color 0.3s ease; +} + +.realtime-node span { + font-size: 7px; +} + +.service-card:hover .realtime-node { + color: #fff; +} + +.realtime-pipe { + flex-grow: 1; + height: 2px; + background: rgba(255, 255, 255, 0.08); + margin: 0 0.5rem; + position: relative; + border-radius: 1px; +} + +.realtime-pulse { + position: absolute; + top: 0; + left: 0; + width: 25px; + height: 2px; + background: linear-gradient(90deg, transparent, #ff5f56, transparent); + opacity: 0; +} + +.service-card:hover .realtime-pulse { + opacity: 1; + animation: realtime-packet-pulse 1.2s infinite linear; +} + +@keyframes realtime-packet-pulse { + 0% { left: -30%; } + 100% { left: 110%; } +} + +.realtime-status-badge { + position: absolute; + bottom: 2px; + left: 50%; + transform: translateX(-50%); + font-size: 6px; + font-weight: 700; + color: #444; + letter-spacing: 0.05em; + transition: all 0.3s ease; +} + +.service-card:hover .realtime-status-badge { + color: #ff5f56; + text-shadow: 0 0 4px rgba(255, 95, 86, 0.6); +} + +/* urBackend Studio Interactive Window in Infrastructure */ +.hero-interactive-window { + max-width: 860px; + margin: 3.5rem auto 4.5rem auto; + background: rgba(10, 10, 10, 0.4); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 14px; + overflow: hidden; + box-shadow: 0 30px 60px -15px rgba(0, 0, 0, 0.9), inset 0 1px 0 rgba(255, 255, 255, 0.05); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); +} + +.lh-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.65rem 1.25rem; + background: rgba(255, 255, 255, 0.02); + border-bottom: 1px solid rgba(255, 255, 255, 0.06); +} + +.lh-dots { + display: flex; + gap: 6px; +} + +.lh-dot { + width: 9px; + height: 9px; + border-radius: 50%; +} + +.lh-dot-red { background: #ff5f56; } +.lh-dot-yellow { background: #ffbd2e; } +.lh-dot-green { background: #27c93f; } + +.lh-title { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.75rem; + color: #888; + font-weight: 500; +} + +.lh-title svg { + color: #00f5d4; +} + +.lh-replay { + background: transparent; + border: none; + color: #00f5d4; + cursor: pointer; + font-size: 0.72rem; + font-weight: 600; + display: flex; + align-items: center; + gap: 4px; + padding: 0.2rem 0.5rem; + border-radius: 4px; + transition: all 0.2s ease; +} + +.lh-replay:hover { + background: rgba(0, 245, 212, 0.08); + color: #fff; +} + +.lh-content { + display: grid; + grid-template-columns: 1.2fr 0.3fr 1.2fr; + background: rgba(0, 0, 0, 0.2); +} + +@media (max-width: 768px) { + .lh-content { + grid-template-columns: 1fr; + } + .lh-middle { + height: 50px; + padding: 0.5rem 0; + } + .lh-line { + width: 2px !important; + height: 100% !important; + } + .lh-pulse { + width: 2px !important; + height: 20px !important; + top: 0 !important; + left: 0 !important; + background: linear-gradient(180deg, transparent, #00f5d4, transparent) !important; + } + .lh-pulse.active { + animation: pulse-travel-vert 1.2s infinite linear !important; + } +} + +@keyframes pulse-travel-vert { + 0% { top: -20%; } + 100% { top: 110%; } +} + +.lh-pane { + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.lh-pane-header { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.8rem; + color: #fff; + font-weight: 600; + border-bottom: 1px solid rgba(255,255,255,0.04); + padding-bottom: 0.45rem; +} + +.lh-pane-header svg { + color: #00f5d4; +} + +.lh-pane-label { + margin-left: auto; + font-size: 0.6rem; + color: #666; + text-transform: uppercase; + letter-spacing: 0.05em; + background: rgba(255,255,255,0.03); + padding: 0.1rem 0.35rem; + border-radius: 4px; +} + +.lh-builder { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.lh-group { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.lh-group label { + font-size: 0.65rem; + color: #666; + font-weight: 600; + text-transform: uppercase; +} + +.lh-input { + background: rgba(255,255,255,0.02); + border: 1px solid rgba(255,255,255,0.08); + border-radius: 6px; + padding: 0.35rem 0.6rem; + font-size: 0.8rem; + color: #fff; + font-family: monospace; +} + +.lh-head-row { + display: grid; + grid-template-columns: 1fr 1fr 60px; + font-size: 0.6rem; + color: #444; + font-weight: 700; + letter-spacing: 0.05em; + padding: 0 0.4rem; +} + +.lh-table { + display: flex; + flex-direction: column; + gap: 0.4rem; +} + +.lh-row { + display: grid; + grid-template-columns: 1fr 1fr 60px; + background: rgba(255, 255, 255, 0.01); + border: 1px solid rgba(255, 255, 255, 0.04); + border-radius: 6px; + padding: 0.35rem 0.4rem; + font-size: 0.75rem; + align-items: center; + opacity: 0; + transform: translateY(5px); + transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1); +} + +.lh-row.visible { + opacity: 1; + transform: translateY(0); +} + +.lh-name { + color: #00f5d4; + font-family: monospace; +} + +.lh-type { + color: #888; +} + +.lh-req { + color: #444; +} + +.lh-req.on { + color: #27c93f; + display: flex; + align-items: center; +} + +.lh-actions { + margin-top: 0.2rem; +} + +.lh-add-btn { + background: rgba(255,255,255,0.02); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 6px; + color: #666; + font-size: 0.7rem; + padding: 0.35rem 0.6rem; + display: flex; + align-items: center; + gap: 5px; + cursor: pointer; + transition: all 0.2s ease; +} + +.lh-add-btn:hover { + border-color: rgba(255,255,255,0.12); + color: #ccc; + background: rgba(255,255,255,0.04); +} + +.lh-middle { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + position: relative; + padding: 0 0.5rem; +} + +.lh-line { + width: 100%; + height: 2px; + background: rgba(255, 255, 255, 0.04); + position: relative; + border-radius: 1px; +} + +.lh-pulse { + position: absolute; + top: 0; + left: 0; + width: 25px; + height: 2px; + background: linear-gradient(90deg, transparent, #00f5d4, transparent); + opacity: 0; +} + +.lh-pulse.active { + opacity: 1; + animation: lh-pulse-travel 1.2s infinite linear; +} + +@keyframes lh-pulse-travel { + 0% { left: -30%; } + 100% { left: 110%; } +} + +.lh-engine { + width: 34px; + height: 34px; + border-radius: 50%; + background: rgba(0, 0, 0, 0.5); + border: 1.5px solid rgba(255,255,255,0.06); + display: flex; + align-items: center; + justify-content: center; + color: #555; + margin-top: 1rem; + transition: all 0.3s ease; +} + +.lh-engine.pulsing { + color: #00f5d4; + border-color: #00f5d4; + box-shadow: 0 0 12px rgba(0, 245, 212, 0.3); + animation: spin-slow 3s infinite linear; +} + +@keyframes spin-slow { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.lh-pane-right { + border-left: 1px solid rgba(255, 255, 255, 0.04); +} + +@media (max-width: 768px) { + .lh-pane-right { + border-left: none; + border-top: 1px solid rgba(255, 255, 255, 0.04); + } +} + +.lh-endpoints { + display: flex; + flex-direction: column; + gap: 0.45rem; + opacity: 0.2; + transition: opacity 0.3s ease; +} + +.lh-endpoints.visible { + opacity: 1; +} + +.lh-endpoint { + display: flex; + align-items: center; + gap: 6px; + background: rgba(255,255,255,0.01); + border: 1px solid rgba(255,255,255,0.03); + border-radius: 6px; + padding: 0.35rem 0.5rem; + opacity: 0; + transform: translateX(10px); + transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); +} + +.lh-endpoints.visible .lh-endpoint { + opacity: 1; + transform: translateX(0); +} + +.lh-endpoint code { + font-size: 0.7rem; + color: #bbb; + font-family: monospace; + flex-grow: 1; +} + +.lh-method { + font-size: 0.6rem; + font-weight: 700; + padding: 0.1rem 0.3rem; + border-radius: 4px; + min-width: 44px; + text-align: center; +} + +.lh-method.get { background: rgba(0, 245, 212, 0.08); color: #00f5d4; } +.lh-method.post { background: rgba(255, 189, 46, 0.08); color: #FFBD2E; } +.lh-method.put { background: rgba(64, 158, 255, 0.08); color: #409EFF; } +.lh-method.delete { background: rgba(255, 95, 86, 0.08); color: #FF5F56; } + +.lh-status { + font-size: 0.65rem; + color: #27c93f; + display: flex; + align-items: center; + gap: 3px; + background: rgba(39, 201, 63, 0.08); + padding: 0.1rem 0.35rem; + border-radius: 4px; +} diff --git a/apps/web-dashboard/src/pages/Login.jsx b/apps/web-dashboard/src/pages/Login.jsx index ac719e089..c532e7e96 100644 --- a/apps/web-dashboard/src/pages/Login.jsx +++ b/apps/web-dashboard/src/pages/Login.jsx @@ -96,9 +96,9 @@ function Login() { return ( navigate('/')} >
- -
- From e06ebf52642617de2d5988da15ccf3b97bf72913 Mon Sep 17 00:00:00 2001 From: Siddh2024 Date: Tue, 16 Jun 2026 22:07:02 +0530 Subject: [PATCH 2/3] feat: enhance landing page with interactive components and unified nav (#251) - Add BorderGlow, Hyperspeed, and MagicBento components - Refactor hero section: centered layout with Hyperspeed background - Replace services grid with MagicBento card component - Add unified glassmorphism nav with active states - Update Pricing page to use shared nav and mobile overlay - Add gsap, three, postprocessing dependencies - Polish animations and visual effects across landing page --- apps/web-dashboard/package.json | 5 +- .../src/components/BorderGlow/BorderGlow.css | 110 ++ .../src/components/BorderGlow/BorderGlow.jsx | 156 +++ .../src/components/Hyperspeed/Hyperspeed.css | 17 + .../src/components/Hyperspeed/Hyperspeed.jsx | 1183 +++++++++++++++++ .../src/components/MagicBento/MagicBento.css | 298 +++++ .../src/components/MagicBento/MagicBento.jsx | 548 ++++++++ .../src/pages/LandingPage/index.jsx | 414 +++--- .../src/pages/LandingPage/style.css | 484 ++++--- apps/web-dashboard/src/pages/Pricing.jsx | 131 +- package-lock.json | 52 +- 11 files changed, 2943 insertions(+), 455 deletions(-) create mode 100644 apps/web-dashboard/src/components/BorderGlow/BorderGlow.css create mode 100644 apps/web-dashboard/src/components/BorderGlow/BorderGlow.jsx create mode 100644 apps/web-dashboard/src/components/Hyperspeed/Hyperspeed.css create mode 100644 apps/web-dashboard/src/components/Hyperspeed/Hyperspeed.jsx create mode 100644 apps/web-dashboard/src/components/MagicBento/MagicBento.css create mode 100644 apps/web-dashboard/src/components/MagicBento/MagicBento.jsx diff --git a/apps/web-dashboard/package.json b/apps/web-dashboard/package.json index d396d13a9..38c6abb56 100644 --- a/apps/web-dashboard/package.json +++ b/apps/web-dashboard/package.json @@ -19,7 +19,9 @@ "@tanstack/react-table": "^8.21.3", "axios": "^1.13.2", "framer-motion": "^12.35.0", + "gsap": "^3.15.0", "lucide-react": "^0.554.0", + "postprocessing": "^6.39.1", "react": "^19.2.0", "react-dom": "^19.2.0", "react-hot-toast": "^2.6.0", @@ -27,7 +29,8 @@ "react-router-dom": "^7.9.6", "recharts": "^2.13.0", "remark-gfm": "^4.0.1", - "tailwindcss": "^4.1.18" + "tailwindcss": "^4.1.18", + "three": "^0.184.0" }, "devDependencies": { "@eslint/js": "^9.39.1", diff --git a/apps/web-dashboard/src/components/BorderGlow/BorderGlow.css b/apps/web-dashboard/src/components/BorderGlow/BorderGlow.css new file mode 100644 index 000000000..5e1bffea4 --- /dev/null +++ b/apps/web-dashboard/src/components/BorderGlow/BorderGlow.css @@ -0,0 +1,110 @@ +.border-glow-card { + --edge-proximity: 0; + --cursor-angle: 45deg; + --edge-sensitivity: 30; + --color-sensitivity: calc(var(--edge-sensitivity) + 20); + --border-radius: 28px; + --glow-padding: 40px; + --cone-spread: 25; + + position: relative; + border-radius: var(--border-radius); + isolation: isolate; + transform: translate3d(0, 0, 0.01px); + display: flex; + flex-direction: column; + border: 1px solid rgba(255, 255, 255, 0.08); + background: var(--card-bg, rgba(255, 255, 255, 0.02)); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + overflow: visible; + box-shadow: + rgba(0, 0, 0, 0.1) 0px 1px 2px, + rgba(0, 0, 0, 0.1) 0px 2px 4px, + rgba(0, 0, 0, 0.1) 0px 4px 8px, + rgba(0, 0, 0, 0.1) 0px 8px 16px, + rgba(0, 0, 0, 0.1) 0px 16px 32px, + rgba(0, 0, 0, 0.1) 0px 32px 64px; +} + +.border-glow-card::before, +.border-glow-card > .edge-light { + content: ""; + position: absolute; + inset: 0; + border-radius: inherit; + transition: opacity 0.25s ease-out; + z-index: -1; +} + +.border-glow-card:not(:hover):not(.sweep-active)::before, +.border-glow-card:not(:hover):not(.sweep-active) > .edge-light { + opacity: 0; + transition: opacity 0.75s ease-in-out; +} + +/* colored mesh-gradient border */ +.border-glow-card::before { + border: 1px solid transparent; + background: + linear-gradient(#0c0a0f 0 100%) padding-box, + linear-gradient(rgba(255, 255, 255, 0) 0% 100%) border-box, + var(--gradient-one, radial-gradient(at 80% 55%, hsla(268, 100%, 76%, 1) 0px, transparent 50%)) border-box, + var(--gradient-two, radial-gradient(at 69% 34%, hsla(349, 100%, 74%, 1) 0px, transparent 50%)) border-box, + var(--gradient-three, radial-gradient(at 8% 6%, hsla(136, 100%, 78%, 1) 0px, transparent 50%)) border-box, + var(--gradient-four, radial-gradient(at 41% 38%, hsla(192, 100%, 64%, 1) 0px, transparent 50%)) border-box, + var(--gradient-five, radial-gradient(at 86% 85%, hsla(186, 100%, 74%, 1) 0px, transparent 50%)) border-box, + var(--gradient-six, radial-gradient(at 82% 18%, hsla(52, 100%, 65%, 1) 0px, transparent 50%)) border-box, + var(--gradient-seven, radial-gradient(at 51% 4%, hsla(12, 100%, 72%, 1) 0px, transparent 50%)) border-box, + var(--gradient-base, linear-gradient(#c299ff 0 100%)) border-box; + + opacity: calc((var(--edge-proximity) - var(--color-sensitivity)) / (100 - var(--color-sensitivity))); + + mask-image: + conic-gradient( + from var(--cursor-angle) at center, + black calc(var(--cone-spread) * 1%), + transparent calc((var(--cone-spread) + 15) * 1%), + transparent calc((100 - var(--cone-spread) - 15) * 1%), + black calc((100 - var(--cone-spread)) * 1%) + ); +} + +/* outer glow layer */ +.border-glow-card > .edge-light { + inset: calc(var(--glow-padding) * -1); + pointer-events: none; + z-index: 1; + + mask-image: + conic-gradient( + from var(--cursor-angle) at center, black 2.5%, transparent 10%, transparent 90%, black 97.5% + ); + + opacity: calc((var(--edge-proximity) - var(--edge-sensitivity)) / (100 - var(--edge-sensitivity))); + mix-blend-mode: plus-lighter; +} + +.border-glow-card > .edge-light::before { + content: ""; + position: absolute; + inset: var(--glow-padding); + border-radius: inherit; + box-shadow: + 0 0 1px 0 var(--glow-color-60, hsl(40deg 80% 80% / 60%)), + 0 0 3px 0 var(--glow-color-50, hsl(40deg 80% 80% / 50%)), + 0 0 6px 0 var(--glow-color-40, hsl(40deg 80% 80% / 40%)), + 0 0 15px 0 var(--glow-color-30, hsl(40deg 80% 80% / 30%)), + 0 0 25px 2px var(--glow-color-20, hsl(40deg 80% 80% / 20%)), + 0 0 50px 2px var(--glow-color-10, hsl(40deg 80% 80% / 10%)); +} + +.border-glow-inner { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; + width: 100%; + position: relative; + z-index: 1; +} diff --git a/apps/web-dashboard/src/components/BorderGlow/BorderGlow.jsx b/apps/web-dashboard/src/components/BorderGlow/BorderGlow.jsx new file mode 100644 index 000000000..e8bdf30dc --- /dev/null +++ b/apps/web-dashboard/src/components/BorderGlow/BorderGlow.jsx @@ -0,0 +1,156 @@ +import { useRef, useCallback, useEffect } from 'react'; +import './BorderGlow.css'; + +function parseHSL(hslStr) { + const match = hslStr.match(/([\d.]+)\s*([\d.]+)%?\s*([\d.]+)%?/); + if (!match) return { h: 40, s: 80, l: 80 }; + return { h: parseFloat(match[1]), s: parseFloat(match[2]), l: parseFloat(match[3]) }; +} + +function buildGlowVars(glowColor, intensity) { + const { h, s, l } = parseHSL(glowColor); + const base = `${h}deg ${s}% ${l}%`; + const opacities = [100, 60, 50, 40, 30, 20, 10]; + const keys = ['', '-60', '-50', '-40', '-30', '-20', '-10']; + const vars = {}; + for (let i = 0; i < opacities.length; i++) { + vars[`--glow-color${keys[i]}`] = `hsl(${base} / ${Math.min(opacities[i] * intensity, 100)}%)`; + } + return vars; +} + +const GRADIENT_POSITIONS = ['80% 55%', '69% 34%', '8% 6%', '41% 38%', '86% 85%', '82% 18%', '51% 4%']; +const GRADIENT_KEYS = ['--gradient-one', '--gradient-two', '--gradient-three', '--gradient-four', '--gradient-five', '--gradient-six', '--gradient-seven']; +const COLOR_MAP = [0, 1, 2, 0, 1, 2, 1]; + +function buildGradientVars(colors) { + const vars = {}; + for (let i = 0; i < 7; i++) { + const c = colors[Math.min(COLOR_MAP[i], colors.length - 1)]; + vars[GRADIENT_KEYS[i]] = `radial-gradient(at ${GRADIENT_POSITIONS[i]}, ${c} 0px, transparent 50%)`; + } + vars['--gradient-base'] = `linear-gradient(${colors[0]} 0 100%)`; + return vars; +} + +function easeOutCubic(x) { return 1 - Math.pow(1 - x, 3); } +function easeInCubic(x) { return x * x * x; } + +function animateValue({ start = 0, end = 100, duration = 1000, delay = 0, ease = easeOutCubic, onUpdate, onEnd }) { + const t0 = performance.now() + delay; + function tick() { + const elapsed = performance.now() - t0; + const t = Math.min(elapsed / duration, 1); + onUpdate(start + (end - start) * ease(t)); + if (t < 1) requestAnimationFrame(tick); + else if (onEnd) onEnd(); + } + setTimeout(() => requestAnimationFrame(tick), delay); +} + +const BorderGlow = ({ + children, + className = '', + edgeSensitivity = 30, + glowColor = '40 80 80', + backgroundColor = 'rgba(255, 255, 255, 0.02)', + borderRadius = 16, + glowRadius = 40, + glowIntensity = 1.0, + coneSpread = 25, + animated = false, + colors = ['#00f5d4', '#00b4ab', '#00ffd8'], + fillOpacity = 0.5, +}) => { + const cardRef = useRef(null); + + const getCenterOfElement = useCallback((el) => { + const { width, height } = el.getBoundingClientRect(); + return [width / 2, height / 2]; + }, []); + + const getEdgeProximity = useCallback((el, x, y) => { + const [cx, cy] = getCenterOfElement(el); + const dx = x - cx; + const dy = y - cy; + let kx = Infinity; + let ky = Infinity; + if (dx !== 0) kx = cx / Math.abs(dx); + if (dy !== 0) ky = cy / Math.abs(dy); + return Math.min(Math.max(1 / Math.min(kx, ky), 0), 1); + }, [getCenterOfElement]); + + const getCursorAngle = useCallback((el, x, y) => { + const [cx, cy] = getCenterOfElement(el); + const dx = x - cx; + const dy = y - cy; + if (dx === 0 && dy === 0) return 0; + const radians = Math.atan2(dy, dx); + let degrees = radians * (180 / Math.PI) + 90; + if (degrees < 0) degrees += 360; + return degrees; + }, [getCenterOfElement]); + + const handlePointerMove = useCallback((e) => { + const card = cardRef.current; + if (!card) return; + + const rect = card.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + const edge = getEdgeProximity(card, x, y); + const angle = getCursorAngle(card, x, y); + + card.style.setProperty('--edge-proximity', `${(edge * 100).toFixed(3)}`); + card.style.setProperty('--cursor-angle', `${angle.toFixed(3)}deg`); + }, [getEdgeProximity, getCursorAngle]); + + useEffect(() => { + if (!animated || !cardRef.current) return; + const card = cardRef.current; + const angleStart = 110; + const angleEnd = 465; + card.classList.add('sweep-active'); + card.style.setProperty('--cursor-angle', `${angleStart}deg`); + + animateValue({ duration: 500, onUpdate: v => card.style.setProperty('--edge-proximity', v) }); + animateValue({ ease: easeInCubic, duration: 1500, end: 50, onUpdate: v => { + card.style.setProperty('--cursor-angle', `${(angleEnd - angleStart) * (v / 100) + angleStart}deg`); + }}); + animateValue({ ease: easeOutCubic, delay: 1500, duration: 2250, start: 50, end: 100, onUpdate: v => { + card.style.setProperty('--cursor-angle', `${(angleEnd - angleStart) * (v / 100) + angleStart}deg`); + }}); + animateValue({ ease: easeInCubic, delay: 2500, duration: 1500, start: 100, end: 0, + onUpdate: v => card.style.setProperty('--edge-proximity', v), + onEnd: () => card.classList.remove('sweep-active'), + }); + }, [animated]); + + const glowVars = buildGlowVars(glowColor, glowIntensity); + + return ( +
+ +
+ {children} +
+
+ ); +}; + +export default BorderGlow; diff --git a/apps/web-dashboard/src/components/Hyperspeed/Hyperspeed.css b/apps/web-dashboard/src/components/Hyperspeed/Hyperspeed.css new file mode 100644 index 000000000..b6210354e --- /dev/null +++ b/apps/web-dashboard/src/components/Hyperspeed/Hyperspeed.css @@ -0,0 +1,17 @@ +#lights { + width: 100%; + height: 100%; + overflow: hidden; + position: absolute; + top: 0; + left: 0; + z-index: 0; /* Behind all content in the hero section! */ + pointer-events: none; /* Make sure users can click elements over it */ + opacity: 0.45; /* Soften the light beams for better foreground text readability */ +} + +canvas { + width: 100%; + height: 100%; + display: block; +} diff --git a/apps/web-dashboard/src/components/Hyperspeed/Hyperspeed.jsx b/apps/web-dashboard/src/components/Hyperspeed/Hyperspeed.jsx new file mode 100644 index 000000000..653017130 --- /dev/null +++ b/apps/web-dashboard/src/components/Hyperspeed/Hyperspeed.jsx @@ -0,0 +1,1183 @@ +import { BloomEffect, EffectComposer, EffectPass, RenderPass, SMAAEffect, SMAAPreset } from 'postprocessing'; +import { useEffect, useRef } from 'react'; +import * as THREE from 'three'; + +import './Hyperspeed.css'; + +const DEFAULT_EFFECT_OPTIONS = { + onSpeedUp: () => {}, + onSlowDown: () => {}, + distortion: 'turbulentDistortion', + length: 400, + roadWidth: 10, + islandWidth: 2, + lanesPerRoad: 4, + fov: 90, + fovSpeedUp: 150, + speedUp: 2, + carLightsFade: 0.4, + totalSideLightSticks: 20, + lightPairsPerRoadWay: 40, + shoulderLinesWidthPercentage: 0.05, + brokenLinesWidthPercentage: 0.1, + brokenLinesLengthPercentage: 0.5, + lightStickWidth: [0.12, 0.5], + lightStickHeight: [1.3, 1.7], + movingAwaySpeed: [60, 80], + movingCloserSpeed: [-120, -160], + carLightsLength: [400 * 0.03, 400 * 0.2], + carLightsRadius: [0.05, 0.14], + carWidthPercentage: [0.3, 0.5], + carShiftX: [-0.8, 0.8], + carFloorSeparation: [0, 5], + colors: { + roadColor: 0x080808, + islandColor: 0x0a0a0a, + background: 0x000000, + shoulderLines: 0xffffff, + brokenLines: 0xffffff, + leftCars: [0xd856bf, 0x6750a2, 0xc247ac], + rightCars: [0x03b3c3, 0x0e5ea5, 0x324555], + sticks: 0x03b3c3 + } +}; + +const Hyperspeed = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => { + const hyperspeed = useRef(null); + const appRef = useRef(null); + + useEffect(() => { + if (appRef.current) { + appRef.current.dispose(); + appRef.current = null; + const container = hyperspeed.current; + if (container) { + while (container.firstChild) { + container.removeChild(container.firstChild); + } + } + } + const mountainUniforms = { + uFreq: { value: new THREE.Vector3(3, 6, 10) }, + uAmp: { value: new THREE.Vector3(30, 30, 20) } + }; + + const xyUniforms = { + uFreq: { value: new THREE.Vector2(5, 2) }, + uAmp: { value: new THREE.Vector2(25, 15) } + }; + + const LongRaceUniforms = { + uFreq: { value: new THREE.Vector2(2, 3) }, + uAmp: { value: new THREE.Vector2(35, 10) } + }; + + const turbulentUniforms = { + uFreq: { value: new THREE.Vector4(4, 8, 8, 1) }, + uAmp: { value: new THREE.Vector4(25, 5, 10, 10) } + }; + + const deepUniforms = { + uFreq: { value: new THREE.Vector2(4, 8) }, + uAmp: { value: new THREE.Vector2(10, 20) }, + uPowY: { value: new THREE.Vector2(20, 2) } + }; + + let nsin = val => Math.sin(val) * 0.5 + 0.5; + + const distortions = { + mountainDistortion: { + uniforms: mountainUniforms, + getDistortion: ` + uniform vec3 uAmp; + uniform vec3 uFreq; + #define PI 3.14159265358979 + float nsin(float val){ + return sin(val) * 0.5 + 0.5; + } + vec3 getDistortion(float progress){ + float movementProgressFix = 0.02; + return vec3( + cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x, + nsin(progress * PI * uFreq.y + uTime) * uAmp.y - nsin(movementProgressFix * PI * uFreq.y + uTime) * uAmp.y, + nsin(progress * PI * uFreq.z + uTime) * uAmp.z - nsin(movementProgressFix * PI * uFreq.z + uTime) * uAmp.z + ); + } + `, + getJS: (progress, time) => { + let movementProgressFix = 0.02; + let uFreq = mountainUniforms.uFreq.value; + let uAmp = mountainUniforms.uAmp.value; + let distortion = new THREE.Vector3( + Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x - + Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x, + nsin(progress * Math.PI * uFreq.y + time) * uAmp.y - + nsin(movementProgressFix * Math.PI * uFreq.y + time) * uAmp.y, + nsin(progress * Math.PI * uFreq.z + time) * uAmp.z - + nsin(movementProgressFix * Math.PI * uFreq.z + time) * uAmp.z + ); + let lookAtAmp = new THREE.Vector3(2, 2, 2); + let lookAtOffset = new THREE.Vector3(0, 0, -5); + return distortion.multiply(lookAtAmp).add(lookAtOffset); + } + }, + xyDistortion: { + uniforms: xyUniforms, + getDistortion: ` + uniform vec2 uFreq; + uniform vec2 uAmp; + #define PI 3.14159265358979 + vec3 getDistortion(float progress){ + float movementProgressFix = 0.02; + return vec3( + cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x, + sin(progress * PI * uFreq.y + PI/2. + uTime) * uAmp.y - sin(movementProgressFix * PI * uFreq.y + PI/2. + uTime) * uAmp.y, + 0. + ); + } + `, + getJS: (progress, time) => { + let movementProgressFix = 0.02; + let uFreq = xyUniforms.uFreq.value; + let uAmp = xyUniforms.uAmp.value; + let distortion = new THREE.Vector3( + Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x - + Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x, + Math.sin(progress * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y - + Math.sin(movementProgressFix * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y, + 0 + ); + let lookAtAmp = new THREE.Vector3(2, 0.4, 1); + let lookAtOffset = new THREE.Vector3(0, 0, -3); + return distortion.multiply(lookAtAmp).add(lookAtOffset); + } + }, + LongRaceDistortion: { + uniforms: LongRaceUniforms, + getDistortion: ` + uniform vec2 uFreq; + uniform vec2 uAmp; + #define PI 3.14159265358979 + vec3 getDistortion(float progress){ + float camProgress = 0.0125; + return vec3( + sin(progress * PI * uFreq.x + uTime) * uAmp.x - sin(camProgress * PI * uFreq.x + uTime) * uAmp.x, + sin(progress * PI * uFreq.y + uTime) * uAmp.y - sin(camProgress * PI * uFreq.y + uTime) * uAmp.y, + 0. + ); + } + `, + getJS: (progress, time) => { + let camProgress = 0.0125; + let uFreq = LongRaceUniforms.uFreq.value; + let uAmp = LongRaceUniforms.uAmp.value; + let distortion = new THREE.Vector3( + Math.sin(progress * Math.PI * uFreq.x + time) * uAmp.x - + Math.sin(camProgress * Math.PI * uFreq.x + time) * uAmp.x, + Math.sin(progress * Math.PI * uFreq.y + time) * uAmp.y - + Math.sin(camProgress * Math.PI * uFreq.y + time) * uAmp.y, + 0 + ); + let lookAtAmp = new THREE.Vector3(1, 1, 0); + let lookAtOffset = new THREE.Vector3(0, 0, -5); + return distortion.multiply(lookAtAmp).add(lookAtOffset); + } + }, + turbulentDistortion: { + uniforms: turbulentUniforms, + getDistortion: ` + uniform vec4 uFreq; + uniform vec4 uAmp; + float nsin(float val){ + return sin(val) * 0.5 + 0.5; + } + #define PI 3.14159265358979 + float getDistortionX(float progress){ + return ( + cos(PI * progress * uFreq.r + uTime) * uAmp.r + + pow(cos(PI * progress * uFreq.g + uTime * (uFreq.g / uFreq.r)), 2. ) * uAmp.g + ); + } + float getDistortionY(float progress){ + return ( + -nsin(PI * progress * uFreq.b + uTime) * uAmp.b + + -pow(nsin(PI * progress * uFreq.a + uTime / (uFreq.b / uFreq.a)), 5.) * uAmp.a + ); + } + vec3 getDistortion(float progress){ + return vec3( + getDistortionX(progress) - getDistortionX(0.0125), + getDistortionY(progress) - getDistortionY(0.0125), + 0. + ); + } + `, + getJS: (progress, time) => { + const uFreq = turbulentUniforms.uFreq.value; + const uAmp = turbulentUniforms.uAmp.value; + + const getX = p => + Math.cos(Math.PI * p * uFreq.x + time) * uAmp.x + + Math.pow(Math.cos(Math.PI * p * uFreq.y + time * (uFreq.y / uFreq.x)), 2) * uAmp.y; + + const getY = p => + -nsin(Math.PI * p * uFreq.z + time) * uAmp.z - + Math.pow(nsin(Math.PI * p * uFreq.w + time / (uFreq.z / uFreq.w)), 5) * uAmp.w; + + let distortion = new THREE.Vector3( + getX(progress) - getX(progress + 0.007), + getY(progress) - getY(progress + 0.007), + 0 + ); + let lookAtAmp = new THREE.Vector3(-2, -5, 0); + let lookAtOffset = new THREE.Vector3(0, 0, -10); + return distortion.multiply(lookAtAmp).add(lookAtOffset); + } + }, + turbulentDistortionStill: { + uniforms: turbulentUniforms, + getDistortion: ` + uniform vec4 uFreq; + uniform vec4 uAmp; + float nsin(float val){ + return sin(val) * 0.5 + 0.5; + } + #define PI 3.14159265358979 + float getDistortionX(float progress){ + return ( + cos(PI * progress * uFreq.r) * uAmp.r + + pow(cos(PI * progress * uFreq.g * (uFreq.g / uFreq.r)), 2. ) * uAmp.g + ); + } + float getDistortionY(float progress){ + return ( + -nsin(PI * progress * uFreq.b) * uAmp.b + + -pow(nsin(PI * progress * uFreq.a / (uFreq.b / uFreq.a)), 5.) * uAmp.a + ); + } + vec3 getDistortion(float progress){ + return vec3( + getDistortionX(progress) - getDistortionX(0.02), + getDistortionY(progress) - getDistortionY(0.02), + 0. + ); + } + ` + }, + deepDistortionStill: { + uniforms: deepUniforms, + getDistortion: ` + uniform vec4 uFreq; + uniform vec4 uAmp; + uniform vec2 uPowY; + float nsin(float val){ + return sin(val) * 0.5 + 0.5; + } + #define PI 3.14159265358979 + float getDistortionX(float progress){ + return ( + sin(progress * PI * uFreq.x) * uAmp.x * 2. + ); + } + float getDistortionY(float progress){ + return ( + pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y) * uAmp.y + ); + } + vec3 getDistortion(float progress){ + return vec3( + getDistortionX(progress) - getDistortionX(0.02), + getDistortionY(progress) - getDistortionY(0.05), + 0. + ); + } + ` + }, + deepDistortion: { + uniforms: deepUniforms, + getDistortion: ` + uniform vec4 uFreq; + uniform vec4 uAmp; + uniform vec2 uPowY; + float nsin(float val){ + return sin(val) * 0.5 + 0.5; + } + #define PI 3.14159265358979 + float getDistortionX(float progress){ + return ( + sin(progress * PI * uFreq.x + uTime) * uAmp.x + ); + } + float getDistortionY(float progress){ + return ( + pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y + uTime) * uAmp.y + ); + } + vec3 getDistortion(float progress){ + return vec3( + getDistortionX(progress) - getDistortionX(0.02), + getDistortionY(progress) - getDistortionY(0.02), + 0. + ); + } + `, + getJS: (progress, time) => { + const uFreq = deepUniforms.uFreq.value; + const uAmp = deepUniforms.uAmp.value; + const uPowY = deepUniforms.uPowY.value; + + const getX = p => Math.sin(p * Math.PI * uFreq.x + time) * uAmp.x; + const getY = p => Math.pow(p * uPowY.x, uPowY.y) + Math.sin(p * Math.PI * uFreq.y + time) * uAmp.y; + + let distortion = new THREE.Vector3( + getX(progress) - getX(progress + 0.01), + getY(progress) - getY(progress + 0.01), + 0 + ); + let lookAtAmp = new THREE.Vector3(-2, -4, 0); + let lookAtOffset = new THREE.Vector3(0, 0, -10); + return distortion.multiply(lookAtAmp).add(lookAtOffset); + } + } + }; + + class App { + constructor(container, options = {}) { + this.options = options; + if (this.options.distortion == null) { + this.options.distortion = { + uniforms: distortion_uniforms, + getDistortion: distortion_vertex + }; + } + this.container = container; + this.hasValidSize = false; + + const initW = Math.max(1, container.offsetWidth); + const initH = Math.max(1, container.offsetHeight); + + this.renderer = new THREE.WebGLRenderer({ + antialias: false, + alpha: true + }); + this.renderer.setSize(initW, initH, false); + this.renderer.setPixelRatio(window.devicePixelRatio); + this.composer = new EffectComposer(this.renderer); + container.append(this.renderer.domElement); + + this.camera = new THREE.PerspectiveCamera(options.fov, initW / initH, 0.1, 10000); + this.camera.position.z = -5; + this.camera.position.y = 8; + this.camera.position.x = 0; + this.scene = new THREE.Scene(); + this.scene.background = null; + + let fog = new THREE.Fog(options.colors.background, options.length * 0.2, options.length * 500); + this.scene.fog = fog; + this.fogUniforms = { + fogColor: { value: fog.color }, + fogNear: { value: fog.near }, + fogFar: { value: fog.far } + }; + this.clock = new THREE.Clock(); + this.assets = {}; + this.disposed = false; + + this.road = new Road(this, options); + this.leftCarLights = new CarLights( + this, + options, + options.colors.leftCars, + options.movingAwaySpeed, + new THREE.Vector2(0, 1 - options.carLightsFade) + ); + this.rightCarLights = new CarLights( + this, + options, + options.colors.rightCars, + options.movingCloserSpeed, + new THREE.Vector2(1, 0 + options.carLightsFade) + ); + this.leftSticks = new LightsSticks(this, options); + + this.fovTarget = options.fov; + this.speedUpTarget = 0; + this.speedUp = 0; + this.timeOffset = 0; + + this.tick = this.tick.bind(this); + this.init = this.init.bind(this); + this.setSize = this.setSize.bind(this); + this.onMouseDown = this.onMouseDown.bind(this); + this.onMouseUp = this.onMouseUp.bind(this); + + this.onTouchStart = this.onTouchStart.bind(this); + this.onTouchEnd = this.onTouchEnd.bind(this); + this.onContextMenu = this.onContextMenu.bind(this); + + this.onWindowResize = this.onWindowResize.bind(this); + window.addEventListener('resize', this.onWindowResize); + + if (container.offsetWidth > 0 && container.offsetHeight > 0) { + this.hasValidSize = true; + } + } + + onWindowResize() { + const width = this.container.offsetWidth; + const height = this.container.offsetHeight; + + if (width <= 0 || height <= 0) { + this.hasValidSize = false; + return; + } + + this.renderer.setSize(width, height); + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + this.composer.setSize(width, height); + this.hasValidSize = true; + } + + initPasses() { + this.renderPass = new RenderPass(this.scene, this.camera); + this.bloomPass = new EffectPass( + this.camera, + new BloomEffect({ + luminanceThreshold: 0.2, + luminanceSmoothing: 0, + resolutionScale: 1 + }) + ); + + const smaaPass = new EffectPass( + this.camera, + new SMAAEffect({ + preset: SMAAPreset.MEDIUM, + searchImage: SMAAEffect.searchImageDataURL, + areaImage: SMAAEffect.areaImageDataURL + }) + ); + this.renderPass.renderToScreen = false; + this.bloomPass.renderToScreen = false; + smaaPass.renderToScreen = true; + this.composer.addPass(this.renderPass); + this.composer.addPass(this.bloomPass); + this.composer.addPass(smaaPass); + } + + loadAssets() { + const assets = this.assets; + return new Promise(resolve => { + const manager = new THREE.LoadingManager(resolve); + + const searchImage = new Image(); + const areaImage = new Image(); + assets.smaa = {}; + searchImage.addEventListener('load', function () { + assets.smaa.search = this; + manager.itemEnd('smaa-search'); + }); + + areaImage.addEventListener('load', function () { + assets.smaa.area = this; + manager.itemEnd('smaa-area'); + }); + manager.itemStart('smaa-search'); + manager.itemStart('smaa-area'); + + searchImage.src = SMAAEffect.searchImageDataURL; + areaImage.src = SMAAEffect.areaImageDataURL; + }); + } + + init() { + this.initPasses(); + const options = this.options; + this.road.init(); + this.leftCarLights.init(); + + this.leftCarLights.mesh.position.setX(-options.roadWidth / 2 - options.islandWidth / 2); + this.rightCarLights.init(); + this.rightCarLights.mesh.position.setX(options.roadWidth / 2 + options.islandWidth / 2); + this.leftSticks.init(); + this.leftSticks.mesh.position.setX(-(options.roadWidth + options.islandWidth / 2)); + + this.container.addEventListener('mousedown', this.onMouseDown); + this.container.addEventListener('mouseup', this.onMouseUp); + this.container.addEventListener('mouseout', this.onMouseUp); + + this.container.addEventListener('touchstart', this.onTouchStart, { passive: true }); + this.container.addEventListener('touchend', this.onTouchEnd, { passive: true }); + this.container.addEventListener('touchcancel', this.onTouchEnd, { passive: true }); + + this.container.addEventListener('contextmenu', this.onContextMenu); + + this.tick(); + } + + onMouseDown(ev) { + if (this.options.onSpeedUp) this.options.onSpeedUp(ev); + this.fovTarget = this.options.fovSpeedUp; + this.speedUpTarget = this.options.speedUp; + } + + onMouseUp(ev) { + if (this.options.onSlowDown) this.options.onSlowDown(ev); + this.fovTarget = this.options.fov; + this.speedUpTarget = 0; + } + + onTouchStart(ev) { + if (this.options.onSpeedUp) this.options.onSpeedUp(ev); + this.fovTarget = this.options.fovSpeedUp; + this.speedUpTarget = this.options.speedUp; + } + + onTouchEnd(ev) { + if (this.options.onSlowDown) this.options.onSlowDown(ev); + this.fovTarget = this.options.fov; + this.speedUpTarget = 0; + } + + onContextMenu(ev) { + ev.preventDefault(); + } + + update(delta) { + let lerpPercentage = Math.exp(-(-60 * Math.log2(1 - 0.1)) * delta); + this.speedUp += lerp(this.speedUp, this.speedUpTarget, lerpPercentage, 0.00001); + this.timeOffset += this.speedUp * delta; + + let time = this.clock.elapsedTime + this.timeOffset; + + this.rightCarLights.update(time); + this.leftCarLights.update(time); + this.leftSticks.update(time); + this.road.update(time); + + let updateCamera = false; + let fovChange = lerp(this.camera.fov, this.fovTarget, lerpPercentage); + if (fovChange !== 0) { + this.camera.fov += fovChange * delta * 6; + updateCamera = true; + } + + if (this.options.distortion.getJS) { + const distortion = this.options.distortion.getJS(0.025, time); + + this.camera.lookAt( + new THREE.Vector3( + this.camera.position.x + distortion.x, + this.camera.position.y + distortion.y, + this.camera.position.z + distortion.z + ) + ); + updateCamera = true; + } + if (updateCamera) { + this.camera.updateProjectionMatrix(); + } + } + + render(delta) { + this.composer.render(delta); + } + + dispose() { + this.disposed = true; + + if (this.scene) { + this.scene.traverse(object => { + const obj = object; + if (!obj.isMesh) return; + + if (obj.geometry) obj.geometry.dispose(); + + if (obj.material) { + if (Array.isArray(obj.material)) { + obj.material.forEach(material => material.dispose()); + } else { + obj.material.dispose(); + } + } + }); + this.scene.clear(); + } + + if (this.renderer) { + this.renderer.dispose(); + this.renderer.forceContextLoss(); + if (this.renderer.domElement && this.renderer.domElement.parentNode) { + this.renderer.domElement.parentNode.removeChild(this.renderer.domElement); + } + } + if (this.composer) { + this.composer.dispose(); + } + + window.removeEventListener('resize', this.onWindowResize); + if (this.container) { + this.container.removeEventListener('mousedown', this.onMouseDown); + this.container.removeEventListener('mouseup', this.onMouseUp); + this.container.removeEventListener('mouseout', this.onMouseUp); + + this.container.removeEventListener('touchstart', this.onTouchStart); + this.container.removeEventListener('touchend', this.onTouchEnd); + this.container.removeEventListener('touchcancel', this.onTouchEnd); + this.container.removeEventListener('contextmenu', this.onContextMenu); + } + } + + setSize(width, height, updateStyles) { + if (width <= 0 || height <= 0) { + this.hasValidSize = false; + return; + } + this.composer.setSize(width, height, updateStyles); + this.hasValidSize = true; + } + + tick() { + if (this.disposed) return; + + if (!this.hasValidSize) { + const w = this.container.offsetWidth; + const h = this.container.offsetHeight; + if (w > 0 && h > 0) { + this.renderer.setSize(w, h, false); + this.camera.aspect = w / h; + this.camera.updateProjectionMatrix(); + this.composer.setSize(w, h); + this.hasValidSize = true; + } else { + requestAnimationFrame(this.tick); + return; + } + } + + if (resizeRendererToDisplaySize(this.renderer, this.setSize)) { + const canvas = this.renderer.domElement; + if (this.hasValidSize) { + this.camera.aspect = canvas.clientWidth / canvas.clientHeight; + this.camera.updateProjectionMatrix(); + } + } + + if (this.hasValidSize) { + const delta = this.clock.getDelta(); + this.render(delta); + this.update(delta); + } + + requestAnimationFrame(this.tick); + } + } + + const distortion_uniforms = { + uDistortionX: { value: new THREE.Vector2(80, 3) }, + uDistortionY: { value: new THREE.Vector2(-40, 2.5) } + }; + + const distortion_vertex = ` + #define PI 3.14159265358979 + uniform vec2 uDistortionX; + uniform vec2 uDistortionY; + float nsin(float val){ + return sin(val) * 0.5 + 0.5; + } + vec3 getDistortion(float progress){ + progress = clamp(progress, 0., 1.); + float xAmp = uDistortionX.r; + float xFreq = uDistortionX.g; + float yAmp = uDistortionY.r; + float yFreq = uDistortionY.g; + return vec3( + xAmp * nsin(progress * PI * xFreq - PI / 2.), + yAmp * nsin(progress * PI * yFreq - PI / 2.), + 0. + ); + } + `; + + const random = base => { + if (Array.isArray(base)) return Math.random() * (base[1] - base[0]) + base[0]; + return Math.random() * base; + }; + + const pickRandom = arr => { + if (Array.isArray(arr)) return arr[Math.floor(Math.random() * arr.length)]; + return arr; + }; + + function lerp(current, target, speed = 0.1, limit = 0.001) { + let change = (target - current) * speed; + if (Math.abs(change) < limit) { + change = target - current; + } + return change; + } + + class CarLights { + constructor(webgl, options, colors, speed, fade) { + this.webgl = webgl; + this.options = options; + this.colors = colors; + this.speed = speed; + this.fade = fade; + } + + init() { + const options = this.options; + let curve = new THREE.LineCurve3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -1)); + let geometry = new THREE.TubeGeometry(curve, 40, 1, 8, false); + + let instanced = new THREE.InstancedBufferGeometry().copy(geometry); + instanced.instanceCount = options.lightPairsPerRoadWay * 2; + + let laneWidth = options.roadWidth / options.lanesPerRoad; + + let aOffset = []; + let aMetrics = []; + let aColor = []; + + let colors = this.colors; + if (Array.isArray(colors)) { + colors = colors.map(c => new THREE.Color(c)); + } else { + colors = new THREE.Color(colors); + } + + for (let i = 0; i < options.lightPairsPerRoadWay; i++) { + let radius = random(options.carLightsRadius); + let length = random(options.carLightsLength); + let speed = random(this.speed); + + let carLane = i % options.lanesPerRoad; + let laneX = carLane * laneWidth - options.roadWidth / 2 + laneWidth / 2; + + let carWidth = random(options.carWidthPercentage) * laneWidth; + let carShiftX = random(options.carShiftX) * laneWidth; + laneX += carShiftX; + + let offsetY = random(options.carFloorSeparation) + radius * 1.3; + + let offsetZ = -random(options.length); + + aOffset.push(laneX - carWidth / 2); + aOffset.push(offsetY); + aOffset.push(offsetZ); + + aOffset.push(laneX + carWidth / 2); + aOffset.push(offsetY); + aOffset.push(offsetZ); + + aMetrics.push(radius); + aMetrics.push(length); + aMetrics.push(speed); + + aMetrics.push(radius); + aMetrics.push(length); + aMetrics.push(speed); + + let color = pickRandom(colors); + aColor.push(color.r); + aColor.push(color.g); + aColor.push(color.b); + + aColor.push(color.r); + aColor.push(color.g); + aColor.push(color.b); + } + + instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 3, false)); + instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 3, false)); + instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false)); + + let material = new THREE.ShaderMaterial({ + fragmentShader: carLightsFragment, + vertexShader: carLightsVertex, + transparent: true, + uniforms: Object.assign( + { + uTime: { value: 0 }, + uTravelLength: { value: options.length }, + uFade: { value: this.fade } + }, + this.webgl.fogUniforms, + options.distortion.uniforms + ) + }); + + material.onBeforeCompile = shader => { + shader.vertexShader = shader.vertexShader.replace( + '#include ', + options.distortion.getDistortion + ); + }; + + let mesh = new THREE.Mesh(instanced, material); + mesh.frustumCulled = false; + this.webgl.scene.add(mesh); + this.mesh = mesh; + } + + update(time) { + this.mesh.material.uniforms.uTime.value = time; + } + } + + const carLightsFragment = ` + #define USE_FOG; + ${THREE.ShaderChunk['fog_pars_fragment']} + varying vec3 vColor; + varying vec2 vUv; + uniform vec2 uFade; + void main() { + vec3 color = vec3(vColor); + float alpha = smoothstep(uFade.x, uFade.y, vUv.x); + gl_FragColor = vec4(color, alpha); + if (gl_FragColor.a < 0.0001) discard; + ${THREE.ShaderChunk['fog_fragment']} + } + `; + + const carLightsVertex = ` + #define USE_FOG; + ${THREE.ShaderChunk['fog_pars_vertex']} + attribute vec3 aOffset; + attribute vec3 aMetrics; + attribute vec3 aColor; + uniform float uTravelLength; + uniform float uTime; + varying vec2 vUv; + varying vec3 vColor; + #include + void main() { + vec3 transformed = position.xyz; + float radius = aMetrics.r; + float myLength = aMetrics.g; + float speed = aMetrics.b; + + transformed.xy *= radius; + transformed.z *= myLength; + + transformed.z += myLength - mod(uTime * speed + aOffset.z, uTravelLength); + transformed.xy += aOffset.xy; + + float progress = abs(transformed.z / uTravelLength); + transformed.xyz += getDistortion(progress); + + vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.); + gl_Position = projectionMatrix * mvPosition; + vUv = uv; + vColor = aColor; + ${THREE.ShaderChunk['fog_vertex']} + } + `; + + class LightsSticks { + constructor(webgl, options) { + this.webgl = webgl; + this.options = options; + } + + init() { + const options = this.options; + const geometry = new THREE.PlaneGeometry(1, 1); + let instanced = new THREE.InstancedBufferGeometry().copy(geometry); + let totalSticks = options.totalSideLightSticks; + instanced.instanceCount = totalSticks; + + let stickoffset = options.length / (totalSticks - 1); + const aOffset = []; + const aColor = []; + const aMetrics = []; + + let colors = options.colors.sticks; + if (Array.isArray(colors)) { + colors = colors.map(c => new THREE.Color(c)); + } else { + colors = new THREE.Color(colors); + } + + for (let i = 0; i < totalSticks; i++) { + let width = random(options.lightStickWidth); + let height = random(options.lightStickHeight); + aOffset.push((i - 1) * stickoffset * 2 + stickoffset * Math.random()); + + let color = pickRandom(colors); + aColor.push(color.r); + aColor.push(color.g); + aColor.push(color.b); + + aMetrics.push(width); + aMetrics.push(height); + } + + instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 1, false)); + instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false)); + instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 2, false)); + + const material = new THREE.ShaderMaterial({ + fragmentShader: sideSticksFragment, + vertexShader: sideSticksVertex, + side: THREE.DoubleSide, + uniforms: Object.assign( + { + uTravelLength: { value: options.length }, + uTime: { value: 0 } + }, + this.webgl.fogUniforms, + options.distortion.uniforms + ) + }); + + material.onBeforeCompile = shader => { + shader.vertexShader = shader.vertexShader.replace( + '#include ', + options.distortion.getDistortion + ); + }; + + const mesh = new THREE.Mesh(instanced, material); + mesh.frustumCulled = false; + this.webgl.scene.add(mesh); + this.mesh = mesh; + } + + update(time) { + this.mesh.material.uniforms.uTime.value = time; + } + } + + const sideSticksVertex = ` + #define USE_FOG; + ${THREE.ShaderChunk['fog_pars_vertex']} + attribute float aOffset; + attribute vec3 aColor; + attribute vec2 aMetrics; + uniform float uTravelLength; + uniform float uTime; + varying vec3 vColor; + mat4 rotationY( in float angle ) { + return mat4( cos(angle), 0, sin(angle), 0, + 0, 1.0, 0, 0, + -sin(angle), 0, cos(angle), 0, + 0, 0, 0, 1); + } + #include + void main(){ + vec3 transformed = position.xyz; + float width = aMetrics.x; + float height = aMetrics.y; + + transformed.xy *= vec2(width, height); + float time = mod(uTime * 60. * 2. + aOffset, uTravelLength); + + transformed = (rotationY(3.14/2.) * vec4(transformed,1.)).xyz; + + transformed.z += - uTravelLength + time; + + float progress = abs(transformed.z / uTravelLength); + transformed.xyz += getDistortion(progress); + + transformed.y += height / 2.; + transformed.x += -width / 2.; + vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.); + gl_Position = projectionMatrix * mvPosition; + vColor = aColor; + ${THREE.ShaderChunk['fog_vertex']} + } + `; + + const sideSticksFragment = ` + #define USE_FOG; + ${THREE.ShaderChunk['fog_pars_fragment']} + varying vec3 vColor; + void main(){ + vec3 color = vec3(vColor); + gl_FragColor = vec4(color,1.); + ${THREE.ShaderChunk['fog_fragment']} + } + `; + + class Road { + constructor(webgl, options) { + this.webgl = webgl; + this.options = options; + this.uTime = { value: 0 }; + } + + createPlane(side, width, isRoad) { + const options = this.options; + let segments = 100; + const geometry = new THREE.PlaneGeometry( + isRoad ? options.roadWidth : options.islandWidth, + options.length, + 20, + segments + ); + let uniforms = { + uTravelLength: { value: options.length }, + uColor: { value: new THREE.Color(isRoad ? options.colors.roadColor : options.colors.islandColor) }, + uTime: this.uTime + }; + + if (isRoad) { + uniforms = Object.assign(uniforms, { + uLanes: { value: options.lanesPerRoad }, + uBrokenLinesColor: { value: new THREE.Color(options.colors.brokenLines) }, + uShoulderLinesColor: { value: new THREE.Color(options.colors.shoulderLines) }, + uShoulderLinesWidthPercentage: { value: options.shoulderLinesWidthPercentage }, + uBrokenLinesLengthPercentage: { value: options.brokenLinesLengthPercentage }, + uBrokenLinesWidthPercentage: { value: options.brokenLinesWidthPercentage } + }); + } + + const material = new THREE.ShaderMaterial({ + fragmentShader: isRoad ? roadFragment : islandFragment, + vertexShader: roadVertex, + side: THREE.DoubleSide, + uniforms: Object.assign(uniforms, this.webgl.fogUniforms, options.distortion.uniforms) + }); + + material.onBeforeCompile = shader => { + shader.vertexShader = shader.vertexShader.replace( + '#include ', + options.distortion.getDistortion + ); + }; + + const mesh = new THREE.Mesh(geometry, material); + mesh.rotation.x = -Math.PI / 2; + mesh.position.z = -options.length / 2; + mesh.position.x += (this.options.islandWidth / 2 + options.roadWidth / 2) * side; + this.webgl.scene.add(mesh); + + return mesh; + } + + init() { + this.leftRoadWay = this.createPlane(-1, this.options.roadWidth, true); + this.rightRoadWay = this.createPlane(1, this.options.roadWidth, true); + this.island = this.createPlane(0, this.options.islandWidth, false); + } + + update(time) { + this.uTime.value = time; + } + } + + const roadBaseFragment = ` + #define USE_FOG; + varying vec2 vUv; + uniform vec3 uColor; + uniform float uTime; + #include + ${THREE.ShaderChunk['fog_pars_fragment']} + void main() { + vec2 uv = vUv; + vec3 color = vec3(uColor); + #include + gl_FragColor = vec4(color, 1.); + ${THREE.ShaderChunk['fog_fragment']} + } + `; + + const islandFragment = roadBaseFragment + .replace('#include ', '') + .replace('#include ', ''); + + const roadMarkings_vars = ` + uniform float uLanes; + uniform vec3 uBrokenLinesColor; + uniform vec3 uShoulderLinesColor; + uniform float uShoulderLinesWidthPercentage; + uniform float uBrokenLinesWidthPercentage; + uniform float uBrokenLinesLengthPercentage; + highp float random(vec2 co) { + highp float a = 12.9898; + highp float b = 78.233; + highp float c = 43758.5453; + highp float dt = dot(co.xy, vec2(a, b)); + highp float sn = mod(dt, 3.14); + return fract(sin(sn) * c); + } + `; + + const roadMarkings_fragment = ` + uv.y = mod(uv.y + uTime * 0.05, 1.); + float laneWidth = 1.0 / uLanes; + float brokenLineWidth = laneWidth * uBrokenLinesWidthPercentage; + float laneEmptySpace = 1. - uBrokenLinesLengthPercentage; + + float brokenLines = step(1.0 - brokenLineWidth, fract(uv.x * 2.0)) * step(laneEmptySpace, fract(uv.y * 10.0)); + float sideLines = step(1.0 - brokenLineWidth, fract((uv.x - laneWidth * (uLanes - 1.0)) * 2.0)) + step(brokenLineWidth, uv.x); + + brokenLines = mix(brokenLines, sideLines, uv.x); + `; + + const roadFragment = roadBaseFragment + .replace('#include ', roadMarkings_fragment) + .replace('#include ', roadMarkings_vars); + + const roadVertex = ` + #define USE_FOG; + uniform float uTime; + ${THREE.ShaderChunk['fog_pars_vertex']} + uniform float uTravelLength; + varying vec2 vUv; + #include + void main() { + vec3 transformed = position.xyz; + vec3 distortion = getDistortion((transformed.y + uTravelLength / 2.) / uTravelLength); + transformed.x += distortion.x; + transformed.z += distortion.y; + transformed.y += -1. * distortion.z; + + vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.); + gl_Position = projectionMatrix * mvPosition; + vUv = uv; + ${THREE.ShaderChunk['fog_vertex']} + } + `; + + function resizeRendererToDisplaySize(renderer, setSize) { + const canvas = renderer.domElement; + const width = canvas.clientWidth; + const height = canvas.clientHeight; + if (width <= 0 || height <= 0) return false; + const needResize = canvas.width !== width || canvas.height !== height; + if (needResize) { + setSize(width, height, false); + } + return needResize; + } + + const container = hyperspeed.current; + if (!container) return; + + const options = { + ...DEFAULT_EFFECT_OPTIONS, + ...effectOptions, + colors: { ...DEFAULT_EFFECT_OPTIONS.colors, ...effectOptions.colors } + }; + options.distortion = distortions[options.distortion]; + + const myApp = new App(container, options); + appRef.current = myApp; + myApp.loadAssets().then(myApp.init); + + return () => { + if (appRef.current) { + appRef.current.dispose(); + appRef.current = null; + } + }; + }, [effectOptions]); + + return
; +}; + +export default Hyperspeed; diff --git a/apps/web-dashboard/src/components/MagicBento/MagicBento.css b/apps/web-dashboard/src/components/MagicBento/MagicBento.css new file mode 100644 index 000000000..84f28a379 --- /dev/null +++ b/apps/web-dashboard/src/components/MagicBento/MagicBento.css @@ -0,0 +1,298 @@ +:root { + --hue: 27; + --sat: 69%; + --white: hsl(0, 0%, 100%); + --purple-primary: rgba(0, 245, 212, 1); + --purple-glow: rgba(0, 245, 212, 0.2); + --purple-border: rgba(0, 245, 212, 0.8); + --border-color: #2F293A; + --background-dark: #120F17; + color-scheme: light dark; +} + +.card-grid { + display: grid; + gap: 0.75em; + padding: 1em; + max-width: 72em; + font-size: clamp(1rem, 0.9rem + 0.5vw, 1.5rem); +} + +.magic-bento-card { + display: flex; + flex-direction: column; + justify-content: space-between; + position: relative; + min-height: 230px; + width: 100%; + max-width: 100%; + padding: 1.4em; + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.02); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + font-weight: 300; + overflow: visible; + transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); + + --glow-x: 50%; + --glow-y: 50%; + --glow-intensity: 0; + --glow-radius: 200px; +} + +.magic-bento-card:hover { + transform: translateY(-4px); + border-color: rgba(0, 245, 212, 0.3); + background: rgba(0, 245, 212, 0.04); + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.3), 0 0 20px rgba(0, 245, 212, 0.08); +} + +.magic-bento-card__header, +.magic-bento-card__content { + display: flex; + position: relative; + color: var(--white); +} + +.magic-bento-card__header { + gap: 0.75em; + justify-content: space-between; + align-items: center; +} + +.magic-bento-card__content { + flex-direction: column; +} + +.magic-bento-card__label { + font-size: 10px; + font-weight: 600; + letter-spacing: 0.1em; + text-transform: uppercase; + color: rgba(0, 245, 212, 0.6); + padding: 0.2em 0.6em; + border-radius: 4px; + background: rgba(0, 245, 212, 0.06); + border: 1px solid rgba(0, 245, 212, 0.1); + transition: all 0.4s ease; + transform: translateY(0); + opacity: 0.7; +} + +.magic-bento-card:hover .magic-bento-card__label { + color: rgba(0, 245, 212, 0.9); + background: rgba(0, 245, 212, 0.1); + border-color: rgba(0, 245, 212, 0.25); + opacity: 1; + transform: translateY(-1px); +} + +.magic-bento-card__title, +.magic-bento-card__description { + --clamp-title: 1; + --clamp-desc: 2; +} + +.magic-bento-card__title { + font-weight: 500; + font-size: 15px; + margin: 0 0 0.35em; + color: #f0f0f0; + letter-spacing: -0.01em; + transition: all 0.35s ease 0.05s; + transform: translateY(0); +} + +.magic-bento-card:hover .magic-bento-card__title { + color: #ffffff; + transform: translateY(-2px); +} + +.magic-bento-card__description { + font-size: 12px; + line-height: 1.45; + color: rgba(255, 255, 255, 0.45); + transition: all 0.4s ease 0.1s; + transform: translateY(0); +} + +.magic-bento-card:hover .magic-bento-card__description { + color: rgba(255, 255, 255, 0.7); + transform: translateY(-2px); +} + +.magic-bento-card--text-autohide .magic-bento-card__title, +.magic-bento-card--text-autohide .magic-bento-card__description { + display: -webkit-box; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; +} + +.magic-bento-card--text-autohide .magic-bento-card__title { + -webkit-line-clamp: var(--clamp-title); + line-clamp: var(--clamp-title); +} + +.magic-bento-card--text-autohide .magic-bento-card__description { + -webkit-line-clamp: var(--clamp-desc); + line-clamp: var(--clamp-desc); +} + +@media (max-width: 599px) { + .card-grid { + grid-template-columns: 1fr; + width: 92%; + margin: 0 auto; + padding: 0.5em; + } + + .magic-bento-card { + width: 100%; + min-height: 200px; + } +} + +@media (min-width: 600px) { + .card-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width: 1024px) { + .card-grid { + grid-template-columns: repeat(4, 1fr); + } + + .magic-bento-card:nth-child(3) { + grid-column: span 2; + grid-row: span 2; + } + + .magic-bento-card:nth-child(4) { + grid-column: 1 / span 2; + grid-row: 2 / span 2; + } + + .magic-bento-card:nth-child(6) { + grid-column: 4; + grid-row: 3; + } +} + +.magic-bento-card--border-glow::after { + content: ''; + position: absolute; + inset: 0; + padding: 6px; + background: radial-gradient( + var(--glow-radius) circle at var(--glow-x) var(--glow-y), + rgba(0, 245, 212, calc(var(--glow-intensity) * 0.8)) 0%, + rgba(0, 245, 212, calc(var(--glow-intensity) * 0.4)) 30%, + transparent 60% + ); + border-radius: inherit; + -webkit-mask: + linear-gradient(#fff 0 0) content-box, + linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask: + linear-gradient(#fff 0 0) content-box, + linear-gradient(#fff 0 0); + mask-composite: exclude; + pointer-events: none; + opacity: 1; + transition: opacity 0.3s ease; + z-index: 1; +} + +.magic-bento-card--border-glow:hover::after { + opacity: 1; +} + +.magic-bento-card--border-glow:hover { + box-shadow: + 0 4px 20px rgba(0, 245, 212, 0.4), + 0 0 30px var(--purple-glow); +} + +.particle-container { + position: relative; + overflow: hidden; +} + +.particle::before { + content: ''; + position: absolute; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + background: rgba(0, 245, 212, 0.2); + border-radius: 50%; + z-index: -1; +} + +.particle-container:hover { + box-shadow: + 0 4px 20px rgba(0, 245, 212, 0.2), + 0 0 30px var(--purple-glow); +} + +.global-spotlight { + mix-blend-mode: screen; + will-change: transform, opacity; + z-index: 200 !important; + pointer-events: none; +} + +.bento-section { + position: relative; + user-select: none; +} + +.magic-bento-card__visual { + display: flex; + align-items: center; + justify-content: center; + flex: 1; + width: 100%; + height: 100%; + min-height: 110px; + overflow: hidden; + position: relative; + z-index: 2; + margin: 0.35em 0; + opacity: 0.85; + transition: opacity 0.4s ease; +} + +.magic-bento-card:hover .magic-bento-card__visual { + opacity: 1; +} + +/* Default mockup scaling for 1x1 cards to fit comfortably */ +.magic-bento-card__visual > * { + transform: scale(0.88); + transform-origin: center; + transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1); +} + +.magic-bento-card:hover .magic-bento-card__visual > * { + transform: scale(0.94); +} + +/* Larger mockup scale for 2x2 double-width cards on desktop */ +@media (min-width: 1024px) { + .magic-bento-card:nth-child(3) .magic-bento-card__visual > *, + .magic-bento-card:nth-child(4) .magic-bento-card__visual > * { + transform: scale(1.1); + } + + .magic-bento-card:nth-child(3):hover .magic-bento-card__visual > *, + .magic-bento-card:nth-child(4):hover .magic-bento-card__visual > * { + transform: scale(1.15); + } +} diff --git a/apps/web-dashboard/src/components/MagicBento/MagicBento.jsx b/apps/web-dashboard/src/components/MagicBento/MagicBento.jsx new file mode 100644 index 000000000..ba1de2dae --- /dev/null +++ b/apps/web-dashboard/src/components/MagicBento/MagicBento.jsx @@ -0,0 +1,548 @@ +import { useRef, useEffect, useCallback, useState } from 'react'; +import { gsap } from 'gsap'; +import './MagicBento.css'; +import BorderGlow from '../BorderGlow/BorderGlow'; + +const DEFAULT_PARTICLE_COUNT = 12; +const DEFAULT_SPOTLIGHT_RADIUS = 300; +const DEFAULT_GLOW_COLOR = '0, 245, 212'; +const MOBILE_BREAKPOINT = 768; + +const cardData = [ + { + color: '#120F17', + title: 'User Authentication', + description: 'Pre-configured secure login/signup flows, session handling with JWTs, and third-party login providers with zero setup.', + label: 'IDENTITY' + }, + { + color: '#120F17', + title: 'JSON Document Database', + description: 'Direct database mutations and queries from your client app. Automated validation schemas protect integrity at high speed.', + label: 'DATABASE' + }, + { + color: '#120F17', + title: 'Secure File Storage', + description: 'Upload, manage, and deliver media assets like avatars and documents. Integrated with bucket systems and global delivery networks.', + label: 'STORAGE' + }, + { + color: '#120F17', + title: 'Transactional Mailer', + description: 'Pre-wired email dispatchers for user verification, password recovery, and system notifications with customizable templates.', + label: 'COMMUNICATION' + }, + { + color: '#120F17', + title: 'Row-Level Security', + description: 'Fine-grained read/write security with RLS. Restrict access based on document ownership and user authentication tokens.', + label: 'SECURITY' + }, + { + color: '#120F17', + title: 'Realtime Broadcast', + description: 'Listen to database mutations instantly or broadcast custom events across clients using low-latency WebSocket connections.', + label: 'REALTIME' + } +]; + + +const createParticleElement = (x, y, color = DEFAULT_GLOW_COLOR) => { + const el = document.createElement('div'); + el.className = 'particle'; + el.style.cssText = ` + position: absolute; + width: 4px; + height: 4px; + border-radius: 50%; + background: rgba(${color}, 1); + box-shadow: 0 0 6px rgba(${color}, 0.6); + pointer-events: none; + z-index: 100; + left: ${x}px; + top: ${y}px; + `; + return el; +}; + +const calculateSpotlightValues = radius => ({ + proximity: radius * 0.5, + fadeDistance: radius * 0.75 +}); + +const updateCardGlowProperties = (card, mouseX, mouseY, glow, radius) => { + const rect = card.getBoundingClientRect(); + const relativeX = ((mouseX - rect.left) / rect.width) * 100; + const relativeY = ((mouseY - rect.top) / rect.height) * 100; + + card.style.setProperty('--glow-x', `${relativeX}%`); + card.style.setProperty('--glow-y', `${relativeY}%`); + card.style.setProperty('--glow-intensity', glow.toString()); + card.style.setProperty('--glow-radius', `${radius}px`); +}; + +const ParticleCard = ({ + children, + className = '', + disableAnimations = false, + style, + particleCount = DEFAULT_PARTICLE_COUNT, + glowColor = DEFAULT_GLOW_COLOR, + enableTilt = true, + clickEffect = false, + enableMagnetism = false +}) => { + const cardRef = useRef(null); + const particlesRef = useRef([]); + const timeoutsRef = useRef([]); + const isHoveredRef = useRef(false); + const memoizedParticles = useRef([]); + const particlesInitialized = useRef(false); + const magnetismAnimationRef = useRef(null); + + const initializeParticles = useCallback(() => { + if (particlesInitialized.current || !cardRef.current) return; + + const { width, height } = cardRef.current.getBoundingClientRect(); + memoizedParticles.current = Array.from({ length: particleCount }, () => + createParticleElement(Math.random() * width, Math.random() * height, glowColor) + ); + particlesInitialized.current = true; + }, [particleCount, glowColor]); + + const clearAllParticles = useCallback(() => { + timeoutsRef.current.forEach(clearTimeout); + timeoutsRef.current = []; + magnetismAnimationRef.current?.kill(); + + particlesRef.current.forEach(particle => { + gsap.to(particle, { + scale: 0, + opacity: 0, + duration: 0.3, + ease: 'back.in(1.7)', + onComplete: () => { + particle.parentNode?.removeChild(particle); + } + }); + }); + particlesRef.current = []; + }, []); + + const animateParticles = useCallback(() => { + if (!cardRef.current || !isHoveredRef.current) return; + + if (!particlesInitialized.current) { + initializeParticles(); + } + + memoizedParticles.current.forEach((particle, index) => { + const timeoutId = setTimeout(() => { + if (!isHoveredRef.current || !cardRef.current) return; + + const clone = particle.cloneNode(true); + cardRef.current.appendChild(clone); + particlesRef.current.push(clone); + + gsap.fromTo(clone, { scale: 0, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.3, ease: 'back.out(1.7)' }); + + gsap.to(clone, { + x: (Math.random() - 0.5) * 100, + y: (Math.random() - 0.5) * 100, + rotation: Math.random() * 360, + duration: 2 + Math.random() * 2, + ease: 'none', + repeat: -1, + yoyo: true + }); + + gsap.to(clone, { + opacity: 0.3, + duration: 1.5, + ease: 'power2.inOut', + repeat: -1, + yoyo: true + }); + }, index * 100); + + timeoutsRef.current.push(timeoutId); + }); + }, [initializeParticles]); + + useEffect(() => { + if (disableAnimations || !cardRef.current) return; + + const element = cardRef.current; + + const handleMouseEnter = () => { + isHoveredRef.current = true; + animateParticles(); + + if (enableTilt) { + gsap.to(element, { + rotateX: 5, + rotateY: 5, + duration: 0.3, + ease: 'power2.out', + transformPerspective: 1000 + }); + } + }; + + const handleMouseLeave = () => { + isHoveredRef.current = false; + clearAllParticles(); + + if (enableTilt) { + gsap.to(element, { + rotateX: 0, + rotateY: 0, + duration: 0.3, + ease: 'power2.out' + }); + } + + if (enableMagnetism) { + gsap.to(element, { + x: 0, + y: 0, + duration: 0.3, + ease: 'power2.out' + }); + } + }; + + const handleMouseMove = e => { + if (!enableTilt && !enableMagnetism) return; + + const rect = element.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + const centerX = rect.width / 2; + const centerY = rect.height / 2; + + if (enableTilt) { + const rotateX = ((y - centerY) / centerY) * -10; + const rotateY = ((x - centerX) / centerX) * 10; + + gsap.to(element, { + rotateX, + rotateY, + duration: 0.1, + ease: 'power2.out', + transformPerspective: 1000 + }); + } + + if (enableMagnetism) { + const magnetX = (x - centerX) * 0.05; + const magnetY = (y - centerY) * 0.05; + + magnetismAnimationRef.current = gsap.to(element, { + x: magnetX, + y: magnetY, + duration: 0.3, + ease: 'power2.out' + }); + } + }; + + const handleClick = e => { + if (!clickEffect) return; + + const rect = element.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + const maxDistance = Math.max( + Math.hypot(x, y), + Math.hypot(x - rect.width, y), + Math.hypot(x, y - rect.height), + Math.hypot(x - rect.width, y - rect.height) + ); + + const ripple = document.createElement('div'); + ripple.style.cssText = ` + position: absolute; + width: ${maxDistance * 2}px; + height: ${maxDistance * 2}px; + border-radius: 50%; + background: radial-gradient(circle, rgba(${glowColor}, 0.4) 0%, rgba(${glowColor}, 0.2) 30%, transparent 70%); + left: ${x - maxDistance}px; + top: ${y - maxDistance}px; + pointer-events: none; + z-index: 1000; + `; + + element.appendChild(ripple); + + gsap.fromTo( + ripple, + { + scale: 0, + opacity: 1 + }, + { + scale: 1, + opacity: 0, + duration: 0.8, + ease: 'power2.out', + onComplete: () => ripple.remove() + } + ); + }; + + element.addEventListener('mouseenter', handleMouseEnter); + element.addEventListener('mouseleave', handleMouseLeave); + element.addEventListener('mousemove', handleMouseMove); + element.addEventListener('click', handleClick); + + return () => { + isHoveredRef.current = false; + element.removeEventListener('mouseenter', handleMouseEnter); + element.removeEventListener('mouseleave', handleMouseLeave); + element.removeEventListener('mousemove', handleMouseMove); + element.removeEventListener('click', handleClick); + clearAllParticles(); + }; + }, [animateParticles, clearAllParticles, disableAnimations, enableTilt, enableMagnetism, clickEffect, glowColor]); + + return ( +
+ {children} +
+ ); +}; + +const GlobalSpotlight = ({ + gridRef, + disableAnimations = false, + enabled = true, + spotlightRadius = DEFAULT_SPOTLIGHT_RADIUS, + glowColor = DEFAULT_GLOW_COLOR +}) => { + const spotlightRef = useRef(null); + const isInsideSection = useRef(false); + + useEffect(() => { + if (disableAnimations || !gridRef?.current || !enabled) return; + + const spotlight = document.createElement('div'); + spotlight.className = 'global-spotlight'; + spotlight.style.cssText = ` + position: fixed; + width: 800px; + height: 800px; + border-radius: 50%; + pointer-events: none; + background: radial-gradient(circle, + rgba(${glowColor}, 0.15) 0%, + rgba(${glowColor}, 0.08) 15%, + rgba(${glowColor}, 0.04) 25%, + rgba(${glowColor}, 0.02) 40%, + rgba(${glowColor}, 0.01) 65%, + transparent 70% + ); + z-index: 200; + opacity: 0; + transform: translate(-50%, -50%); + mix-blend-mode: screen; + `; + document.body.appendChild(spotlight); + spotlightRef.current = spotlight; + + const handleMouseMove = e => { + if (!spotlightRef.current || !gridRef.current) return; + + const section = gridRef.current.closest('.bento-section'); + const rect = section?.getBoundingClientRect(); + const mouseInside = + rect && e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom; + + isInsideSection.current = mouseInside || false; + const cards = gridRef.current.querySelectorAll('.magic-bento-card'); + + if (!mouseInside) { + gsap.to(spotlightRef.current, { + opacity: 0, + duration: 0.3, + ease: 'power2.out' + }); + cards.forEach(card => { + card.style.setProperty('--glow-intensity', '0'); + }); + return; + } + + const { proximity, fadeDistance } = calculateSpotlightValues(spotlightRadius); + let minDistance = Infinity; + + cards.forEach(card => { + const cardElement = card; + const cardRect = cardElement.getBoundingClientRect(); + const centerX = cardRect.left + cardRect.width / 2; + const centerY = cardRect.top + cardRect.height / 2; + const distance = + Math.hypot(e.clientX - centerX, e.clientY - centerY) - Math.max(cardRect.width, cardRect.height) / 2; + const effectiveDistance = Math.max(0, distance); + + minDistance = Math.min(minDistance, effectiveDistance); + + let glowIntensity = 0; + if (effectiveDistance <= proximity) { + glowIntensity = 1; + } else if (effectiveDistance <= fadeDistance) { + glowIntensity = (fadeDistance - effectiveDistance) / (fadeDistance - proximity); + } + + updateCardGlowProperties(cardElement, e.clientX, e.clientY, glowIntensity, spotlightRadius); + }); + + gsap.to(spotlightRef.current, { + left: e.clientX, + top: e.clientY, + duration: 0.1, + ease: 'power2.out' + }); + + const targetOpacity = + minDistance <= proximity + ? 0.8 + : minDistance <= fadeDistance + ? ((fadeDistance - minDistance) / (fadeDistance - proximity)) * 0.8 + : 0; + + gsap.to(spotlightRef.current, { + opacity: targetOpacity, + duration: targetOpacity > 0 ? 0.2 : 0.5, + ease: 'power2.out' + }); + }; + + const handleMouseLeave = () => { + isInsideSection.current = false; + gridRef.current?.querySelectorAll('.magic-bento-card').forEach(card => { + card.style.setProperty('--glow-intensity', '0'); + }); + if (spotlightRef.current) { + gsap.to(spotlightRef.current, { + opacity: 0, + duration: 0.3, + ease: 'power2.out' + }); + } + }; + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseleave', handleMouseLeave); + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseleave', handleMouseLeave); + spotlightRef.current?.parentNode?.removeChild(spotlightRef.current); + }; + }, [gridRef, disableAnimations, enabled, spotlightRadius, glowColor]); + + return null; +}; + +const BentoCardGrid = ({ children, gridRef }) => ( +
+ {children} +
+); + +const useMobileDetection = () => { + const [isMobile, setIsMobile] = useState(false); + + useEffect(() => { + const checkMobile = () => setIsMobile(window.innerWidth <= MOBILE_BREAKPOINT); + + checkMobile(); + window.addEventListener('resize', checkMobile); + + return () => window.removeEventListener('resize', checkMobile); + }, []); + + return isMobile; +}; + +const MagicBento = ({ + cards, + textAutoHide = true, + // enableStars = true, + enableSpotlight = true, + // enableBorderGlow = true, + disableAnimations = false, + spotlightRadius = DEFAULT_SPOTLIGHT_RADIUS, + // particleCount = DEFAULT_PARTICLE_COUNT, + // enableTilt = false, + glowColor = DEFAULT_GLOW_COLOR, + // clickEffect = true, + // enableMagnetism = true +}) => { + const gridRef = useRef(null); + const isMobile = useMobileDetection(); + const shouldDisableAnimations = disableAnimations || isMobile; + + return ( + <> + {enableSpotlight && ( + + )} + + + {(cards || cardData).map((card, index) => { + const title = card.title; + const description = card.description || card.desc; + const label = card.label || card.badge; + const visual = card.visual; + + const baseClassName = `magic-bento-card ${textAutoHide ? 'magic-bento-card--text-autohide' : ''}`; + + return ( + +
+
{label}
+
+ {visual && ( +
+ {visual} +
+ )} +
+

{title}

+

{description}

+
+
+ ); + })} +
+ + ); +}; + +export default MagicBento; diff --git a/apps/web-dashboard/src/pages/LandingPage/index.jsx b/apps/web-dashboard/src/pages/LandingPage/index.jsx index 2662cfc33..4806cffbb 100644 --- a/apps/web-dashboard/src/pages/LandingPage/index.jsx +++ b/apps/web-dashboard/src/pages/LandingPage/index.jsx @@ -1,7 +1,6 @@ -import { useState, useEffect, useRef } from 'react'; -import { Link, useNavigate } from 'react-router-dom'; +import { useState, useEffect } from 'react'; +import { Link, useNavigate, useLocation } from 'react-router-dom'; import { useAuth } from '../../context/AuthContext'; -import api from '../../utils/api'; import BackToTop from '../../components/Layout/BackToTop'; import { Database, @@ -26,12 +25,12 @@ import { Plus, Github, Mail, - UserRound, - Eye, - EyeOff + UserRound } from 'lucide-react'; import { motion as Motion, AnimatePresence } from 'framer-motion'; import Footer from '../../components/Layout/Footer'; +import MagicBento from '../../components/MagicBento/MagicBento'; +import Hyperspeed from '../../components/Hyperspeed/Hyperspeed'; import './style.css'; const HERO_ENDPOINTS = [ @@ -427,19 +426,50 @@ const APP_SERVICES = [ color: '#409EFF', visual: (
-
-
- -
- avatar_profile.png -
- Uploading (54%)... - Complete • 1.4 MB +
+
+ + media-uploads + 3 files +
+
+
+
IMG
+
+ avatar.png + 1.4 MB +
+ +
+
+
DOC
+
+ invoice_q4.pdf + 340 KB +
+ +
+
+
VID
+
+ demo_clip.mp4 + + Uploading… + Done • 12 MB + +
+
+ + + +
-
-
+
+
@@ -517,19 +547,70 @@ const APP_SERVICES = [ } ]; +const HYPERSPEED_OPTIONS = { + distortion: 'turbulentDistortion', + length: 400, + roadWidth: 10, + islandWidth: 2, + lanesPerRoad: 3, + fov: 90, + fovSpeedUp: 150, + speedUp: 2, + carLightsFade: 0.4, + totalSideLightSticks: 20, + lightPairsPerRoadWay: 40, + shoulderLinesWidthPercentage: 0.05, + brokenLinesWidthPercentage: 0.1, + brokenLinesLengthPercentage: 0.5, + lightStickWidth: [0.12, 0.5], + lightStickHeight: [1.3, 1.7], + movingAwaySpeed: [60, 80], + movingCloserSpeed: [-120, -160], + carLightsLength: [12, 80], + carLightsRadius: [0.05, 0.14], + carWidthPercentage: [0.3, 0.5], + carShiftX: [-0.8, 0.8], + carFloorSeparation: [0, 5], + colors: { + roadColor: 0x080808, + islandColor: 0x0a0a0a, + background: 0x000000, + shoulderLines: 0x00f5d4, + brokenLines: 0x00f5d4, + leftCars: [0x00f5d4, 0x00ffd8, 0x00b4ab], + rightCars: [0x00f5d4, 0x028090, 0x00ffd8], + sticks: 0x00f5d4 + } +}; + +const NAV_ITEMS = [ + { label: 'Features', href: '/#client-services', icon: Zap }, + { label: 'Use Cases', href: '/#use-cases', icon: Box }, + { label: 'Pricing', href: '/pricing', icon: Check }, + { label: 'Docs', href: 'https://docs.ub.bitbros.in', icon: Terminal, external: true } +]; + function LandingPage() { - const { isAuthenticated, login } = useAuth(); + const { isAuthenticated } = useAuth(); const navigate = useNavigate(); + const location = useLocation(); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); - const [isNavVisible, setIsNavVisible] = useState(true); - const lastScrollY = useRef(0); const [scrolled, setScrolled] = useState(false); + useEffect(() => { + if (location.hash) { + const id = location.hash.substring(1); + const element = document.getElementById(id); + if (element) { + const timer = setTimeout(() => { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }, 100); + return () => clearTimeout(timer); + } + } + }, [location.pathname, location.hash]); + const [openFaqIndex, setOpenFaqIndex] = useState(null); - const [authMode, setAuthMode] = useState('login'); - const [authForm, setAuthForm] = useState({ name: '', email: '', password: '' }); - const [showPassword, setShowPassword] = useState(false); - const [authLoading, setAuthLoading] = useState(false); const [hoveredTech, setHoveredTech] = useState(null); const [studioStep, setStudioStep] = useState(6); @@ -578,17 +659,7 @@ function LandingPage() { useEffect(() => { const handleScroll = () => { const currentScrollY = window.scrollY; - const delta = currentScrollY - lastScrollY.current; setScrolled(currentScrollY > 20); - - // Show quickly on even slight upward scroll; hide only on clear downward movement. - if (currentScrollY < 80 || delta < -2) { - setIsNavVisible(true); - } else if (delta > 10 && currentScrollY > 140) { - setIsNavVisible(false); - } - - lastScrollY.current = currentScrollY; }; window.addEventListener('scroll', handleScroll); @@ -602,38 +673,7 @@ function LandingPage() { setOpenFaqIndex(openFaqIndex === index ? null : index); }; - const handleAuthChange = (e) => { - const { name, value } = e.target; - setAuthForm(prev => ({ ...prev, [name]: value })); - }; - const handleAuthSubmit = async (e) => { - e.preventDefault(); - setAuthLoading(true); - try { - if (authMode === 'signup') { - const resp = await api.post('/api/auth/register', authForm); - if (resp.data?.success) { - navigate('/login'); - } - } else { - const resp = await api.post('/api/auth/login', { email: authForm.email, password: authForm.password }); - if (resp.data?.success) { - login(resp.data.user); - navigate('/dashboard'); - } - } - } catch (err) { - console.error('Auth error:', err); - } finally { - setAuthLoading(false); - } - }; - - const handleGithubSignIn = () => { - setAuthLoading(true); - window.location.href = `${import.meta.env.VITE_PUBLIC_API_URL}/api/userAuth/github/start?x-api-key=${import.meta.env.VITE_PUBLIC_API_KEY}`; - }; return (
@@ -670,29 +710,48 @@ function LandingPage() { )}
-
@@ -909,37 +847,19 @@ function LandingPage() {

-
- {APP_SERVICES.map((service, index) => { - const IconComponent = service.icon; - return ( - -
-
-
- -
- {service.badge} -
-
-

{service.title}

-

{service.desc}

-
-
- {service.visual} -
-
- ); - })} -
+
diff --git a/apps/web-dashboard/src/pages/LandingPage/style.css b/apps/web-dashboard/src/pages/LandingPage/style.css index 4ced93479..89c818123 100644 --- a/apps/web-dashboard/src/pages/LandingPage/style.css +++ b/apps/web-dashboard/src/pages/LandingPage/style.css @@ -84,38 +84,49 @@ /* NAVBAR */ .nav-glass { - --nav-height: 80px; - background: rgba(3, 3, 3, 0.7); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - border-bottom: 1px solid rgba(255, 255, 255, 0.05); + --nav-height: 70px; + background: rgba(10, 8, 15, 0.5); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.08); position: fixed; - top: 0; - left: 0; - right: 0; + top: 1.5rem; + left: 50%; + transform: translateX(-50%); z-index: 100; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); - width: 100%; + transition: all 0.5s cubic-bezier(0.16, 1, 0.3, 1); + width: calc(100% - 3rem); + max-width: 1200px; + border-radius: 99px; + box-shadow: + 0 10px 40px rgba(0, 0, 0, 0.5), + inset 0 1px 0 rgba(255, 255, 255, 0.15), + inset 0 -1px 0 rgba(0, 0, 0, 0.3); } .nav-scrolled { - background: rgba(3, 3, 3, 0.95); - border-bottom: 1px solid rgba(255, 255, 255, 0.1); - box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4); + background: rgba(8, 6, 12, 0.82); + border: 1px solid rgba(0, 245, 212, 0.2); + box-shadow: + 0 15px 50px rgba(0, 0, 0, 0.7), + inset 0 1px 0 rgba(255, 255, 255, 0.1), + 0 0 15px rgba(0, 245, 212, 0.05); + top: 0.75rem; + width: calc(100% - 2rem); } .nav-hidden { - transform: translateY(-100%); + transform: translate(-50%, -150%); } .nav-container { - max-width: 1400px; - margin: 0 auto; + max-width: 100%; + margin: 0; display: flex; justify-content: space-between; align-items: center; - padding: 1rem 2rem; - gap: 2rem; + padding: 0.5rem 1.5rem 0.5rem 2rem; + gap: 1.5rem; } .nav-logo { @@ -126,87 +137,122 @@ } .nav-logo img { - height: 36px; + height: 32px; width: auto; } .nav-logo-text { - font-size: 1.25rem; - font-weight: 700; - color: #fff; - letter-spacing: -0.02em; + font-size: 1.2rem; + font-weight: 800; + background: linear-gradient(135deg, #ffffff 40%, #a3a1a8 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + letter-spacing: -0.025em; + text-shadow: 0 2px 10px rgba(0,0,0,0.3); } .nav-links { display: flex; align-items: center; - gap: 0.5rem; + gap: 0.25rem; } .nav-link { display: flex; align-items: center; - gap: 0.5rem; - padding: 0.5rem 1rem; - color: #888; + gap: 0.45rem; + padding: 0.5rem 1.25rem; + color: #9e9ba5; text-decoration: none; - font-size: 0.9rem; + font-size: 0.88rem; font-weight: 500; - border-radius: 8px; - transition: all 0.2s ease; + border-radius: 99px; + border: 1px solid transparent; + transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1); } .nav-link:hover { - color: #fff; - background: rgba(255, 255, 255, 0.05); + color: #00f5d4; + background: rgba(0, 245, 212, 0.06); + border-color: rgba(0, 245, 212, 0.1); + box-shadow: 0 0 12px rgba(0, 245, 212, 0.05); } .nav-link svg { - opacity: 0.7; + opacity: 0.75; + transition: all 0.3s ease; +} + +.nav-link:hover svg { + transform: translateY(-1px); + color: #00f5d4; + opacity: 1; +} + +.nav-link.active { + color: #00f5d4 !important; + background: transparent !important; + border-color: transparent !important; + font-weight: 600; + box-shadow: none !important; +} + +.nav-link.active svg { + color: #00f5d4 !important; + opacity: 1; } .nav-actions { display: flex; align-items: center; - gap: 0.75rem; + gap: 0.5rem; flex-shrink: 0; } .nav-btn-ghost { - padding: 0.5rem 1.25rem; - color: #aaa; + padding: 0.45rem 1.25rem; + color: #9e9ba5; text-decoration: none; - font-size: 0.9rem; + font-size: 0.88rem; font-weight: 500; - border-radius: 8px; - transition: all 0.2s ease; + border-radius: 99px; + border: 1px solid transparent; + transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1); } .nav-btn-ghost:hover { color: #fff; - background: rgba(255, 255, 255, 0.05); + background: rgba(255, 255, 255, 0.06); + border-color: rgba(255, 255, 255, 0.05); } .nav-btn-primary { display: flex; align-items: center; - gap: 0.5rem; - padding: 0.6rem 1.5rem; - background: linear-gradient(135deg, #00f5d4 0%, #00c9a7 100%); - color: #000; + gap: 0.4rem; + padding: 0.5rem 1.35rem; + background: linear-gradient(135deg, #00f5d4 0%, #00ffd8 100%); + color: #0c0a0f !important; text-decoration: none; - font-size: 0.9rem; + font-size: 0.88rem; font-weight: 600; - border-radius: 8px; + border-radius: 99px; border: none; cursor: pointer; - transition: all 0.3s ease; - box-shadow: 0 2px 8px rgba(0, 245, 212, 0.2); + transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1); + box-shadow: 0 4px 12px rgba(0, 245, 212, 0.2); } .nav-btn-primary:hover { transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(0, 245, 212, 0.3); + box-shadow: 0 6px 16px rgba(0, 245, 212, 0.35); + background: linear-gradient(135deg, #00ffd8 0%, #00ffd8 100%); + color: #0c0a0f !important; +} + +.nav-btn-primary svg, +.nav-btn-primary:hover svg { + color: #0c0a0f !important; } .mobile-menu-btn { @@ -232,7 +278,7 @@ } .nav-container { - padding: 1rem 1.5rem; + padding: 0.6rem 1.25rem; } } @@ -384,11 +430,11 @@ /* HERO SPLIT LAYOUT */ .hero-section { position: relative; - padding: 2rem 1.5rem 4rem; + padding: 2rem 1.5rem 8rem; z-index: 1; min-height: calc(100vh - 80px); display: flex; - align-items: center; + align-items: flex-end; justify-content: center; margin-top: var(--nav-height, 80px); } @@ -401,11 +447,12 @@ } .hero-split { - display: grid; - grid-template-columns: 1fr 420px; - gap: 3rem; + display: flex; + flex-direction: column; align-items: center; - max-width: 1200px; + text-align: center; + gap: 2rem; + max-width: 900px; width: 100%; position: relative; z-index: 1; @@ -414,32 +461,37 @@ .hero-split-left { display: flex; flex-direction: column; - align-items: flex-start; - text-align: left; + align-items: center; + text-align: center; + width: 100%; } .hero-split-left .status-pill { - margin-left: 0; + margin-left: auto; + margin-right: auto; } .hero-split-left .hero-heading { - margin-left: 0; - margin-right: 0; - text-align: left; - font-size: clamp(2rem, 3.5vw, 3rem); + margin-left: auto; + margin-right: auto; + text-align: center; + font-size: clamp(2.5rem, 5.5vw, 4.25rem); + line-height: 1.15; + max-width: 850px; } .hero-split-left .hero-sub { - margin-left: 0; - margin-right: 0; - text-align: left; - max-width: 520px; + margin-left: auto; + margin-right: auto; + text-align: center; + max-width: 620px; + font-size: 1.15rem; } .hero-split-left .hero-flow { - margin-top: 2rem; - justify-content: flex-start; - max-width: 550px; + margin-top: 2.5rem; + justify-content: center; + max-width: 700px; padding: 0; } @@ -1931,7 +1983,10 @@ left: 0; right: 0; bottom: 0; - background: #050505; + background: rgba(8, 6, 12, 0.85); + backdrop-filter: blur(30px); + -webkit-backdrop-filter: blur(30px); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); z-index: 49; padding-top: 100px; display: flex; @@ -1939,7 +1994,7 @@ align-items: center; gap: 2rem; transform: translateY(-100%); - transition: transform 0.3s ease-in-out; + transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1); } .mobile-menu-overlay.open { @@ -2731,15 +2786,13 @@ pointer-events: none; } -.service-card:hover .service-card-glow { +.magic-bento-card:hover .service-card-glow { opacity: 1; } -.service-card:hover { - transform: translateY(-6px); - border-color: var(--accent-color); - background: rgba(255, 255, 255, 0.035); - box-shadow: 0 20px 40px -15px rgba(0, 0, 0, 0.8), 0 0 25px -5px var(--accent-glow); +.magic-bento-card:hover { + border-color: rgba(0, 245, 212, 0.3); + background: rgba(0, 245, 212, 0.04); } .service-card-header { @@ -2762,7 +2815,7 @@ transition: all 0.3s ease; } -.service-card:hover .service-icon-wrap { +.magic-bento-card:hover .service-icon-wrap { background: var(--accent-glow); border-color: var(--accent-color); transform: scale(1.08); @@ -2800,7 +2853,7 @@ transition: all 0.4s ease; } -.service-card:hover .service-card-visual-container { +.magic-bento-card:hover .service-card-visual-container { border-color: rgba(255, 255, 255, 0.08); background: rgba(0, 0, 0, 0.6); } @@ -2831,7 +2884,7 @@ position: relative; } -.service-card:hover .auth-visual .mini-auth-box { +.magic-bento-card:hover .auth-visual .mini-auth-box { transform: translateY(-3px) scale(1.02); border-color: rgba(0, 245, 212, 0.3); } @@ -2904,7 +2957,7 @@ transition: all 0.3s ease; } -.service-card:hover .mini-auth-btn-primary { +.magic-bento-card:hover .mini-auth-btn-primary { animation: auth-btn-click 1.6s ease forwards; } @@ -2923,14 +2976,10 @@ box-shadow: 0 0 5px rgba(0, 245, 212, 0.4); } 58% { - background: #00e676; - transform: scale(1.02); - box-shadow: 0 0 15px rgba(0, 230, 118, 0.6); + background: #00f5d4; transform: scale(1.02); box-shadow: 0 0 15px rgba(0, 245, 212, 0.6); } 100% { - background: #00e676; - transform: scale(1); - box-shadow: 0 0 15px rgba(0, 230, 118, 0.6); + background: #00f5d4; transform: scale(1); box-shadow: 0 0 15px rgba(0, 245, 212, 0.6); } } @@ -2953,7 +3002,7 @@ color: #aaa; } -.service-card:hover .mini-provider-btn { +.magic-bento-card:hover .mini-provider-btn { border-color: rgba(255, 255, 255, 0.15); color: #fff; } @@ -2969,7 +3018,7 @@ z-index: 10; } -.service-card:hover .mini-cursor { +.magic-bento-card:hover .mini-cursor { opacity: 1; transform: translate(-30px, -28px); } @@ -2989,9 +3038,9 @@ transition: all 0.3s ease; } -.service-card:hover .db-visual .mini-db-code { - border-color: rgba(255, 189, 46, 0.4); - box-shadow: 0 0 15px rgba(255, 189, 46, 0.15); +.magic-bento-card:hover .db-visual .mini-db-code { + border-color: rgba(0, 245, 212, 0.4); + box-shadow: 0 0 15px rgba(0, 245, 212, 0.15); } .code-key { color: #c792ea; } @@ -3004,7 +3053,7 @@ transition: opacity 0.4s ease; } -.service-card:hover .code-json { +.magic-bento-card:hover .code-json { opacity: 1; transition-delay: 0.3s; } @@ -3015,104 +3064,203 @@ .json-bool { color: #ff5370; } .json-str { color: #c3e88d; } -/* Storage Visual */ -.storage-visual .mini-storage-card { - width: 190px; +/* Storage Visual - Mini File Manager Panel */ +.storage-visual .mini-storage-panel { + width: 200px; background: rgba(255, 255, 255, 0.02); - border: 1px solid rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.07); border-radius: 10px; - padding: 0.75rem; + overflow: hidden; + box-shadow: 0 8px 20px -5px rgba(0,0,0,0.5); + transition: all 0.4s ease; +} + +.magic-bento-card:hover .mini-storage-panel { + border-color: rgba(0, 245, 212, 0.2); + box-shadow: 0 8px 25px -5px rgba(0,0,0,0.6), 0 0 10px rgba(0,245,212,0.06); +} + +.storage-panel-header { display: flex; - flex-direction: column; - gap: 0.6rem; - transition: all 0.3s ease; + align-items: center; + gap: 5px; + padding: 0.4rem 0.6rem; + background: rgba(255, 255, 255, 0.025); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); } -.service-card:hover .mini-storage-card { - border-color: rgba(64, 158, 255, 0.3); - transform: scale(1.02); +.storage-header-icon { color: #00f5d4; flex-shrink: 0; } + +.storage-bucket-name { + font-size: 9px; + font-weight: 600; + color: #ddd; + font-family: 'Courier New', Courier, monospace; +} + +.storage-file-count { + margin-left: auto; + font-size: 7px; + color: #666; + background: rgba(255,255,255,0.04); + padding: 1px 5px; + border-radius: 3px; +} + +.storage-file-list { + display: flex; + flex-direction: column; + padding: 0.35rem 0; } -.mini-storage-file { +.storage-file-row { display: flex; align-items: center; - gap: 0.5rem; + gap: 6px; + padding: 0.3rem 0.6rem; + transition: all 0.3s ease; + opacity: 0.6; + transform: translateX(-4px); } -.mini-storage-file .file-icon { - color: #409EFF; +.magic-bento-card:hover .storage-file-row { + opacity: 1; + transform: translateX(0); } -.mini-storage-file .file-details { +.magic-bento-card:hover .storage-file-row:nth-child(1) { transition-delay: 0.05s; } +.magic-bento-card:hover .storage-file-row:nth-child(2) { transition-delay: 0.15s; } +.magic-bento-card:hover .storage-file-row:nth-child(3) { transition-delay: 0.25s; } + +.storage-file-row:not(:last-child) { + border-bottom: 1px solid rgba(255, 255, 255, 0.03); +} + +.storage-file-type { + font-size: 6px; + font-weight: 700; + letter-spacing: 0.04em; + padding: 2px 4px; + border-radius: 3px; + flex-shrink: 0; + text-align: center; + min-width: 22px; +} + +.storage-file-type.img { + background: rgba(100, 210, 255, 0.12); + color: #64d2ff; + border: 1px solid rgba(100, 210, 255, 0.15); +} + +.storage-file-type.doc { + background: rgba(255, 189, 46, 0.12); + color: #ffbd2e; + border: 1px solid rgba(255, 189, 46, 0.15); +} + +.storage-file-type.vid { + background: rgba(0, 245, 212, 0.1); + color: #00f5d4; + border: 1px solid rgba(0, 245, 212, 0.15); +} + +.storage-file-info { display: flex; flex-direction: column; + flex: 1; + min-width: 0; } -.mini-storage-file .file-name { - font-size: 10px; +.storage-fname { + font-size: 9px; font-weight: 500; - color: #fff; + color: #ccc; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -.file-status-container { +.storage-fsize { font-size: 7px; color: #555; } + +.storage-check { + color: rgba(0, 245, 212, 0.35); + flex-shrink: 0; + transition: color 0.3s ease; +} + +.magic-bento-card:hover .storage-check { + color: rgba(0, 245, 212, 0.8); +} + +.storage-upload-status { position: relative; - height: 12px; - overflow: hidden; + height: 10px; display: flex; align-items: center; + overflow: hidden; } -.file-size-uploading { - font-size: 8px; +.storage-uploading-text { + font-size: 7px; color: #888; position: absolute; - top: 0; left: 0; opacity: 1; transition: opacity 0.3s ease, transform 0.3s ease; } -.file-size-complete { - font-size: 8px; - color: #00e676; +.storage-done-text { + font-size: 7px; + color: #00f5d4; position: absolute; - top: 0; left: 0; opacity: 0; - transform: translateY(10px); + transform: translateY(8px); transition: opacity 0.3s ease, transform 0.3s ease; } -.service-card:hover .file-size-uploading { +.magic-bento-card:hover .storage-uploading-text { opacity: 0; - transform: translateY(-10px); - transition-delay: 1.2s; + transform: translateY(-8px); + transition-delay: 1.4s; } -.service-card:hover .file-size-complete { +.magic-bento-card:hover .storage-done-text { opacity: 1; transform: translateY(0); - transition-delay: 1.2s; + transition-delay: 1.4s; } -.mini-progress-bar { - height: 4px; - background: rgba(255, 255, 255, 0.05); - border-radius: 2px; - overflow: hidden; +.storage-upload-ring { + flex-shrink: 0; + opacity: 0.3; + transition: opacity 0.3s ease; +} + +.magic-bento-card:hover .storage-upload-ring { opacity: 1; } + +.storage-ring-progress { + transition: stroke-dashoffset 1.5s cubic-bezier(0.4, 0, 0.2, 1) 0.3s; + transform: rotate(-90deg); + transform-origin: center; +} + +.magic-bento-card:hover .storage-ring-progress { stroke-dashoffset: 0; } + +.storage-bar-track { + height: 3px; + background: rgba(255, 255, 255, 0.04); } -.mini-progress-fill { +.storage-bar-fill { height: 100%; width: 0%; - background: linear-gradient(90deg, #409EFF, #00f5d4); - border-radius: 2px; - transition: width 1.5s cubic-bezier(0.4, 0, 0.2, 1); + background: linear-gradient(90deg, rgba(0, 245, 212, 0.2), #00f5d4); + transition: width 1.6s cubic-bezier(0.4, 0, 0.2, 1) 0.2s; } -.service-card:hover .mini-progress-fill { - width: 100%; -} +.magic-bento-card:hover .storage-bar-fill { width: 72%; } /* Mail Visual */ .mail-visual .mini-mail-envelope { @@ -3125,8 +3273,8 @@ transition: all 0.3s ease; } -.service-card:hover .mini-mail-envelope { - border-color: rgba(0, 230, 118, 0.3); +.magic-bento-card:hover .mini-mail-envelope { + border-color: rgba(0, 245, 212, 0.3); transform: translateY(-3px); } @@ -3142,7 +3290,7 @@ } .mail-badge-icon { - color: #00e676; + color: #00f5d4; } .mail-content { @@ -3161,10 +3309,10 @@ .mail-bar-btn { height: 18px; border-radius: 4px; - background: rgba(0, 230, 118, 0.15); - border: 1px solid rgba(0, 230, 118, 0.3); + background: rgba(0, 245, 212, 0.15); + border: 1px solid rgba(0, 245, 212, 0.3); font-size: 8px; - color: #00e676; + color: #00f5d4; font-weight: 600; display: flex; align-items: center; @@ -3173,10 +3321,10 @@ transition: all 0.3s ease; } -.service-card:hover .mail-bar-btn { - background: #00e676; +.magic-bento-card:hover .mail-bar-btn { + background: #00f5d4; color: #000; - border-color: #00e676; + border-color: #00f5d4; transition-delay: 0.8s; } @@ -3201,7 +3349,7 @@ .mail-status-sent { font-size: 8px; - color: #00e676; + color: #00f5d4; position: absolute; left: 8px; opacity: 0; @@ -3209,13 +3357,13 @@ transition: opacity 0.3s ease, transform 0.3s ease; } -.service-card:hover .mail-status-sending { +.magic-bento-card:hover .mail-status-sending { opacity: 0; transform: translateY(-10px); transition-delay: 0.8s; } -.service-card:hover .mail-status-sent { +.magic-bento-card:hover .mail-status-sent { opacity: 1; transform: translateY(0); transition-delay: 0.8s; @@ -3230,9 +3378,9 @@ border-radius: 4px; font-size: 7px; font-weight: 700; - color: #a855f7; - background: rgba(168, 85, 247, 0.15); - border: 1px solid rgba(168, 85, 247, 0.3); + color: #00f5d4; + background: rgba(0, 245, 212, 0.15); + border: 1px solid rgba(0, 245, 212, 0.3); letter-spacing: 0.05em; } @@ -3246,7 +3394,7 @@ transition: all 0.3s ease; } -.service-card:hover .policy-editor { +.magic-bento-card:hover .policy-editor { color: #b4b4b4; } @@ -3259,8 +3407,8 @@ padding-left: 10px; } -.service-card:hover .policy-line.indent { - background: rgba(168, 85, 247, 0.06); +.magic-bento-card:hover .policy-line.indent { + background: rgba(0, 245, 212, 0.06); border-radius: 2px; } @@ -3284,9 +3432,9 @@ transition: all 0.3s ease; } -.service-card:hover .mini-realtime-stage { - border-color: rgba(255, 95, 86, 0.3); - box-shadow: 0 0 15px rgba(255, 95, 86, 0.15); +.magic-bento-card:hover .mini-realtime-stage { + border-color: rgba(0, 245, 212, 0.3); + box-shadow: 0 0 15px rgba(0, 245, 212, 0.15); } .realtime-node { @@ -3304,7 +3452,7 @@ font-size: 7px; } -.service-card:hover .realtime-node { +.magic-bento-card:hover .realtime-node { color: #fff; } @@ -3323,11 +3471,11 @@ left: 0; width: 25px; height: 2px; - background: linear-gradient(90deg, transparent, #ff5f56, transparent); + background: linear-gradient(90deg, transparent, #00f5d4, transparent); opacity: 0; } -.service-card:hover .realtime-pulse { +.magic-bento-card:hover .realtime-pulse { opacity: 1; animation: realtime-packet-pulse 1.2s infinite linear; } @@ -3349,9 +3497,9 @@ transition: all 0.3s ease; } -.service-card:hover .realtime-status-badge { - color: #ff5f56; - text-shadow: 0 0 4px rgba(255, 95, 86, 0.6); +.magic-bento-card:hover .realtime-status-badge { + color: #00f5d4; + text-shadow: 0 0 4px rgba(0, 245, 212, 0.6); } /* urBackend Studio Interactive Window in Infrastructure */ diff --git a/apps/web-dashboard/src/pages/Pricing.jsx b/apps/web-dashboard/src/pages/Pricing.jsx index d3148d6b8..2b6512d9d 100644 --- a/apps/web-dashboard/src/pages/Pricing.jsx +++ b/apps/web-dashboard/src/pages/Pricing.jsx @@ -1,7 +1,19 @@ -import { Link } from 'react-router-dom'; -import { Check } from 'lucide-react'; +import { useState, useEffect } from 'react'; +import { Link, useNavigate, useLocation } from 'react-router-dom'; +import { useAuth } from '../context/AuthContext'; +import { + Check, + Zap, + Box, + Terminal, + ArrowRight, + Activity, + Menu, + X +} from 'lucide-react'; import Footer from '../components/Layout/Footer'; import './Pricing.css'; +import './LandingPage/style.css'; const FREE_FEATURES = [ '1 Project', @@ -30,21 +42,116 @@ const PRO_FEATURES = [ 'Priority support', ]; +const NAV_ITEMS = [ + { label: 'Features', href: '/#client-services', icon: Zap }, + { label: 'Use Cases', href: '/#use-cases', icon: Box }, + { label: 'Pricing', href: '/pricing', icon: Check }, + { label: 'Docs', href: 'https://docs.ub.bitbros.in', icon: Terminal, external: true } +]; + export default function Pricing() { + const navigate = useNavigate(); + const location = useLocation(); + const { isAuthenticated } = useAuth(); + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + const [scrolled, setScrolled] = useState(false); + + useEffect(() => { + const handleScroll = () => { + setScrolled(window.scrollY > 20); + }; + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + return (
-
-
- - urBackend - -
- Docs - Log in - Get Started +
+ setIsMobileMenuOpen(false)} style={{ fontSize: '1.5rem', fontWeight: 700, color: '#fff', textDecoration: 'none' }}>Features + setIsMobileMenuOpen(false)} style={{ fontSize: '1.5rem', fontWeight: 700, color: '#fff', textDecoration: 'none' }}>Use Cases + setIsMobileMenuOpen(false)} style={{ fontSize: '1.5rem', fontWeight: 700, color: '#fff', textDecoration: 'none' }}>Pricing + setIsMobileMenuOpen(false)} style={{ fontSize: '1.5rem', fontWeight: 700, color: '#fff', textDecoration: 'none' }}>Docs +
+ {isAuthenticated ? ( + + ) : ( + <> + setIsMobileMenuOpen(false)} style={{ fontSize: '1.2rem', fontWeight: 500, color: '#aaa', textDecoration: 'none' }}>Log in + setIsMobileMenuOpen(false)} className="btn btn-primary" style={{ fontWeight: 600, padding: '12px 30px', width: '200px', textAlign: 'center' }}>Start for Free + + )} +
+ +
+
diff --git a/package-lock.json b/package-lock.json index 76c104f74..2586794be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1151,7 +1151,9 @@ "@tanstack/react-table": "^8.21.3", "axios": "^1.13.2", "framer-motion": "^12.35.0", + "gsap": "^3.15.0", "lucide-react": "^0.554.0", + "postprocessing": "^6.39.1", "react": "^19.2.0", "react-dom": "^19.2.0", "react-hot-toast": "^2.6.0", @@ -1159,7 +1161,8 @@ "react-router-dom": "^7.9.6", "recharts": "^2.13.0", "remark-gfm": "^4.0.1", - "tailwindcss": "^4.1.18" + "tailwindcss": "^4.1.18", + "three": "^0.184.0" }, "devDependencies": { "@eslint/js": "^9.39.1", @@ -2909,7 +2912,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2943,7 +2945,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2977,7 +2978,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3043,7 +3043,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6876,7 +6875,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6893,7 +6891,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6910,7 +6907,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6927,7 +6923,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6944,7 +6939,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6961,7 +6955,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6978,7 +6971,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6995,7 +6987,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7012,7 +7003,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7029,7 +7019,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7046,7 +7035,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7063,7 +7051,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7080,7 +7067,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7097,7 +7083,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7114,7 +7099,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7131,7 +7115,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7148,7 +7131,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7165,7 +7147,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7182,7 +7163,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7199,7 +7179,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7216,7 +7195,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7233,7 +7211,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -8192,6 +8169,12 @@ "version": "4.2.11", "license": "ISC" }, + "node_modules/gsap": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz", + "integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==", + "license": "Standard 'no charge' license: https://gsap.com/standard-license." + }, "node_modules/has-bigints": { "version": "1.1.0", "dev": true, @@ -11839,6 +11822,15 @@ } } }, + "node_modules/postprocessing": { + "version": "6.39.1", + "resolved": "https://registry.npmjs.org/postprocessing/-/postprocessing-6.39.1.tgz", + "integrity": "sha512-R2dG2zy+BAx3USl5EHw+PvnrlbT5PKnZVp3se0HCR0pWH8WQdh742yNG4YWOsq6c0bFpffk0Gd2RqPeoP/wKng==", + "license": "Zlib", + "peerDependencies": { + "three": ">= 0.168.0 < 0.185.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "dev": true, @@ -13353,6 +13345,12 @@ "node": ">=0.8" } }, + "node_modules/three": { + "version": "0.184.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.184.0.tgz", + "integrity": "sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg==", + "license": "MIT" + }, "node_modules/tiny-invariant": { "version": "1.3.3", "license": "MIT" From 5fd4dc39496d8594bfbdff7add0aa01b911aa951 Mon Sep 17 00:00:00 2001 From: Siddh2024 Date: Wed, 17 Jun 2026 16:20:13 +0530 Subject: [PATCH 3/3] fix: resolve CodeRabbit review comments - Fix Stylelint declaration-empty-line-before in BorderGlow.css - Add animation cancellation cleanup to BorderGlow.jsx - Scope canvas selector to #lights in Hyperspeed.css - Guard async asset-init against unmount race in Hyperspeed.jsx - Scope :root tokens to .bento-section in MagicBento.css - Wire ParticleCard props in MagicBento.jsx - Add ARIA attributes and extract inline styles in Pricing.jsx/css --- .../src/components/BorderGlow/BorderGlow.css | 4 -- .../src/components/BorderGlow/BorderGlow.jsx | 32 +++++++--- .../src/components/Hyperspeed/Hyperspeed.css | 2 +- .../src/components/Hyperspeed/Hyperspeed.jsx | 8 ++- .../src/components/MagicBento/MagicBento.css | 3 +- .../src/components/MagicBento/MagicBento.jsx | 64 +++++++++++-------- apps/web-dashboard/src/pages/Pricing.css | 32 ++++++++++ apps/web-dashboard/src/pages/Pricing.jsx | 26 +++++--- 8 files changed, 116 insertions(+), 55 deletions(-) diff --git a/apps/web-dashboard/src/components/BorderGlow/BorderGlow.css b/apps/web-dashboard/src/components/BorderGlow/BorderGlow.css index 5e1bffea4..9ef6848f4 100644 --- a/apps/web-dashboard/src/components/BorderGlow/BorderGlow.css +++ b/apps/web-dashboard/src/components/BorderGlow/BorderGlow.css @@ -57,9 +57,7 @@ var(--gradient-six, radial-gradient(at 82% 18%, hsla(52, 100%, 65%, 1) 0px, transparent 50%)) border-box, var(--gradient-seven, radial-gradient(at 51% 4%, hsla(12, 100%, 72%, 1) 0px, transparent 50%)) border-box, var(--gradient-base, linear-gradient(#c299ff 0 100%)) border-box; - opacity: calc((var(--edge-proximity) - var(--color-sensitivity)) / (100 - var(--color-sensitivity))); - mask-image: conic-gradient( from var(--cursor-angle) at center, @@ -75,12 +73,10 @@ inset: calc(var(--glow-padding) * -1); pointer-events: none; z-index: 1; - mask-image: conic-gradient( from var(--cursor-angle) at center, black 2.5%, transparent 10%, transparent 90%, black 97.5% ); - opacity: calc((var(--edge-proximity) - var(--edge-sensitivity)) / (100 - var(--edge-sensitivity))); mix-blend-mode: plus-lighter; } diff --git a/apps/web-dashboard/src/components/BorderGlow/BorderGlow.jsx b/apps/web-dashboard/src/components/BorderGlow/BorderGlow.jsx index e8bdf30dc..f901ccd3c 100644 --- a/apps/web-dashboard/src/components/BorderGlow/BorderGlow.jsx +++ b/apps/web-dashboard/src/components/BorderGlow/BorderGlow.jsx @@ -38,14 +38,22 @@ function easeInCubic(x) { return x * x * x; } function animateValue({ start = 0, end = 100, duration = 1000, delay = 0, ease = easeOutCubic, onUpdate, onEnd }) { const t0 = performance.now() + delay; + let rafId = null; function tick() { const elapsed = performance.now() - t0; const t = Math.min(elapsed / duration, 1); onUpdate(start + (end - start) * ease(t)); - if (t < 1) requestAnimationFrame(tick); + if (t < 1) rafId = requestAnimationFrame(tick); else if (onEnd) onEnd(); } - setTimeout(() => requestAnimationFrame(tick), delay); + const timeoutId = setTimeout(() => { + rafId = requestAnimationFrame(tick); + }, delay); + + return () => { + clearTimeout(timeoutId); + if (rafId != null) cancelAnimationFrame(rafId); + }; } const BorderGlow = ({ @@ -114,17 +122,23 @@ const BorderGlow = ({ card.classList.add('sweep-active'); card.style.setProperty('--cursor-angle', `${angleStart}deg`); - animateValue({ duration: 500, onUpdate: v => card.style.setProperty('--edge-proximity', v) }); - animateValue({ ease: easeInCubic, duration: 1500, end: 50, onUpdate: v => { + const stops = []; + stops.push(animateValue({ duration: 500, onUpdate: v => card.style.setProperty('--edge-proximity', v) })); + stops.push(animateValue({ ease: easeInCubic, duration: 1500, end: 50, onUpdate: v => { card.style.setProperty('--cursor-angle', `${(angleEnd - angleStart) * (v / 100) + angleStart}deg`); - }}); - animateValue({ ease: easeOutCubic, delay: 1500, duration: 2250, start: 50, end: 100, onUpdate: v => { + }})); + stops.push(animateValue({ ease: easeOutCubic, delay: 1500, duration: 2250, start: 50, end: 100, onUpdate: v => { card.style.setProperty('--cursor-angle', `${(angleEnd - angleStart) * (v / 100) + angleStart}deg`); - }}); - animateValue({ ease: easeInCubic, delay: 2500, duration: 1500, start: 100, end: 0, + }})); + stops.push(animateValue({ ease: easeInCubic, delay: 2500, duration: 1500, start: 100, end: 0, onUpdate: v => card.style.setProperty('--edge-proximity', v), onEnd: () => card.classList.remove('sweep-active'), - }); + })); + + return () => { + stops.forEach(stop => stop?.()); + card.classList.remove('sweep-active'); + }; }, [animated]); const glowVars = buildGlowVars(glowColor, glowIntensity); diff --git a/apps/web-dashboard/src/components/Hyperspeed/Hyperspeed.css b/apps/web-dashboard/src/components/Hyperspeed/Hyperspeed.css index b6210354e..b76bf9b0f 100644 --- a/apps/web-dashboard/src/components/Hyperspeed/Hyperspeed.css +++ b/apps/web-dashboard/src/components/Hyperspeed/Hyperspeed.css @@ -10,7 +10,7 @@ opacity: 0.45; /* Soften the light beams for better foreground text readability */ } -canvas { +#lights canvas { width: 100%; height: 100%; display: block; diff --git a/apps/web-dashboard/src/components/Hyperspeed/Hyperspeed.jsx b/apps/web-dashboard/src/components/Hyperspeed/Hyperspeed.jsx index 653017130..093068875 100644 --- a/apps/web-dashboard/src/components/Hyperspeed/Hyperspeed.jsx +++ b/apps/web-dashboard/src/components/Hyperspeed/Hyperspeed.jsx @@ -1167,9 +1167,15 @@ const Hyperspeed = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => { const myApp = new App(container, options); appRef.current = myApp; - myApp.loadAssets().then(myApp.init); + let cancelled = false; + myApp.loadAssets().then(() => { + if (!cancelled && !myApp.disposed) { + myApp.init(); + } + }); return () => { + cancelled = true; if (appRef.current) { appRef.current.dispose(); appRef.current = null; diff --git a/apps/web-dashboard/src/components/MagicBento/MagicBento.css b/apps/web-dashboard/src/components/MagicBento/MagicBento.css index 84f28a379..88a7b8b85 100644 --- a/apps/web-dashboard/src/components/MagicBento/MagicBento.css +++ b/apps/web-dashboard/src/components/MagicBento/MagicBento.css @@ -1,4 +1,4 @@ -:root { +.bento-section { --hue: 27; --sat: 69%; --white: hsl(0, 0%, 100%); @@ -7,7 +7,6 @@ --purple-border: rgba(0, 245, 212, 0.8); --border-color: #2F293A; --background-dark: #120F17; - color-scheme: light dark; } .card-grid { diff --git a/apps/web-dashboard/src/components/MagicBento/MagicBento.jsx b/apps/web-dashboard/src/components/MagicBento/MagicBento.jsx index ba1de2dae..2d35c8f9f 100644 --- a/apps/web-dashboard/src/components/MagicBento/MagicBento.jsx +++ b/apps/web-dashboard/src/components/MagicBento/MagicBento.jsx @@ -475,16 +475,14 @@ const useMobileDetection = () => { const MagicBento = ({ cards, textAutoHide = true, - // enableStars = true, enableSpotlight = true, - // enableBorderGlow = true, disableAnimations = false, spotlightRadius = DEFAULT_SPOTLIGHT_RADIUS, - // particleCount = DEFAULT_PARTICLE_COUNT, - // enableTilt = false, + particleCount = DEFAULT_PARTICLE_COUNT, + enableTilt = false, glowColor = DEFAULT_GLOW_COLOR, - // clickEffect = true, - // enableMagnetism = true + clickEffect = true, + enableMagnetism = true }) => { const gridRef = useRef(null); const isMobile = useMobileDetection(); @@ -512,32 +510,42 @@ const MagicBento = ({ const baseClassName = `magic-bento-card ${textAutoHide ? 'magic-bento-card--text-autohide' : ''}`; return ( - -
-
{label}
-
- {visual && ( -
- {visual} + +
+
{label}
+
+ {visual && ( +
+ {visual} +
+ )} +
+

{title}

+

{description}

- )} -
-

{title}

-

{description}

-
-
+ + ); })} diff --git a/apps/web-dashboard/src/pages/Pricing.css b/apps/web-dashboard/src/pages/Pricing.css index 25f4be397..b9872c799 100644 --- a/apps/web-dashboard/src/pages/Pricing.css +++ b/apps/web-dashboard/src/pages/Pricing.css @@ -182,3 +182,35 @@ gap: 1rem; } } + +.mobile-menu-link { + font-size: 1.5rem; + font-weight: 700; + color: #fff; + text-decoration: none; +} + +.mobile-menu-sub-link { + font-size: 1.2rem; + font-weight: 500; + color: #aaa; + text-decoration: none; +} + +.mobile-menu-divider { + height: 1px; + width: 60px; + background: #333; + margin: 10px 0; +} + +.mobile-menu-action { + font-weight: 600; + width: 200px; + padding: 12px; +} + +.mobile-menu-action.btn-primary { + text-align: center; + padding: 12px 30px; +} diff --git a/apps/web-dashboard/src/pages/Pricing.jsx b/apps/web-dashboard/src/pages/Pricing.jsx index 2b6512d9d..29d40827a 100644 --- a/apps/web-dashboard/src/pages/Pricing.jsx +++ b/apps/web-dashboard/src/pages/Pricing.jsx @@ -66,20 +66,20 @@ export default function Pricing() { return (
-
- setIsMobileMenuOpen(false)} style={{ fontSize: '1.5rem', fontWeight: 700, color: '#fff', textDecoration: 'none' }}>Features - setIsMobileMenuOpen(false)} style={{ fontSize: '1.5rem', fontWeight: 700, color: '#fff', textDecoration: 'none' }}>Use Cases - setIsMobileMenuOpen(false)} style={{ fontSize: '1.5rem', fontWeight: 700, color: '#fff', textDecoration: 'none' }}>Pricing - setIsMobileMenuOpen(false)} style={{ fontSize: '1.5rem', fontWeight: 700, color: '#fff', textDecoration: 'none' }}>Docs -
+
+ setIsMobileMenuOpen(false)}>Features + setIsMobileMenuOpen(false)}>Use Cases + setIsMobileMenuOpen(false)}>Pricing + setIsMobileMenuOpen(false)}>Docs +
{isAuthenticated ? ( - ) : ( <> - setIsMobileMenuOpen(false)} style={{ fontSize: '1.2rem', fontWeight: 500, color: '#aaa', textDecoration: 'none' }}>Log in - setIsMobileMenuOpen(false)} className="btn btn-primary" style={{ fontWeight: 600, padding: '12px 30px', width: '200px', textAlign: 'center' }}>Start for Free + setIsMobileMenuOpen(false)}>Log in + setIsMobileMenuOpen(false)}>Start for Free )}
@@ -146,7 +146,13 @@ export default function Pricing() { )} -