1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-05 17:53:12 +02:00

fix: fonts

This commit is contained in:
FredrikOseberg 2025-08-14 13:53:56 +02:00
parent ff2f9ed97e
commit 5ab1aec408
No known key found for this signature in database
GPG Key ID: 282FD8A6D8F9BCF0
4 changed files with 6 additions and 585 deletions

View File

@ -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;
}
}
}

View File

@ -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 (
<Head>
{/* Preload font files for faster loading */}
<link
rel="preload"
href="/fonts/Sen-Regular.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
<link
rel="preload"
href="/fonts/Sen-Bold.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
{/* Font-face declarations with font-display: swap */}
<style>{`
@font-face {
font-family: 'Sen';
src: url('/fonts/Sen-Regular.woff2') format('woff2'),
url('/fonts/Sen-Regular.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap; /* Critical: shows text immediately */
}
@font-face {
font-family: 'Sen';
src: url('/fonts/Sen-Bold.woff2') format('woff2'),
url('/fonts/Sen-Bold.woff') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Sen';
src: url('/fonts/Sen-SemiBold.woff2') format('woff2'),
url('/fonts/Sen-SemiBold.woff') format('woff');
font-weight: 600;
font-style: normal;
font-display: swap;
}
/* Fallback for older browsers */
@supports not (font-display: swap) {
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Oxygen, Ubuntu, sans-serif;
}
}
`}</style>
</Head>
);
}

View File

@ -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 = `
<div class="skeleton skeleton-title"></div>
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text" style="width: 80%"></div>
`;
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 (
<Head>
<style>{`
/* Layout Stability Styles */
/* Reserve space for navbar */
.navbar.layout-reserved {
min-height: 56px;
contain: layout style;
}
/* Reserve space for sidebar */
aside.layout-reserved {
min-width: 250px;
contain: layout style;
}
/* Stabilize main container */
main.layout-stabilized {
min-height: calc(100vh - 56px);
contain: layout;
will-change: auto;
}
/* Prevent layout shifts from async content */
article {
min-height: 400px;
contain: layout style;
}
/* Image stability */
img {
max-width: 100%;
height: auto;
display: block;
background: #f0f0f0;
}
img[data-stabilized] {
background: transparent;
}
/* Skeleton loading styles */
.skeleton-loader {
padding: 1rem;
animation: fade-in 0.3s ease-in;
}
.skeleton {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: skeleton-shimmer 1.5s infinite;
border-radius: 4px;
margin-bottom: 1rem;
}
.skeleton-title {
height: 2rem;
width: 60%;
margin-bottom: 1.5rem;
}
.skeleton-text {
height: 1rem;
width: 100%;
margin-bottom: 0.5rem;
}
@keyframes skeleton-shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
/* Prevent CLS from font loading */
body {
font-synthesis: none;
text-rendering: optimizeLegibility;
}
/* Stabilize buttons and interactive elements */
button, a.button {
min-height: 36px;
contain: layout style;
}
/* Stabilize form elements */
input, textarea, select {
min-height: 36px;
contain: layout style;
}
/* Code blocks stability */
pre {
min-height: 60px;
contain: layout;
overflow: auto;
}
/* Table stability */
table {
table-layout: fixed;
width: 100%;
contain: layout;
}
/* Mobile stability */
@media (max-width: 996px) {
main.layout-stabilized {
min-height: calc(100vh - 60px);
}
.navbar.layout-reserved {
min-height: 60px;
}
}
/* Prevent layout shift from lazy-loaded content */
[data-lazy-load] {
min-height: 100px;
contain: layout;
}
/* Optimize Cumulative Layout Shift */
* {
/* Prevent margin collapse issues */
margin-top: 0;
}
h1, h2, h3, h4, h5, h6, p, ul, ol {
margin-bottom: 1rem;
}
`}</style>
</Head>
);
}

View File

@ -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;
}