From 5ab1aec4084fd4560137e7ac87a740442d7141e3 Mon Sep 17 00:00:00 2001 From: FredrikOseberg Date: Thu, 14 Aug 2025 13:53:56 +0200 Subject: [PATCH] fix: fonts --- website/src/css/critical.css | 14 +- website/src/theme/FontLoader.tsx | 168 ---------------- website/src/theme/LayoutStabilizer.tsx | 253 ------------------------- website/src/theme/OptimizedStyles.tsx | 156 --------------- 4 files changed, 6 insertions(+), 585 deletions(-) delete mode 100644 website/src/theme/FontLoader.tsx delete mode 100644 website/src/theme/LayoutStabilizer.tsx delete mode 100644 website/src/theme/OptimizedStyles.tsx diff --git a/website/src/css/critical.css b/website/src/css/critical.css index cb9117f0f4..86aa0850dd 100644 --- a/website/src/css/critical.css +++ b/website/src/css/critical.css @@ -2,11 +2,9 @@ /* Only includes above-the-fold styles needed for initial paint */ /* This CSS should not conflict with main styles - only prevent layout shift */ -/* Import Google Fonts with optimal loading */ -@import url('https://fonts.googleapis.com/css2?family=Sen:wght@400;600;700&display=swap'); - /* Prevent layout shift - reserve space */ -html, body { +html, +body { margin: 0; padding: 0; overflow-x: hidden; @@ -30,7 +28,7 @@ html, body { .navbar .navbar__brand { display: none; } - + /* Reserve space for GitHub star button to prevent shift */ .header-github-link.navbar-link-outlined { min-width: 120px; /* Reserve space for "Star 12.5k" */ @@ -38,7 +36,7 @@ html, body { align-items: center; justify-content: center; } - + /* Ensure navbar items don't shift */ .navbar__items--right { min-width: 450px; /* Stabilize right navbar width */ @@ -85,9 +83,9 @@ main { aside[class*="docSidebarContainer"] { display: none; } - + main { width: 100%; padding: 0 0.5rem; } -} \ No newline at end of file +} diff --git a/website/src/theme/FontLoader.tsx b/website/src/theme/FontLoader.tsx deleted file mode 100644 index e775dd2f1a..0000000000 --- a/website/src/theme/FontLoader.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import React, { useEffect } from 'react'; -import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; -import Head from '@docusaurus/Head'; - -export default function FontLoader(): React.JSX.Element { - useEffect(() => { - if (!ExecutionEnvironment.canUseDOM) { - return; - } - - // Font loading strategy - const loadFonts = async () => { - // Check if fonts are already loaded - if (document.fonts && document.fonts.ready) { - try { - await document.fonts.ready; - document.documentElement.classList.add('fonts-loaded'); - } catch (error) { - console.error('Font loading error:', error); - } - } - - // Create font face with font-display: swap - const senFontFace = new FontFace( - 'Sen', - `url('/fonts/Sen-Regular.woff2') format('woff2'), - url('/fonts/Sen-Regular.woff') format('woff')`, - { - weight: '400', - style: 'normal', - display: 'swap' // Critical for preventing invisible text - } - ); - - const senBoldFontFace = new FontFace( - 'Sen', - `url('/fonts/Sen-Bold.woff2') format('woff2'), - url('/fonts/Sen-Bold.woff') format('woff')`, - { - weight: '700', - style: 'normal', - display: 'swap' - } - ); - - // Load fonts asynchronously - try { - const loadedFonts = await Promise.all([ - senFontFace.load(), - senBoldFontFace.load() - ]); - - // Add fonts to document - loadedFonts.forEach(font => { - (document.fonts as any).add(font); - }); - - // Mark fonts as loaded - document.documentElement.classList.add('custom-fonts-loaded'); - } catch (error) { - console.error('Failed to load custom fonts:', error); - // Fallback to system fonts is automatic due to font stack - } - }; - - // Load fonts with requestIdleCallback for better performance - if ('requestIdleCallback' in window) { - window.requestIdleCallback(() => loadFonts(), { timeout: 3000 }); - } else { - // Fallback for browsers without requestIdleCallback - setTimeout(loadFonts, 100); - } - - // Add CSS for font loading states - const fontLoadingStyles = document.createElement('style'); - fontLoadingStyles.textContent = ` - /* Use system fonts initially for faster paint */ - body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, - Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; - } - - /* Apply custom fonts when loaded */ - .custom-fonts-loaded body { - font-family: "Sen", -apple-system, BlinkMacSystemFont, "Segoe UI", - Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; - } - - /* Smooth transition when fonts load */ - body, h1, h2, h3, h4, h5, h6, p, a, li { - transition: font-family 0.2s ease-in-out; - } - - /* Prevent layout shift from font loading */ - h1, h2, h3, h4, h5, h6 { - font-synthesis: none; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - } - `; - document.head.appendChild(fontLoadingStyles); - - return () => { - // Cleanup if needed - if (fontLoadingStyles.parentNode) { - fontLoadingStyles.parentNode.removeChild(fontLoadingStyles); - } - }; - }, []); - - return ( - - {/* Preload font files for faster loading */} - - - - {/* Font-face declarations with font-display: swap */} - - - ); -} \ No newline at end of file diff --git a/website/src/theme/LayoutStabilizer.tsx b/website/src/theme/LayoutStabilizer.tsx deleted file mode 100644 index b5d3eae018..0000000000 --- a/website/src/theme/LayoutStabilizer.tsx +++ /dev/null @@ -1,253 +0,0 @@ -import React, { useEffect } from 'react'; -import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; -import Head from '@docusaurus/Head'; - -export default function LayoutStabilizer(): React.JSX.Element { - useEffect(() => { - if (!ExecutionEnvironment.canUseDOM) { - return; - } - - // Add layout stability classes - const stabilizeLayout = () => { - // Reserve space for navbar - const navbar = document.querySelector('.navbar'); - if (navbar && !navbar.classList.contains('layout-reserved')) { - navbar.classList.add('layout-reserved'); - } - - // Reserve space for sidebar - const sidebar = document.querySelector('aside[class*="docSidebarContainer"]'); - if (sidebar && !sidebar.classList.contains('layout-reserved')) { - sidebar.classList.add('layout-reserved'); - } - - // Stabilize main container - const mainContainer = document.querySelector('main[class*="docMainContainer"]'); - if (mainContainer && !mainContainer.classList.contains('layout-stabilized')) { - mainContainer.classList.add('layout-stabilized'); - } - - // Add skeleton loading for slow content - const addSkeletonLoading = () => { - const contentContainers = document.querySelectorAll('article:empty, .markdown:empty'); - contentContainers.forEach(container => { - if (!container.querySelector('.skeleton-loader')) { - const skeleton = document.createElement('div'); - skeleton.className = 'skeleton-loader'; - skeleton.innerHTML = ` -
-
-
-
- `; - container.appendChild(skeleton); - } - }); - }; - - addSkeletonLoading(); - - // Observe images for lazy loading with proper dimensions - const images = document.querySelectorAll('img:not([data-stabilized])'); - images.forEach(img => { - const imgElement = img as HTMLImageElement; - - // If image has intrinsic dimensions, reserve space - if (imgElement.naturalWidth && imgElement.naturalHeight) { - const aspectRatio = imgElement.naturalHeight / imgElement.naturalWidth; - imgElement.style.aspectRatio = `${imgElement.naturalWidth} / ${imgElement.naturalHeight}`; - } else { - // Set a default aspect ratio for common image types - if (imgElement.src.includes('screenshot') || imgElement.src.includes('demo')) { - imgElement.style.aspectRatio = '16 / 9'; - } else if (imgElement.src.includes('logo') || imgElement.src.includes('icon')) { - imgElement.style.aspectRatio = '1 / 1'; - } else { - imgElement.style.aspectRatio = '4 / 3'; // Default aspect ratio - } - } - - imgElement.setAttribute('data-stabilized', 'true'); - imgElement.loading = 'lazy'; // Enable native lazy loading - imgElement.decoding = 'async'; // Async decoding for better performance - }); - }; - - // Run stabilization immediately - stabilizeLayout(); - - // Re-run on route changes (for SPA navigation) - const observer = new MutationObserver(() => { - stabilizeLayout(); - }); - - observer.observe(document.body, { - childList: true, - subtree: true - }); - - // Monitor viewport changes - let resizeTimer: NodeJS.Timeout; - const handleResize = () => { - clearTimeout(resizeTimer); - resizeTimer = setTimeout(() => { - stabilizeLayout(); - }, 250); - }; - - window.addEventListener('resize', handleResize, { passive: true }); - - // Cleanup - return () => { - observer.disconnect(); - window.removeEventListener('resize', handleResize); - clearTimeout(resizeTimer); - }; - }, []); - - return ( - - - - ); -} \ No newline at end of file diff --git a/website/src/theme/OptimizedStyles.tsx b/website/src/theme/OptimizedStyles.tsx deleted file mode 100644 index b9cfa69afd..0000000000 --- a/website/src/theme/OptimizedStyles.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import React, { useEffect } from 'react'; -import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; - -export default function OptimizedStyles() { - useEffect(() => { - if (!ExecutionEnvironment.canUseDOM) { - return; - } - - // Function to load CSS asynchronously - const loadStylesheet = (href: string, media: string = 'all') => { - // Check if already loaded - if (document.querySelector(`link[href="${href}"]`)) { - return; - } - - // Create preload link - const preloadLink = document.createElement('link'); - preloadLink.rel = 'preload'; - preloadLink.as = 'style'; - preloadLink.href = href; - - // Create actual stylesheet link - const styleLink = document.createElement('link'); - styleLink.rel = 'stylesheet'; - styleLink.href = href; - styleLink.media = media; - - // Once preloaded, apply the stylesheet - preloadLink.onload = () => { - preloadLink.onload = null; - // Switch from preload to stylesheet - preloadLink.rel = 'stylesheet'; - }; - - // Fallback for browsers that don't support preload - const noscriptFallback = document.createElement('noscript'); - const fallbackLink = document.createElement('link'); - fallbackLink.rel = 'stylesheet'; - fallbackLink.href = href; - noscriptFallback.appendChild(fallbackLink); - - // Add to document head - document.head.appendChild(preloadLink); - document.head.appendChild(noscriptFallback); - - // Also add regular link with print media to load in background - const printLink = document.createElement('link'); - printLink.rel = 'stylesheet'; - printLink.href = href; - printLink.media = 'print'; - printLink.onload = function() { - (this as HTMLLinkElement).media = media; - }; - document.head.appendChild(printLink); - }; - - // Load non-critical styles after initial render - const loadNonCriticalStyles = () => { - // Load additional styles based on viewport - if (window.innerWidth > 996) { - // Desktop-specific styles - loadStylesheet('/assets/css/desktop-enhancements.css', 'screen and (min-width: 997px)'); - } else { - // Mobile-specific styles - loadStylesheet('/assets/css/mobile-enhancements.css', 'screen and (max-width: 996px)'); - } - - // Load animation and interaction styles - loadStylesheet('/assets/css/animations.css', 'all'); - - // Load theme-specific styles - const theme = document.documentElement.getAttribute('data-theme'); - if (theme === 'dark') { - loadStylesheet('/assets/css/dark-theme.css', 'all'); - } - }; - - // Use requestIdleCallback if available, otherwise setTimeout - if ('requestIdleCallback' in window) { - window.requestIdleCallback(loadNonCriticalStyles, { timeout: 2000 }); - } else { - setTimeout(loadNonCriticalStyles, 100); - } - - // Optimize existing stylesheets - const optimizeExistingStyles = () => { - const allStylesheets = document.querySelectorAll('link[rel="stylesheet"]'); - allStylesheets.forEach((stylesheet) => { - const link = stylesheet as HTMLLinkElement; - // Skip critical styles and any styles that are already optimized - if (link.href.includes('critical') || - link.getAttribute('data-critical') === 'true' || - link.getAttribute('data-optimized') === 'true') { - return; - } - - // Mark as optimized to avoid re-processing - link.setAttribute('data-optimized', 'true'); - - // Convert blocking stylesheets to non-blocking - if (!link.media || link.media === 'all') { - // Temporarily set to print to make non-blocking - const originalMedia = link.media || 'all'; - link.media = 'print'; - link.onload = function() { - (this as HTMLLinkElement).media = originalMedia; - }; - } - }); - }; - - // Run optimization immediately - optimizeExistingStyles(); - - // Monitor for dynamically added stylesheets - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if (mutation.type === 'childList') { - mutation.addedNodes.forEach((node) => { - if (node.nodeName === 'LINK') { - const link = node as HTMLLinkElement; - if (link.rel === 'stylesheet' && - !link.getAttribute('data-optimized') && - !link.getAttribute('data-critical') && - !link.href.includes('critical')) { - link.setAttribute('data-optimized', 'true'); - // Make it non-blocking - const originalMedia = link.media || 'all'; - link.media = 'print'; - link.onload = function() { - (this as HTMLLinkElement).media = originalMedia; - }; - } - } - }); - } - }); - }); - - // Start observing the document head for changes - observer.observe(document.head, { - childList: true, - subtree: false - }); - - // Cleanup - return () => { - observer.disconnect(); - }; - }, []); - - // This component handles non-critical CSS optimization only - // Critical CSS is handled in Root.tsx - return null; -} \ No newline at end of file