From 903f6a66ea1c4e777cdba2639e6feaf1da2f2839 Mon Sep 17 00:00:00 2001 From: flexykrn Date: Wed, 17 Jun 2026 17:54:06 +0530 Subject: [PATCH] Docusaurus migration - website-src-components-2 Part 64 of the Docusaurus migration split. --- .../src/components/CodeSnippetSwitcher.tsx | 82 ++++++++ website/src/components/CodeTabs.tsx | 54 +++++ .../src/components/ConsensusVisualizer.tsx | 92 +++++++++ website/src/components/DocsHero.tsx | 59 ++++++ .../src/components/FloatingAIAssistant.tsx | 35 ++++ website/src/components/GasFeeCalculator.tsx | 149 ++++++++++++++ website/src/components/HashSandbox.tsx | 62 ++++++ website/src/components/Hero3D.tsx | 184 ++++++++++++++++++ .../src/components/HomepageFeatures/index.tsx | 71 +++++++ .../HomepageFeatures/styles.module.css | 11 ++ 10 files changed, 799 insertions(+) create mode 100644 website/src/components/CodeSnippetSwitcher.tsx create mode 100644 website/src/components/CodeTabs.tsx create mode 100644 website/src/components/ConsensusVisualizer.tsx create mode 100644 website/src/components/DocsHero.tsx create mode 100644 website/src/components/FloatingAIAssistant.tsx create mode 100644 website/src/components/GasFeeCalculator.tsx create mode 100644 website/src/components/HashSandbox.tsx create mode 100644 website/src/components/Hero3D.tsx create mode 100644 website/src/components/HomepageFeatures/index.tsx create mode 100644 website/src/components/HomepageFeatures/styles.module.css diff --git a/website/src/components/CodeSnippetSwitcher.tsx b/website/src/components/CodeSnippetSwitcher.tsx new file mode 100644 index 00000000..608091b8 --- /dev/null +++ b/website/src/components/CodeSnippetSwitcher.tsx @@ -0,0 +1,82 @@ +import React, { useState } from 'react'; +import { Code2, Terminal, Copy, Check } from 'lucide-react'; + +const snippets = { + javascript: `const Web3 = require('web3'); +const web3 = new Web3('https://rpc.xinfin.network'); + +const balance = await web3.eth.getBalance('0x...'); +console.log(balance);`, + python: `from web3 import Web3 + +w3 = Web3(Web3.HTTPProvider('https://rpc.xinfin.network')) +balance = w3.eth.get_balance('0x...') +print(balance)`, + curl: `curl -X POST https://rpc.xinfin/network \\ + -H "Content-Type: application/json" \\ + -d '{ + "jsonrpc":"2.0", + "method":"eth_getBalance", + "params":["0x...","latest"], + "id":1 + }'`, + solidity: `// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract HelloXDC { + string public message = "Hello XDC"; +}`, +}; + +type Language = keyof typeof snippets; + +export function CodeSnippetSwitcher() { + const [lang, setLang] = useState('javascript'); + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + await navigator.clipboard.writeText(snippets[lang]); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
+
+
+ + Code Snippet +
+ +
+
+ {(Object.keys(snippets) as Language[]).map((l) => ( + + ))} +
+
+
+          {snippets[lang]}
+        
+
+
+ ); +} + +export default CodeSnippetSwitcher; diff --git a/website/src/components/CodeTabs.tsx b/website/src/components/CodeTabs.tsx new file mode 100644 index 00000000..dc5a7b69 --- /dev/null +++ b/website/src/components/CodeTabs.tsx @@ -0,0 +1,54 @@ +import React, { useEffect, useState } from 'react'; +import { Tabs, TabsList, TabsTrigger, TabsContent } from '@site/src/components/ui/tabs'; + +interface CodeTabsProps { + defaultValue: string; + children: React.ReactNode; + group?: string; +} + +const STORAGE_KEY = 'xdc-docs-code-language'; + +export function CodeTabs({ defaultValue, children, group = 'default' }: CodeTabsProps) { + const [value, setValue] = useState(defaultValue); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + try { + const saved = localStorage.getItem(STORAGE_KEY); + if (saved) { + const parsed = JSON.parse(saved); + if (parsed[group]) { + setValue(parsed[group]); + } + } + } catch { + // ignore + } + }, [group]); + + const handleChange = (newValue: string) => { + setValue(newValue); + try { + const saved = localStorage.getItem(STORAGE_KEY); + const parsed = saved ? JSON.parse(saved) : {}; + parsed[group] = newValue; + localStorage.setItem(STORAGE_KEY, JSON.stringify(parsed)); + } catch { + // ignore + } + }; + + if (!mounted) { + return
{children}
; + } + + return ( + + {children} + + ); +} + +export { TabsList, TabsTrigger, TabsContent }; diff --git a/website/src/components/ConsensusVisualizer.tsx b/website/src/components/ConsensusVisualizer.tsx new file mode 100644 index 00000000..8af90c7f --- /dev/null +++ b/website/src/components/ConsensusVisualizer.tsx @@ -0,0 +1,92 @@ +import React, { useState } from 'react'; +import { Shield, AlertTriangle, Lock, Eye, Users } from 'lucide-react'; + +const steps = [ + { + icon: Users, + title: 'Proposal', + description: 'Masternodes propose the next block based on round-robin scheduling.', + }, + { + icon: Eye, + title: 'Verification', + description: 'Validators verify block validity, signatures, and transaction correctness.', + }, + { + icon: Lock, + title: 'Commit', + description: 'Two-thirds majority commits the block through BFT voting.', + }, + { + icon: Shield, + title: 'Finality', + description: 'Block becomes irreversible with instant finality.', + }, +]; + +export function ConsensusVisualizer() { + const [activeStep, setActiveStep] = useState(0); + + return ( +
+
+
+ +

XDPoS Consensus Flow

+
+
+
+
+ {steps.map((_, index) => ( + + + {index < steps.length - 1 && ( +
+ )} + + ))} +
+ +
+
+ {React.createElement(steps[activeStep].icon, { className: 'h-6 w-6' })} +
+

{steps[activeStep].title}

+

{steps[activeStep].description}

+
+ +
+ {steps.map((_, index) => ( +
+
+
+ ); +} + +export default ConsensusVisualizer; diff --git a/website/src/components/DocsHero.tsx b/website/src/components/DocsHero.tsx new file mode 100644 index 00000000..eab8881c --- /dev/null +++ b/website/src/components/DocsHero.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { cn } from '@site/src/lib/utils'; + +interface DocsHeroProps { + title: string; + description?: string; + badge?: string; + children?: React.ReactNode; + className?: string; + visual?: 'globe' | 'none'; + image?: string; + imageAlt?: string; +} + +export function DocsHero({ title, description, badge, children, className, visual = 'none', image, imageAlt }: DocsHeroProps) { + return ( +
+ {visual === 'globe' && !image && ( +
+
+
+ )} +
+
+ {badge && ( + + {badge} + + )} +

{title}

+ {description && ( +

{description}

+ )} + {children} +
+ {image && ( +
+
+
+ {imageAlt +
+
+ )} +
+
+ ); +} + +export default DocsHero; diff --git a/website/src/components/FloatingAIAssistant.tsx b/website/src/components/FloatingAIAssistant.tsx new file mode 100644 index 00000000..803e1ced --- /dev/null +++ b/website/src/components/FloatingAIAssistant.tsx @@ -0,0 +1,35 @@ +import React, { useState, useEffect } from 'react'; +import { Bot, X } from 'lucide-react'; +import { useLocation } from '@docusaurus/router'; +import { AIChatbot } from '@site/src/components/AIChatbot'; + +export default function FloatingAIAssistant() { + const [open, setOpen] = useState(false); + const location = useLocation(); + + useEffect(() => { + if (open) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = ''; + } + return () => { + document.body.style.overflow = ''; + }; + }, [open]); + + return ( + <> + setOpen(false)} currentPath={location.pathname} /> + + + ); +} diff --git a/website/src/components/GasFeeCalculator.tsx b/website/src/components/GasFeeCalculator.tsx new file mode 100644 index 00000000..128cd6f1 --- /dev/null +++ b/website/src/components/GasFeeCalculator.tsx @@ -0,0 +1,149 @@ +import React, { useState, useCallback } from 'react'; +import { Fuel, TrendingDown, Info } from 'lucide-react'; + +const CHAINS = [ + { name: 'XDC Network', color: '#1e90ff', avgGasPrice: 0.00025, symbol: 'XDC', usdPerToken: 0.08 }, + { name: 'Ethereum', color: '#627EEA', avgGasPrice: 0.0025, symbol: 'ETH', usdPerToken: 3800 }, + { name: 'BNB Chain', color: '#F3BA2F', avgGasPrice: 0.0005, symbol: 'BNB', usdPerToken: 620 }, + { name: 'Polygon', color: '#8247E5', avgGasPrice: 0.0003, symbol: 'MATIC', usdPerToken: 0.65 }, +]; + +const TX_TYPES = [ + { label: 'Token Transfer (ERC-20)', gasLimit: 65000 }, + { label: 'ETH/XDC Transfer', gasLimit: 21000 }, + { label: 'NFT Mint', gasLimit: 150000 }, + { label: 'Swap (DEX)', gasLimit: 180000 }, + { label: 'Smart Contract Deploy', gasLimit: 800000 }, + { label: 'Custom', gasLimit: 0 }, +]; + +export function GasFeeCalculator() { + const [txTypeIdx, setTxTypeIdx] = useState(0); + const [customGas, setCustomGas] = useState(100000); + + const selectedType = TX_TYPES[txTypeIdx]; + const gasLimit = selectedType.gasLimit > 0 ? selectedType.gasLimit : customGas; + + const computeFeeUSD = useCallback((chain: typeof CHAINS[0]) => { + const feeInToken = gasLimit * chain.avgGasPrice * 1e-9 * 1e9; // gwei → token simplified + const usd = (gasLimit * chain.avgGasPrice) * chain.usdPerToken; + return { feeToken: (gasLimit * chain.avgGasPrice).toFixed(6), usd: usd < 0.01 ? '< $0.01' : `$${usd.toFixed(4)}` }; + }, [gasLimit]); + + const xdcFee = computeFeeUSD(CHAINS[0]); + const xdcUSD = parseFloat((gasLimit * CHAINS[0].avgGasPrice * CHAINS[0].usdPerToken).toFixed(6)); + const maxSaving = parseFloat((gasLimit * CHAINS[1].avgGasPrice * CHAINS[1].usdPerToken).toFixed(4)); + const savingPct = maxSaving > 0 ? Math.round((1 - xdcUSD / maxSaving) * 100) : 0; + + return ( +
+ {/* Header */} +
+
+
+ +
+
+

Gas Fee Calculator

+

Compare XDC fees vs. other chains

+
+
+ + Interactive + +
+ +
+ {/* Transaction type selector */} +
+ +
+ {TX_TYPES.map((type, i) => ( + + ))} +
+
+ + {/* Custom gas limit */} + {selectedType.gasLimit === 0 && ( +
+ + setCustomGas(Number(e.target.value))} + className="w-full accent-primary" + /> +
+ )} + + {/* Chain comparison bars */} +
+ +
+ {CHAINS.map((chain) => { + const fee = computeFeeUSD(chain); + const usdVal = parseFloat((gasLimit * chain.avgGasPrice * chain.usdPerToken).toFixed(6)); + const pct = maxSaving > 0 ? Math.min(100, (usdVal / maxSaving) * 100) : 0; + return ( +
+
+ {chain.name} +
+ {fee.feeToken} {chain.symbol} + · {fee.usd} +
+
+
+
+
+
+ ); + })} +
+
+ + {/* Summary: XDC savings */} +
+ +
+

+ XDC costs {savingPct}% less than Ethereum +

+

+ XDC fee: {xdcFee.feeToken} XDC ({xdcFee.usd}) · For {gasLimit.toLocaleString()} gas units +

+
+
+ +

+ + Estimates use typical gas prices. Actual fees vary with network conditions. +

+
+
+ ); +} + +export default GasFeeCalculator; diff --git a/website/src/components/HashSandbox.tsx b/website/src/components/HashSandbox.tsx new file mode 100644 index 00000000..840bc367 --- /dev/null +++ b/website/src/components/HashSandbox.tsx @@ -0,0 +1,62 @@ +import React, { useState } from 'react'; +import { Hash, Copy, Check } from 'lucide-react'; + +export function HashSandbox() { + const [input, setInput] = useState('Hello XDC Network'); + const [copied, setCopied] = useState(false); + + const generateHash = (str: string) => { + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; + } + return Math.abs(hash).toString(16).padStart(64, '0'); + }; + + const hash = generateHash(input); + + const handleCopy = async () => { + await navigator.clipboard.writeText(hash); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
+
+
+ +

Hash Sandbox

+
+
+
+
+ + setInput(e.target.value)} + className="w-full rounded-lg border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring" + /> +
+
+
+ {hash} + +
+
+

Type anything to see how data transforms into a fixed-length hash.

+
+
+ ); +} + +export default HashSandbox; diff --git a/website/src/components/Hero3D.tsx b/website/src/components/Hero3D.tsx new file mode 100644 index 00000000..300c0c6a --- /dev/null +++ b/website/src/components/Hero3D.tsx @@ -0,0 +1,184 @@ +import React, { useEffect, useRef, useCallback } from 'react'; + +/** + * Premium mesh gradient hero background. + * Uses a canvas-based flowing gradient mesh with subtle mouse parallax. + * Inspired by Vercel/Stripe hero aesthetics — restrained, elegant, high-quality. + * < 10KB code, 60fps, mobile-safe, reduced-motion accessible. + */ + +interface MeshPoint { + x: number; + y: number; + vx: number; + vy: number; + color: [number, number, number]; + radius: number; +} + +export function Hero3D({ className }: { className?: string }) { + const canvasRef = useRef(null); + const pointsRef = useRef([]); + const mouseRef = useRef({ x: 0.5, y: 0.5 }); // normalized 0-1 + const rafRef = useRef(); + const timeRef = useRef(0); + + const initPoints = useCallback((w: number, h: number, isDark: boolean) => { + // Color palette: XDC Blue + subtle surrounding hues + // Dark mode: deeper blues, navy teals + // Light mode: soft sky blues, light indigos + const palette: Array<[number, number, number]> = isDark + ? [ + [30, 100, 200], // deep XDC blue + [15, 70, 160], // navy blue + [40, 130, 230], // bright blue + [20, 60, 120], // dark navy + [60, 150, 220], // sky blue + [10, 50, 100], // very deep blue + ] + : [ + [100, 180, 255], // light sky blue + [60, 140, 240], // XDC blue light + [130, 200, 255], // pale blue + [80, 160, 250], // medium blue + [150, 210, 255], // very light blue + [50, 120, 220], // normal blue + ]; + + const count = 6; + const points: MeshPoint[] = []; + for (let i = 0; i < count; i++) { + points.push({ + x: Math.random() * w, + y: Math.random() * h, + vx: (Math.random() - 0.5) * 0.3, + vy: (Math.random() - 0.5) * 0.3, + color: palette[i % palette.length], + radius: Math.min(w, h) * (0.35 + Math.random() * 0.25), + }); + } + pointsRef.current = points; + }, []); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + const dpr = Math.min(window.devicePixelRatio || 1, 2); + let width = 0; + let height = 0; + let animationStopped = false; + + const isDark = () => + document.documentElement.getAttribute('data-theme') === 'dark'; + + const resize = () => { + width = canvas.offsetWidth; + height = canvas.offsetHeight; + canvas.width = width * dpr; + canvas.height = height * dpr; + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + initPoints(width, height, isDark()); + }; + + resize(); + window.addEventListener('resize', resize); + + const handleMouseMove = (e: MouseEvent) => { + const rect = canvas.getBoundingClientRect(); + mouseRef.current = { + x: (e.clientX - rect.left) / rect.width, + y: (e.clientY - rect.top) / rect.height, + }; + }; + + window.addEventListener('mousemove', handleMouseMove); + + const draw = () => { + if (animationStopped) return; + timeRef.current += 0.004; // very slow, smooth + const t = timeRef.current; + const dark = isDark(); + const points = pointsRef.current; + + // Clear with base background + ctx.clearRect(0, 0, width, height); + + // Move points gently — Lissajous-like orbits + for (let i = 0; i < points.length; i++) { + const p = points[i]; + // Gentle sinusoidal drift + p.x += p.vx + Math.sin(t * 0.8 + i * 1.1) * 0.12; + p.y += p.vy + Math.cos(t * 0.7 + i * 0.9) * 0.12; + + // Soft boundary bounce with wrap + if (p.x < -p.radius * 0.5) p.x = width + p.radius * 0.5; + if (p.x > width + p.radius * 0.5) p.x = -p.radius * 0.5; + if (p.y < -p.radius * 0.5) p.y = height + p.radius * 0.5; + if (p.y > height + p.radius * 0.5) p.y = -p.radius * 0.5; + } + + // Apply subtle mouse parallax shift to points + const mx = (mouseRef.current.x - 0.5) * 20; + const my = (mouseRef.current.y - 0.5) * 12; + + // Draw each gradient blob + for (const p of points) { + const gx = p.x + mx; + const gy = p.y + my; + const grad = ctx.createRadialGradient(gx, gy, 0, gx, gy, p.radius); + const [r, g, b] = p.color; + const alpha = dark ? 0.13 : 0.09; + grad.addColorStop(0, `rgba(${r},${g},${b},${alpha})`); + grad.addColorStop(1, `rgba(${r},${g},${b},0)`); + ctx.beginPath(); + ctx.arc(gx, gy, p.radius, 0, Math.PI * 2); + ctx.fillStyle = grad; + ctx.fill(); + } + + rafRef.current = requestAnimationFrame(draw); + }; + + const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)'); + + if (!prefersReduced.matches) { + draw(); + } else { + // Static fallback — draw once, no animation + initPoints(width, height, isDark()); + const dark = isDark(); + for (const p of pointsRef.current) { + const grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.radius); + const [r, g, b] = p.color; + const alpha = dark ? 0.1 : 0.07; + grad.addColorStop(0, `rgba(${r},${g},${b},${alpha})`); + grad.addColorStop(1, `rgba(${r},${g},${b},0)`); + ctx.beginPath(); + ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2); + ctx.fillStyle = grad; + ctx.fill(); + } + } + + return () => { + animationStopped = true; + window.removeEventListener('resize', resize); + window.removeEventListener('mousemove', handleMouseMove); + if (rafRef.current) cancelAnimationFrame(rafRef.current); + }; + }, [initPoints]); + + return ( +