diff --git a/website/src/components/AIChatbot.tsx b/website/src/components/AIChatbot.tsx
new file mode 100644
index 00000000..dd3a4192
--- /dev/null
+++ b/website/src/components/AIChatbot.tsx
@@ -0,0 +1,674 @@
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+import {
+ Bot,
+ Send,
+ User,
+ X,
+ ThumbsUp,
+ ThumbsDown,
+ History,
+ Plus,
+ ChevronLeft,
+ ExternalLink,
+ Trash2,
+ MessageSquare,
+} from 'lucide-react';
+import { Button } from '@site/src/components/ui/button';
+import { Input } from '@site/src/components/ui/input';
+import { findFAQ, SUGGESTION_CHIPS } from '@site/src/data/faqs';
+import { cn, truncate } from '@site/src/lib/utils';
+
+interface Message {
+ id: string;
+ role: 'user' | 'assistant';
+ content: string;
+ sources?: Source[];
+}
+
+interface Source {
+ title: string;
+ url: string;
+ score?: number;
+}
+
+interface Thread {
+ id: string;
+ title: string;
+ messages: Message[];
+ createdAt: number;
+ updatedAt: number;
+}
+
+const STORAGE_KEY = 'xdc-rag-threads-v1';
+
+function generateId() {
+ return `${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
+}
+
+function makeWelcomeMessage(): Message {
+ return {
+ id: generateId(),
+ role: 'assistant',
+ content:
+ "Hi! I'm the XDC Docs assistant. Ask me anything about XDC Network, smart contracts, subnets, nodes, or wallets.",
+ };
+}
+
+function getSuggestedPrompts(path = '') {
+ if (path.startsWith('/smartcontract')) {
+ return [
+ 'How do I deploy a smart contract?',
+ 'Verify a contract on XDC',
+ 'Create an XRC20 token',
+ 'Gas optimization tips',
+ 'Hardhat setup guide',
+ 'Foundry vs Remix',
+ ];
+ }
+ if (path.startsWith('/subnet')) {
+ return [
+ 'How do I deploy a subnet?',
+ 'Subnet faucet setup',
+ 'What is SubSwap?',
+ 'Subnet checkpoints explained',
+ 'XDC Zero overview',
+ 'Subnet explorer setup',
+ ];
+ }
+ if (path.startsWith('/xdcchain')) {
+ return [
+ 'Run a masternode',
+ 'Masternode requirements',
+ 'Docker full node setup',
+ 'Validator handbook',
+ 'Slashing rules',
+ 'One-click installer',
+ ];
+ }
+ if (path.startsWith('/api')) {
+ return ['Mainnet RPC endpoints', 'Apothem testnet RPC', 'WebSocket API guide', 'JSON-RPC methods'];
+ }
+ if (path.startsWith('/enterprise')) {
+ return ['ISO 20022 integration', 'RWA tokenization', 'Private subnets', 'Trade finance use cases'];
+ }
+ if (path.startsWith('/security')) {
+ return ['Security best practices', 'Set up a multisig wallet', 'Key management guide', 'Report a vulnerability'];
+ }
+ if (path.startsWith('/learn')) {
+ return ['XDC architecture', 'What is XDPoS?', 'Gas fees explained', 'Blockchain basics'];
+ }
+ if (path.startsWith('/whitepaper')) {
+ return ['XDC whitepaper summary', 'XDPoS technical details'];
+ }
+ return SUGGESTION_CHIPS;
+}
+
+/** Minimal markdown → JSX: bold, inline code, lists, line breaks */
+function RenderMarkdown({ content }: { content: string }) {
+ const lines = content.split('\n');
+ return (
+ <>
+ {lines.map((line, li) => {
+ const isOl = /^\d+\.\s/.test(line);
+ const isUl = /^[-*]\s/.test(line);
+ const text = isOl ? line.replace(/^\d+\.\s/, '') : isUl ? line.replace(/^[-*]\s/, '') : line;
+ const parts = text.split(/(\*\*[^*]+\*\*|`[^`]+`)/g);
+ const rendered = parts.map((part, i) => {
+ if (part.startsWith('**') && part.endsWith('**')) {
+ return {part.slice(2, -2)};
+ }
+ if (part.startsWith('`') && part.endsWith('`')) {
+ return (
+
+ {part.slice(1, -1)}
+
+ );
+ }
+ return part;
+ });
+ if (isOl) return
{rendered};
+ if (isUl) return {rendered};
+ if (line === '') return
;
+ return {rendered};
+ })}
+ >
+ );
+}
+
+export function AIChatbot({
+ open,
+ onClose,
+ currentPath,
+}: {
+ open: boolean;
+ onClose: () => void;
+ currentPath?: string;
+}) {
+ const { siteConfig } = useDocusaurusContext();
+ const apiUrl = (siteConfig.customFields?.ragApiUrl as string) || 'http://localhost:3001';
+
+ const [threads, setThreads] = useState([]);
+ const [activeThreadId, setActiveThreadId] = useState(null);
+ const [view, setView] = useState<'chat' | 'history'>('chat');
+ const [input, setInput] = useState('');
+ const [isTyping, setIsTyping] = useState(false);
+ const [feedback, setFeedback] = useState<{ [key: string]: { rating?: 'up' | 'down'; comment?: string; submitted?: boolean } }>({});
+ const scrollRef = useRef(null);
+ const inputRef = useRef(null);
+
+ // Load threads from localStorage on mount.
+ useEffect(() => {
+ try {
+ const raw = localStorage.getItem(STORAGE_KEY);
+ if (raw) {
+ const parsed = JSON.parse(raw) as Thread[];
+ setThreads(parsed);
+ setActiveThreadId(parsed[0]?.id ?? null);
+ }
+ } catch {
+ // Ignore corrupt storage.
+ }
+ }, []);
+
+ // Create an initial thread if none exists.
+ useEffect(() => {
+ if (threads.length === 0) {
+ const thread: Thread = {
+ id: generateId(),
+ title: 'New conversation',
+ messages: [makeWelcomeMessage()],
+ createdAt: Date.now(),
+ updatedAt: Date.now(),
+ };
+ setThreads([thread]);
+ setActiveThreadId(thread.id);
+ }
+ }, [threads.length]);
+
+ // Persist threads.
+ useEffect(() => {
+ if (threads.length > 0) {
+ try {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(threads));
+ } catch {
+ // localStorage may be full/disabled.
+ }
+ }
+ }, [threads]);
+
+ // Focus / body scroll lock.
+ useEffect(() => {
+ if (open) {
+ document.body.style.overflow = 'hidden';
+ setTimeout(() => inputRef.current?.focus(), 100);
+ } else {
+ document.body.style.overflow = '';
+ }
+ return () => {
+ document.body.style.overflow = '';
+ };
+ }, [open]);
+
+ // Close on Escape.
+ useEffect(() => {
+ if (!open) return;
+ const handler = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') onClose();
+ };
+ document.addEventListener('keydown', handler);
+ return () => document.removeEventListener('keydown', handler);
+ }, [open, onClose]);
+
+ const activeThread = useMemo(
+ () => threads.find((t) => t.id === activeThreadId),
+ [threads, activeThreadId]
+ );
+
+ // Auto-scroll.
+ useEffect(() => {
+ scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' });
+ }, [activeThread?.messages, isTyping, view]);
+
+ const suggestedPrompts = useMemo(() => getSuggestedPrompts(currentPath), [currentPath]);
+
+ const updateThreadMessages = (threadId: string, updater: (msgs: Message[]) => Message[]) => {
+ setThreads((prev) =>
+ prev.map((t) =>
+ t.id === threadId
+ ? {
+ ...t,
+ messages: updater(t.messages),
+ updatedAt: Date.now(),
+ title: getThreadTitle(updater(t.messages), t.title),
+ }
+ : t
+ )
+ );
+ };
+
+ function getThreadTitle(messages: Message[], fallback: string) {
+ const firstUser = messages.find((m) => m.role === 'user');
+ if (firstUser) return truncate(firstUser.content, 32);
+ return fallback;
+ }
+
+ const handleSend = useCallback(
+ async (textOverride?: string) => {
+ const text = (textOverride ?? input).trim();
+ if (!text || !activeThread || isTyping) return;
+
+ const userMsg: Message = { id: generateId(), role: 'user', content: text };
+ updateThreadMessages(activeThread.id, (prev) => [...prev, userMsg]);
+ setInput('');
+ setIsTyping(true);
+
+ try {
+ const payload = activeThread.messages
+ .concat(userMsg)
+ .map((m) => ({ role: m.role, content: m.content }));
+ const res = await fetch(`${apiUrl}/api/chat`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ messages: payload, topK: 5 }),
+ });
+ if (!res.ok) throw new Error(`Server returned ${res.status}`);
+ const data = await res.json();
+ const assistantMsg: Message = {
+ id: generateId(),
+ role: 'assistant',
+ content: data.answer || 'No response generated.',
+ sources: data.sources || [],
+ };
+ updateThreadMessages(activeThread.id, (prev) => [...prev, assistantMsg]);
+ } catch (err) {
+ const fallbackMsg: Message = {
+ id: generateId(),
+ role: 'assistant',
+ content: "I couldn't reach the AI backend. Make sure the RAG API is running, or try rephrasing your question.",
+ };
+ updateThreadMessages(activeThread.id, (prev) => [...prev, fallbackMsg]);
+ } finally {
+ setIsTyping(false);
+ }
+ },
+ [input, activeThread, isTyping, apiUrl]
+ );
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ handleSend();
+ }
+ };
+
+ const startNewThread = () => {
+ const thread: Thread = {
+ id: generateId(),
+ title: 'New conversation',
+ messages: [makeWelcomeMessage()],
+ createdAt: Date.now(),
+ updatedAt: Date.now(),
+ };
+ setThreads((prev) => [thread, ...prev]);
+ setActiveThreadId(thread.id);
+ setView('chat');
+ inputRef.current?.focus();
+ };
+
+ const selectThread = (id: string) => {
+ setActiveThreadId(id);
+ setView('chat');
+ inputRef.current?.focus();
+ };
+
+ const clearAllHistory = () => {
+ if (confirm('Clear all chat history? This cannot be undone.')) {
+ localStorage.removeItem(STORAGE_KEY);
+ const thread: Thread = {
+ id: generateId(),
+ title: 'New conversation',
+ messages: [makeWelcomeMessage()],
+ createdAt: Date.now(),
+ updatedAt: Date.now(),
+ };
+ setThreads([thread]);
+ setActiveThreadId(thread.id);
+ setView('chat');
+ }
+ };
+
+ const submitFeedback = async (messageId: string, rating: 'up' | 'down') => {
+ const msg = activeThread?.messages.find((m) => m.id === messageId);
+ const comment = feedback[messageId]?.comment || '';
+ setFeedback((prev) => ({
+ ...prev,
+ [messageId]: { ...prev[messageId], rating, submitted: true },
+ }));
+ try {
+ await fetch(`${apiUrl}/api/feedback`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ messageId,
+ rating,
+ comment,
+ question: activeThread?.messages.find((m) => m.role === 'user')?.content || '',
+ answer: msg?.content || '',
+ sources: msg?.sources || [],
+ }),
+ });
+ } catch {
+ // Feedback is best-effort; keep UI responsive.
+ }
+ };
+
+ const showSuggestions =
+ activeThread &&
+ activeThread.messages.length === 1 &&
+ activeThread.messages[0].role === 'assistant' &&
+ !isTyping;
+
+ if (!open) return null;
+
+ return (
+
+ {/* Backdrop */}
+
+
+ {/* Modal */}
+
+ {/* Header */}
+
+
+
+
+
+
+
+ XDC Assistant
+
+
AI docs helper
+
+
+
+
+ AI
+
+
+
+
+
+
+
+ {/* History view */}
+ {view === 'history' ? (
+
+
+
+
+
+
+ {threads.length === 0 ? (
+
No conversation history yet.
+ ) : (
+
+ {threads.map((t) => (
+
+ ))}
+
+ )}
+
+
+ ) : (
+ <>
+ {/* Messages */}
+
+ {activeThread?.messages.map((msg) => (
+
+
+ {msg.role === 'user' ? : }
+
+
+
+
+
+
+ {/* Sources */}
+ {msg.role === 'assistant' && msg.sources && msg.sources.length > 0 && (
+
+ )}
+
+ {/* Feedback */}
+ {msg.role === 'assistant' && (
+
+ {!feedback[msg.id]?.submitted ? (
+
+
+ Was this helpful?
+
+
+
+ {feedback[msg.id]?.rating && (
+
+
+ setFeedback((prev) => ({
+ ...prev,
+ [msg.id]: { ...prev[msg.id], comment: e.target.value },
+ }))
+ }
+ onKeyDown={(e) => {
+ if (e.key === 'Enter')
+ submitFeedback(msg.id, feedback[msg.id].rating!);
+ }}
+ placeholder="Tell us more (optional)"
+ className="flex-1 rounded-md border border-border/70 bg-background px-2 py-1 text-xs placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring dark:border-white/[0.1]"
+ />
+
+
+ )}
+
+ ) : (
+
Thanks for your feedback!
+ )}
+
+ )}
+
+
+ ))}
+
+ {/* Typing indicator */}
+ {isTyping && (
+
+ )}
+
+
+ {/* Suggestion chips */}
+ {showSuggestions && (
+
+
+ Quick questions
+
+
+ {suggestedPrompts.map((chip) => (
+
+ ))}
+
+
+ )}
+
+ {/* Input area */}
+
+
+ setInput(e.target.value)}
+ onKeyDown={handleKeyDown}
+ placeholder="Ask about XDC Network..."
+ className="flex-1 rounded-full border-border/60 bg-muted/40 text-sm dark:border-border/30"
+ disabled={isTyping}
+ aria-label="Chat message"
+ />
+
+
+
+ >
+ )}
+
+
+);
+}
+
+export default AIChatbot;
diff --git a/website/src/components/AccessControlVisualizer.tsx b/website/src/components/AccessControlVisualizer.tsx
new file mode 100644
index 00000000..f10b9cdf
--- /dev/null
+++ b/website/src/components/AccessControlVisualizer.tsx
@@ -0,0 +1,85 @@
+import React, { useState } from 'react';
+import { Shield, AlertTriangle, Lock, UserCheck } from 'lucide-react';
+
+const roles = [
+ {
+ name: 'Owner',
+ icon: Shield,
+ color: 'bg-amber-500/10 text-amber-500',
+ permissions: ['Upgrade contract', 'Transfer ownership', 'Emergency pause'],
+ },
+ {
+ name: 'Admin',
+ icon: UserCheck,
+ color: 'bg-blue-500/10 text-blue-500',
+ permissions: ['Manage validators', 'Update parameters', 'Grant roles'],
+ },
+ {
+ name: 'Pauser',
+ icon: AlertTriangle,
+ color: 'bg-red-500/10 text-red-500',
+ permissions: ['Pause operations', 'Resume operations'],
+ },
+ {
+ name: 'Minter',
+ icon: Lock,
+ color: 'bg-emerald-500/10 text-emerald-500',
+ permissions: ['Mint tokens', 'Burn tokens'],
+ },
+];
+
+export function AccessControlVisualizer() {
+ const [activeRole, setActiveRole] = useState(roles[0]);
+
+ return (
+
+
+
+
+
Access Control Roles
+
+
+
+
+ {roles.map((role) => {
+ const Icon = role.icon;
+ return (
+
+ );
+ })}
+
+
+
+ {React.createElement(activeRole.icon, {
+ className: `h-5 w-5 ${activeRole.color.split(' ')[1]}`,
+ })}
+
{activeRole.name} Permissions
+
+
+ {activeRole.permissions.map((perm) => (
+ -
+
+ {perm}
+
+ ))}
+
+
+
+
+ );
+}
+
+export default AccessControlVisualizer;
diff --git a/website/src/components/AddressValidator.tsx b/website/src/components/AddressValidator.tsx
new file mode 100644
index 00000000..e4536331
--- /dev/null
+++ b/website/src/components/AddressValidator.tsx
@@ -0,0 +1,52 @@
+import React, { useState } from 'react';
+import { CheckCircle2, XCircle, Search } from 'lucide-react';
+
+export function AddressValidator() {
+ const [address, setAddress] = useState('xdc8ba9bc4ce7a37d8a84ac7f8');
+ const [isValid, setIsValid] = useState(false);
+
+ const validate = (value: string) => {
+ const regex = /^xdc[0-9a-fA-F]{40}$/;
+ setIsValid(regex.test(value));
+ setAddress(value);
+ };
+
+ return (
+
+
+
+
+
Address Validator
+
+
+
+
+
validate(e.target.value)}
+ placeholder="Enter xdc... address"
+ className="w-full rounded-lg border border-input bg-background px-3 py-2.5 pr-10 text-sm font-mono outline-none focus:ring-2 focus:ring-ring"
+ />
+
+ {isValid ? (
+
+ ) : (
+
+ )}
+
+
+
+ {isValid ? 'Valid XDC address format' : 'Invalid XDC address format'}
+
+
XDC addresses start with "xdc" followed by 40 hexadecimal characters.
+
+
+ );
+}
+
+export default AddressValidator;
diff --git a/website/src/components/AnimatedHero/index.tsx b/website/src/components/AnimatedHero/index.tsx
new file mode 100644
index 00000000..a7ca34aa
--- /dev/null
+++ b/website/src/components/AnimatedHero/index.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import styles from './styles.module.css';
+
+export default function AnimatedHero() {
+ return (
+
+
+
+ XDC Network
+
+ Documentation
+
+
+ Build the future of decentralized finance on the XDC Chain
+
+
+
+
+
+ );
+}
diff --git a/website/src/components/AnimatedHero/styles.module.css b/website/src/components/AnimatedHero/styles.module.css
new file mode 100644
index 00000000..0d4f57fb
--- /dev/null
+++ b/website/src/components/AnimatedHero/styles.module.css
@@ -0,0 +1,112 @@
+.hero {
+ position: relative;
+ min-height: 80vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ padding: 2rem;
+}
+
+.content {
+ position: relative;
+ z-index: 1;
+ text-align: center;
+ max-width: 800px;
+}
+
+.title {
+ font-size: 3.5rem;
+ font-weight: 800;
+ line-height: 1.1;
+ margin-bottom: 1rem;
+}
+
+.gradient {
+ background: linear-gradient(135deg, #2563eb, #06b6d4);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+ animation: gradient-shift 3s ease infinite;
+ background-size: 200% 200%;
+}
+
+@keyframes gradient-shift {
+ 0%, 100% { background-position: 0% 50%; }
+ 50% { background-position: 100% 50%; }
+}
+
+.subtitle {
+ font-size: 1.25rem;
+ opacity: 0.8;
+ margin-bottom: 2rem;
+}
+
+.cta {
+ display: flex;
+ gap: 1rem;
+ justify-content: center;
+}
+
+.primaryButton {
+ padding: 0.875rem 2rem;
+ border-radius: 12px;
+ background: linear-gradient(135deg, #2563eb, #1d4ed8);
+ color: white;
+ font-weight: 600;
+ text-decoration: none;
+ transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
+ transform-style: preserve-3d;
+}
+
+.primaryButton:hover {
+ transform: translateY(-2px) translateZ(10px);
+ box-shadow: 0 10px 40px rgba(37, 99, 235, 0.3);
+}
+
+.secondaryButton {
+ padding: 0.875rem 2rem;
+ border-radius: 12px;
+ border: 1px solid var(--ifm-color-emphasis-300);
+ color: var(--ifm-font-color-base);
+ font-weight: 600;
+ text-decoration: none;
+ transition: all 0.3s ease;
+ background: transparent;
+}
+
+.secondaryButton:hover {
+ background: var(--ifm-color-emphasis-100);
+ transform: translateY(-2px);
+}
+
+.grid {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ z-index: 0;
+ background:
+ repeating-linear-gradient(
+ 90deg,
+ var(--ifm-color-emphasis-200) 0px,
+ var(--ifm-color-emphasis-200) 1px,
+ transparent 1px,
+ transparent 100px
+ ),
+ repeating-linear-gradient(
+ 0deg,
+ var(--ifm-color-emphasis-200) 0px,
+ var(--ifm-color-emphasis-200) 1px,
+ transparent 1px,
+ transparent 100px
+ );
+ transform: perspective(500px) rotateX(60deg);
+ transform-origin: center top;
+ opacity: 0.2;
+ animation: grid-move 20s linear infinite;
+}
+
+@keyframes grid-move {
+ 0% { background-position: 0 0; }
+ 100% { background-position: 0 100px; }
+}
diff --git a/website/src/components/AnimatedSection.tsx b/website/src/components/AnimatedSection.tsx
new file mode 100644
index 00000000..aae15a0a
--- /dev/null
+++ b/website/src/components/AnimatedSection.tsx
@@ -0,0 +1,114 @@
+import React from 'react';
+import { motion, useReducedMotion } from 'framer-motion';
+import { cn } from '@site/src/lib/utils';
+
+interface AnimatedSectionProps {
+ children: React.ReactNode;
+ className?: string;
+ delay?: number;
+}
+
+export function AnimatedSection({ children, className, delay = 0 }: AnimatedSectionProps) {
+ const shouldReduceMotion = useReducedMotion();
+
+ return (
+
+ {children}
+
+ );
+}
+
+interface StaggerContainerProps {
+ children: React.ReactNode;
+ className?: string;
+ staggerDelay?: number;
+}
+
+export function StaggerContainer({ children, className, staggerDelay = 0.08 }: StaggerContainerProps) {
+ const shouldReduceMotion = useReducedMotion();
+
+ return (
+
+ {React.Children.map(children, (child) =>
+ React.isValidElement(child) ? (
+
+ {child}
+
+ ) : (
+ child
+ )
+ )}
+
+ );
+}
+
+interface FadeInProps {
+ children: React.ReactNode;
+ className?: string;
+ delay?: number;
+ direction?: 'up' | 'down' | 'left' | 'right';
+}
+
+export function FadeIn({ children, className, delay = 0, direction = 'up' }: FadeInProps) {
+ const shouldReduceMotion = useReducedMotion();
+
+ const directionMap = {
+ up: { y: 24 },
+ down: { y: -24 },
+ left: { x: 24 },
+ right: { x: -24 },
+ };
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/website/src/components/AntiGravityField.tsx b/website/src/components/AntiGravityField.tsx
new file mode 100644
index 00000000..427265cd
--- /dev/null
+++ b/website/src/components/AntiGravityField.tsx
@@ -0,0 +1,302 @@
+import React, { useRef, useMemo, useEffect, useState } from 'react';
+import { Canvas, useFrame, useThree } from '@react-three/fiber';
+import * as THREE from 'three';
+
+/**
+ * AntiGravity-style interactive particle mesh.
+ * Thousands of particles floating in 3D space, connected by proximity lines,
+ * with mouse repulsion and gentle ambient drift.
+ * Inspired by premium fintech landing pages (antigravity, stripe, etc).
+ */
+
+const PARTICLE_COUNT_DESKTOP = 2200;
+const PARTICLE_COUNT_MOBILE = 900;
+const CONNECTION_DISTANCE = 2.8;
+const MAX_CONNECTIONS = 3;
+const MOUSE_RADIUS = 4.5;
+const MOUSE_FORCE = 0.08;
+
+interface ParticleFieldProps {
+ reducedMotion: boolean;
+ count: number;
+}
+
+function ParticleField({ reducedMotion, count }: ParticleFieldProps) {
+ const pointsRef = useRef(null);
+ const linesRef = useRef(null);
+ const mouseRef = useRef(new THREE.Vector3(0, 0, 100));
+ const raycasterRef = useRef(new THREE.Raycaster());
+ const planeRef = useRef(new THREE.Plane(new THREE.Vector3(0, 0, 1), 0));
+ const { viewport, pointer, size } = useThree();
+
+ const { positions, velocities, originalPositions, colors, sizes } = useMemo(() => {
+ const positions = new Float32Array(count * 3);
+ const velocities = new Float32Array(count * 3);
+ const originalPositions = new Float32Array(count * 3);
+ const colors = new Float32Array(count * 3);
+ const sizes = new Float32Array(count);
+
+ const width = viewport.width * 1.4;
+ const height = viewport.height * 1.4;
+ const depth = 12;
+
+ for (let i = 0; i < count; i++) {
+ const i3 = i * 3;
+ const x = (Math.random() - 0.5) * width;
+ const y = (Math.random() - 0.5) * height;
+ const z = (Math.random() - 0.5) * depth;
+
+ positions[i3] = x;
+ positions[i3 + 1] = y;
+ positions[i3 + 2] = z;
+
+ originalPositions[i3] = x;
+ originalPositions[i3 + 1] = y;
+ originalPositions[i3 + 2] = z;
+
+ velocities[i3] = (Math.random() - 0.5) * 0.004;
+ velocities[i3 + 1] = (Math.random() - 0.5) * 0.004;
+ velocities[i3 + 2] = (Math.random() - 0.5) * 0.002;
+
+ // Mix of XDC blue, cyan, and subtle white
+ const colorChoice = Math.random();
+ if (colorChoice > 0.92) {
+ colors[i3] = 0.6; colors[i3 + 1] = 0.85; colors[i3 + 2] = 1.0;
+ } else if (colorChoice > 0.75) {
+ colors[i3] = 0.12; colors[i3 + 1] = 0.56; colors[i3 + 2] = 1.0;
+ } else if (colorChoice > 0.55) {
+ colors[i3] = 0.2; colors[i3 + 1] = 0.75; colors[i3 + 2] = 0.9;
+ } else {
+ colors[i3] = 0.85; colors[i3 + 1] = 0.9; colors[i3 + 2] = 1.0;
+ }
+
+ sizes[i] = 1.5 + Math.random() * 2.5;
+ }
+
+ return { positions, velocities, originalPositions, colors, sizes };
+ }, [viewport.width, viewport.height]);
+
+ const pointGeometry = useMemo(() => {
+ const geometry = new THREE.BufferGeometry();
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
+ geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
+ geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
+ return geometry;
+ }, [positions, colors, sizes]);
+
+ const lineGeometry = useMemo(() => {
+ const geometry = new THREE.BufferGeometry();
+ const linePositions = new Float32Array(count * MAX_CONNECTIONS * 6);
+ const lineColors = new Float32Array(count * MAX_CONNECTIONS * 6);
+ geometry.setAttribute('position', new THREE.BufferAttribute(linePositions, 3));
+ geometry.setAttribute('color', new THREE.BufferAttribute(lineColors, 3));
+ geometry.setDrawRange(0, 0);
+ return geometry;
+ }, []);
+
+ const pointMaterial = useMemo(() => {
+ return new THREE.ShaderMaterial({
+ uniforms: {
+ uPixelRatio: { value: Math.min(window.devicePixelRatio, 2) },
+ },
+ vertexShader: `
+ attribute float size;
+ attribute vec3 color;
+ varying vec3 vColor;
+ uniform float uPixelRatio;
+ void main() {
+ vColor = color;
+ vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
+ gl_PointSize = size * uPixelRatio * (300.0 / -mvPosition.z);
+ gl_Position = projectionMatrix * mvPosition;
+ }
+ `,
+ fragmentShader: `
+ varying vec3 vColor;
+ void main() {
+ float dist = length(gl_PointCoord - vec2(0.5));
+ if (dist > 0.5) discard;
+ float alpha = 1.0 - smoothstep(0.0, 0.5, dist);
+ gl_FragColor = vec4(vColor, alpha * 0.9);
+ }
+ `,
+ transparent: true,
+ depthWrite: false,
+ blending: THREE.AdditiveBlending,
+ vertexColors: true,
+ });
+ }, []);
+
+ const lineMaterial = useMemo(() => {
+ return new THREE.LineBasicMaterial({
+ vertexColors: true,
+ transparent: true,
+ opacity: 0.15,
+ blending: THREE.AdditiveBlending,
+ depthWrite: false,
+ });
+ }, []);
+
+ useEffect(() => {
+ const handleResize = () => {
+ pointMaterial.uniforms.uPixelRatio.value = Math.min(window.devicePixelRatio, 2);
+ };
+ window.addEventListener('resize', handleResize);
+ return () => window.removeEventListener('resize', handleResize);
+ }, [pointMaterial]);
+
+ useFrame((state) => {
+ if (!pointsRef.current || !linesRef.current) return;
+
+ const posAttr = pointsRef.current.geometry.attributes.position as THREE.BufferAttribute;
+ const positions = posAttr.array as Float32Array;
+ const linePosAttr = linesRef.current.geometry.attributes.position as THREE.BufferAttribute;
+ const lineColAttr = linesRef.current.geometry.attributes.color as THREE.BufferAttribute;
+ const linePositions = linePosAttr.array as Float32Array;
+ const lineColors = lineColAttr.array as Float32Array;
+
+ // Update mouse position in world space
+ raycasterRef.current.setFromCamera(pointer, state.camera);
+ const target = new THREE.Vector3();
+ raycasterRef.current.ray.intersectPlane(planeRef.current, target);
+ if (target) {
+ mouseRef.current.lerp(target, 0.12);
+ }
+
+ const time = state.clock.elapsedTime;
+ const mx = mouseRef.current.x;
+ const my = mouseRef.current.y;
+
+ let lineIndex = 0;
+ const tempLineIndices: number[] = [];
+
+ for (let i = 0; i < count; i++) {
+ const i3 = i * 3;
+ let px = positions[i3];
+ let py = positions[i3 + 1];
+ let pz = positions[i3 + 2];
+
+ if (!reducedMotion) {
+ // Ambient drift
+ px += velocities[i3] + Math.sin(time * 0.3 + i * 0.1) * 0.002;
+ py += velocities[i3 + 1] + Math.cos(time * 0.25 + i * 0.1) * 0.002;
+ pz += velocities[i3 + 2] + Math.sin(time * 0.15 + i * 0.05) * 0.001;
+
+ // Soft return to origin
+ const ox = originalPositions[i3];
+ const oy = originalPositions[i3 + 1];
+ const oz = originalPositions[i3 + 2];
+ px += (ox - px) * 0.003;
+ py += (oy - py) * 0.003;
+ pz += (oz - pz) * 0.003;
+
+ // Mouse repulsion (anti-gravity field)
+ const dx = px - mx;
+ const dy = py - my;
+ const dz = pz;
+ const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
+ if (dist < MOUSE_RADIUS && dist > 0.01) {
+ const force = (1 - dist / MOUSE_RADIUS) * MOUSE_FORCE;
+ px += (dx / dist) * force;
+ py += (dy / dist) * force;
+ pz += (dz / dist) * force * 0.5;
+ }
+ }
+
+ positions[i3] = px;
+ positions[i3 + 1] = py;
+ positions[i3 + 2] = pz;
+
+ // Build proximity lines (limit connections per particle)
+ let connections = 0;
+ for (let j = i + 1; j < count && connections < MAX_CONNECTIONS; j++) {
+ const j3 = j * 3;
+ const dx = px - positions[j3];
+ const dy = py - positions[j3 + 1];
+ const dz = pz - positions[j3 + 2];
+ const distSq = dx * dx + dy * dy + dz * dz;
+
+ if (distSq < CONNECTION_DISTANCE * CONNECTION_DISTANCE) {
+ const dist = Math.sqrt(distSq);
+ const alpha = 1 - dist / CONNECTION_DISTANCE;
+ const li = lineIndex * 6;
+
+ linePositions[li] = px;
+ linePositions[li + 1] = py;
+ linePositions[li + 2] = pz;
+ linePositions[li + 3] = positions[j3];
+ linePositions[li + 4] = positions[j3 + 1];
+ linePositions[li + 5] = positions[j3 + 2];
+
+ const r = 0.12 * alpha;
+ const g = 0.56 * alpha;
+ const b = 1.0 * alpha;
+ lineColors[li] = r; lineColors[li + 1] = g; lineColors[li + 2] = b;
+ lineColors[li + 3] = r; lineColors[li + 4] = g; lineColors[li + 5] = b;
+
+ lineIndex++;
+ connections++;
+ }
+ }
+ }
+
+ posAttr.needsUpdate = true;
+ linePosAttr.needsUpdate = true;
+ lineColAttr.needsUpdate = true;
+ linesRef.current.geometry.setDrawRange(0, lineIndex * 2);
+ });
+
+ return (
+ <>
+
+
+ >
+ );
+}
+
+export function AntiGravityField({ className }: { className?: string }) {
+ const [reducedMotion, setReducedMotion] = useState(false);
+ const [mounted, setMounted] = useState(false);
+ const [isMobile, setIsMobile] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+ const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
+ setReducedMotion(mq.matches);
+ const handler = (e: MediaQueryListEvent) => setReducedMotion(e.matches);
+ mq.addEventListener('change', handler);
+
+ const mobileMq = window.matchMedia('(max-width: 768px)');
+ setIsMobile(mobileMq.matches);
+ const mobileHandler = (e: MediaQueryListEvent) => setIsMobile(e.matches);
+ mobileMq.addEventListener('change', mobileHandler);
+
+ return () => {
+ mq.removeEventListener('change', handler);
+ mobileMq.removeEventListener('change', mobileHandler);
+ };
+ }, []);
+
+ if (!mounted) return null;
+
+ const count = isMobile ? PARTICLE_COUNT_MOBILE : PARTICLE_COUNT_DESKTOP;
+
+ return (
+
+ );
+}
+
+export default AntiGravityField;
diff --git a/website/src/components/ArchitectureLayers.tsx b/website/src/components/ArchitectureLayers.tsx
new file mode 100644
index 00000000..f1253dc6
--- /dev/null
+++ b/website/src/components/ArchitectureLayers.tsx
@@ -0,0 +1,78 @@
+import React, { useState } from 'react';
+import { Layers, Server, Database, Globe } from 'lucide-react';
+
+const layers = [
+ {
+ name: 'Application Layer',
+ icon: Globe,
+ description: 'DApps, wallets, exchanges, and enterprise applications interact with the network.',
+ color: 'bg-purple-500/10 text-purple-500',
+ },
+ {
+ name: 'Smart Contract Layer',
+ icon: Database,
+ description: 'Solidity smart contracts execute business logic and token standards like XRC20/721/1155.',
+ color: 'bg-blue-500/10 text-blue-500',
+ },
+ {
+ name: 'Consensus Layer',
+ icon: Server,
+ description: 'XDPoS 2.0 ensures fast, secure, and energy-efficient block validation.',
+ color: 'bg-emerald-500/10 text-emerald-500',
+ },
+ {
+ name: 'Network Layer',
+ icon: Layers,
+ description: 'Peer-to-peer communication propagates transactions and blocks globally.',
+ color: 'bg-amber-500/10 text-amber-500',
+ },
+];
+
+export function ArchitectureLayers() {
+ const [activeLayer, setActiveLayer] = useState(0);
+
+ return (
+
+
+
+
+
XDC Architecture Layers
+
+
+
+
+ {layers.map((layer, index) => {
+ const Icon = layer.icon;
+ return (
+
+ );
+ })}
+
+
+
+ {React.createElement(layers[activeLayer].icon, {
+ className: `h-6 w-6 ${layers[activeLayer].color.split(' ')[1]}`,
+ })}
+
+
{layers[activeLayer].name}
+
{layers[activeLayer].description}
+
+
+
+ );
+}
+
+export default ArchitectureLayers;
diff --git a/website/src/components/BlockVisualizer.tsx b/website/src/components/BlockVisualizer.tsx
new file mode 100644
index 00000000..b7eb0afc
--- /dev/null
+++ b/website/src/components/BlockVisualizer.tsx
@@ -0,0 +1,80 @@
+import React, { useState, useEffect, useRef } from 'react';
+
+interface Block {
+ number: number;
+ txs: number;
+ size: number;
+ time: string;
+}
+
+export function BlockVisualizer() {
+ const [blocks, setBlocks] = useState([]);
+ const intervalRef = useRef | null>(null);
+
+ useEffect(() => {
+ const base = 76543210;
+ const initial: Block[] = Array.from({ length: 5 }, (_, i) => ({
+ number: base - (4 - i),
+ txs: Math.floor(Math.random() * 80) + 20,
+ size: Math.floor(Math.random() * 30000) + 10000,
+ time: `${4 - i}s ago`,
+ }));
+ setBlocks(initial);
+
+ intervalRef.current = setInterval(() => {
+ setBlocks((prev) => {
+ const next = [...prev];
+ next.shift();
+ const last = next[next.length - 1].number;
+ next.push({
+ number: last + 1,
+ txs: Math.floor(Math.random() * 80) + 20,
+ size: Math.floor(Math.random() * 30000) + 10000,
+ time: 'just now',
+ });
+ return next.map((b, i) => ({ ...b, time: i === next.length - 1 ? 'just now' : `${(next.length - 1 - i) * 2}s ago` }));
+ });
+ }, 2000);
+
+ return () => {
+ if (intervalRef.current) clearInterval(intervalRef.current);
+ };
+ }, []);
+
+ return (
+
+
+
+ {blocks.map((block, index) => (
+
+
+
+ #{block.number.toString().slice(-4)}
+
+
+
Block {block.number.toLocaleString()}
+
{block.txs} txs · {(block.size / 1024).toFixed(1)} KB
+
+
+
{block.time}
+
+ ))}
+
+
+ );
+}
+
+export default BlockVisualizer;
diff --git a/website/src/components/BridgeVisualizer.tsx b/website/src/components/BridgeVisualizer.tsx
new file mode 100644
index 00000000..4cca2226
--- /dev/null
+++ b/website/src/components/BridgeVisualizer.tsx
@@ -0,0 +1,65 @@
+import React, { useState } from 'react';
+import { Network, ArrowRightLeft, Shield, Lock } from 'lucide-react';
+
+const steps = [
+ { icon: Lock, title: 'Lock Assets', description: 'Tokens are locked in a smart contract on the source chain.' },
+ { icon: Network, title: 'Verify Proof', description: 'Validators confirm the lock transaction on the source chain.' },
+ { icon: ArrowRightLeft, title: 'Mint Wrapped Assets', description: 'Equivalent wrapped tokens are minted on XDC.' },
+ { icon: Shield, title: 'Use on XDC', description: 'Assets can now be used within the XDC ecosystem.' },
+];
+
+export function BridgeVisualizer() {
+ const [activeStep, setActiveStep] = useState(0);
+
+ return (
+
+
+
+
+
Cross-Chain Bridge Flow
+
+
+
+
+ {steps.map((step, index) => {
+ const Icon = step.icon;
+ return (
+
+ );
+ })}
+
+
+
+
+
+
+ {React.createElement(steps[activeStep].icon, { className: 'h-5 w-5 text-primary' })}
+
{steps[activeStep].title}
+
+
{steps[activeStep].description}
+
+
+
+ );
+}
+
+export default BridgeVisualizer;