diff --git a/pages/package.json b/pages/package.json index 6a058129..9a6b5b14 100644 --- a/pages/package.json +++ b/pages/package.json @@ -31,6 +31,7 @@ "@types/three": "^0.185.0", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", + "compression-webpack-plugin": "^12.0.0", "copy-webpack-plugin": "^14.0.0", "css-loader": "^6.8.1", "html-webpack-plugin": "^5.5.3", diff --git a/pages/src/App.tsx b/pages/src/App.tsx index 765c37ad..6c4fe913 100644 --- a/pages/src/App.tsx +++ b/pages/src/App.tsx @@ -1,11 +1,12 @@ -import React, { useEffect } from 'react'; +import React, { Suspense, useEffect } from 'react'; import { Routes, Route, useLocation } from 'react-router-dom'; import LandingPage from './components/LandingPage'; import FeaturesPage from './pages/FeaturesPage'; -import BenchmarkPage from './pages/BenchmarkPage'; -import QuickStartPage from './pages/QuickStartPage'; -import DocsPage from './pages/DocsPage'; -import BlogPage from './pages/BlogPage'; + +const BenchmarkPage = React.lazy(() => import(/* webpackChunkName: "benchmark-page" */ './pages/BenchmarkPage')); +const QuickStartPage = React.lazy(() => import(/* webpackChunkName: "quickstart-page" */ './pages/QuickStartPage')); +const DocsPage = React.lazy(() => import(/* webpackChunkName: "docs-page" */ './pages/DocsPage')); +const BlogPage = React.lazy(() => import(/* webpackChunkName: "blog-page" */ './pages/BlogPage')); const ScrollToTop: React.FC = () => { const { pathname } = useLocation(); @@ -19,15 +20,17 @@ const App: React.FC = () => { return ( <> - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - + }> + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + ); }; diff --git a/pages/src/components/HeroSection.tsx b/pages/src/components/HeroSection.tsx index 0af00952..852c71fd 100644 --- a/pages/src/components/HeroSection.tsx +++ b/pages/src/components/HeroSection.tsx @@ -1,12 +1,13 @@ -import React, { useCallback, useState, useEffect } from 'react'; +import React, { Suspense, useCallback, useState, useEffect } from 'react'; import ReactDOM from 'react-dom'; import { Link } from 'react-router-dom'; import { useTranslation } from '../i18n'; import { useResponsive } from '../hooks/useResponsive'; -import ColorBends from './ColorBends'; import docDownloadIcon from '../assets/icons/doc-download-green.svg'; import copyIcon from '../assets/icons/icon-copy.svg'; +const ColorBends = React.lazy(() => import(/* webpackChunkName: "color-bends" */ './ColorBends')); + const TC = { brand: '#756BFF', @@ -123,6 +124,7 @@ const HeroSection: React.FC = () => { const { isMobile, isTablet } = useResponsive(); const [toastVisible, setToastVisible] = useState(false); const [toastMessage, setToastMessage] = useState(''); + const [showShaderBackground, setShowShaderBackground] = useState(false); const showToast = (message: string) => { setToastMessage(message); @@ -164,6 +166,29 @@ const HeroSection: React.FC = () => { return () => clearTimeout(timer); }, [toastVisible]); + useEffect(() => { + let secondFrame: number | undefined; + const firstFrame = requestAnimationFrame(() => { + secondFrame = requestAnimationFrame(() => setShowShaderBackground(true)); + }); + + return () => { + cancelAnimationFrame(firstFrame); + if (secondFrame !== undefined) cancelAnimationFrame(secondFrame); + }; + }, []); + + const shaderFallback = ( +
+ ); + return ( <>
{ }} > {/* Shader Background */} - + {shaderFallback} + {showShaderBackground && ( + + + + )} {/* Gradient overlay */}
/ is kept, scripts/handlers are stripped. - // This is why we can inject the returned SVG directly below without re-sanitizing. - securityLevel: 'strict', - theme: 'dark', - themeVariables: { - primaryColor: '#1a1a2e', - primaryTextColor: 'rgba(255,255,255,0.85)', - primaryBorderColor: 'rgba(255,255,255,0.2)', - lineColor: 'rgba(255,255,255,0.4)', - secondaryColor: '#16213e', - tertiaryColor: '#0f3460', - background: '#000000', - mainBkg: 'rgba(255,255,255,0.04)', - nodeBorder: 'rgba(255,255,255,0.16)', - clusterBkg: 'rgba(255,255,255,0.02)', - titleColor: '#FFFFFF', - edgeLabelBackground: '#000000', - }, - flowchart: { - htmlLabels: true, - curve: 'basis', - }, -}); +type Mermaid = typeof import('mermaid')['default']; + +let mermaidPromise: Promise | null = null; + +function loadMermaid(): Promise { + if (!mermaidPromise) { + mermaidPromise = import('mermaid') + .then(({ default: mermaid }) => { + mermaid.initialize({ + startOnLoad: false, + // 'strict' makes mermaid sanitize its own SVG output (DOMPurify internally): + // safe label HTML like / is kept, scripts/handlers are stripped. + // This is why we can inject the returned SVG directly below without re-sanitizing. + securityLevel: 'strict', + theme: 'dark', + themeVariables: { + primaryColor: '#1a1a2e', + primaryTextColor: 'rgba(255,255,255,0.85)', + primaryBorderColor: 'rgba(255,255,255,0.2)', + lineColor: 'rgba(255,255,255,0.4)', + secondaryColor: '#16213e', + tertiaryColor: '#0f3460', + background: '#000000', + mainBkg: 'rgba(255,255,255,0.04)', + nodeBorder: 'rgba(255,255,255,0.16)', + clusterBkg: 'rgba(255,255,255,0.02)', + titleColor: '#FFFFFF', + edgeLabelBackground: '#000000', + }, + flowchart: { + htmlLabels: true, + curve: 'basis', + }, + }); + return mermaid; + }) + .catch((error) => { + // Allow a later navigation to retry after a transient chunk-load failure. + mermaidPromise = null; + throw error; + }); + } + return mermaidPromise; +} interface MarkdownRendererProps { content: string; @@ -132,33 +148,47 @@ const MarkdownRenderer: React.FC = ({ content }) => { const mermaidBlocks = containerRef.current.querySelectorAll('code.language-mermaid'); if (mermaidBlocks.length === 0) return; - const renderPromises = Array.from(mermaidBlocks).map(async (block) => { - const pre = block.parentElement; - if (!pre) return; - const code = block.textContent || ''; + const renderMermaidBlocks = async () => { try { - const id = `mermaid-diagram-${crypto.randomUUID()}`; - const { svg } = await mermaid.render(id, code); + const mermaid = await loadMermaid(); if (cancelled) return; - // Replace the
 with rendered SVG. The SVG is produced by mermaid with
-        // securityLevel:'strict' (see initialize above), which already sanitizes its
-        // output. Re-running DOMPurify over the whole SVG breaks it (namespaces,
-        // inline