Skip to content

Commit 5210232

Browse files
committed
Optimized font loading, and adds self-hosted fonts
1 parent 954a2c6 commit 5210232

20 files changed

+313
-37
lines changed

src/app.html

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,23 @@
44
<meta charset="utf-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1" />
66

7+
<!-- Preload self-hosted fonts for default themes (Tokyo Night, Dark, Light) -->
8+
<link rel="preload" as="style" href="/fonts/fonts.css" />
9+
<link rel="stylesheet" href="/fonts/fonts.css" media="print" onload="this.media='all';this.onload=null;" />
10+
<noscript>
11+
<link rel="stylesheet" href="/fonts/fonts.css" />
12+
</noscript>
13+
14+
<!-- Preconnect to Google Fonts for other themes -->
15+
<link rel="preconnect" href="https://fonts.googleapis.com" />
16+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
17+
718
<!-- Prevent FOUC by applying theme before page renders -->
819
<script>
920
(function () {
1021
try {
1122
// Apply theme
12-
const theme = localStorage.getItem('theme') || 'dark';
23+
const theme = localStorage.getItem('theme') || 'tokyonight';
1324
if (theme !== 'dark') {
1425
document.documentElement.classList.add('theme-' + theme);
1526
}

src/lib/config/customizable-settings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const DEFAULT_NAVBAR_DISPLAY: NavbarDisplayMode = (env.NTB_NAVBAR_DISPLAY
5353
* Default theme
5454
* Options: 'dark', 'light', 'midnight', 'arctic', 'ocean', 'purple', 'cyberpunk', 'terminal', 'lightpurple', 'muteddark', 'solarized'
5555
*/
56-
export const DEFAULT_THEME: ThemeOption = (env.NTB_DEFAULT_THEME as ThemeOption) ?? 'dark';
56+
export const DEFAULT_THEME: ThemeOption = (env.NTB_DEFAULT_THEME as ThemeOption) ?? 'tokyonight';
5757

5858
/**
5959
* Default language

src/lib/config/font-config.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Font Loading Configuration
3+
* Hybrid approach: Use self-hosted fonts for downloaded fonts, Google Fonts CDN for others
4+
*/
5+
6+
// Set to true to use self-hosted fonts for Fira Code, Fira Sans, and Inter
7+
// Other theme fonts will continue using Google Fonts CDN
8+
// Set to false to use Google Fonts CDN for all fonts
9+
export const USE_SELF_HOSTED_FONTS = true;
10+
11+
// Self-hosted fonts base path
12+
export const SELF_HOSTED_FONTS_PATH = '/fonts/fonts.css';
13+
14+
// Google Fonts CDN base
15+
export const GOOGLE_FONTS_CDN = 'https://fonts.googleapis.com/css2?family=';

src/lib/constants/theme-list.ts

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
// import type { Theme } from "$lib/stores/theme";
1+
import { USE_SELF_HOSTED_FONTS, SELF_HOSTED_FONTS_PATH, GOOGLE_FONTS_CDN } from '$lib/config/font-config';
22

3-
const primaryCdn = 'https://fonts.googleapis.com/css2?family=';
3+
const primaryCdn = GOOGLE_FONTS_CDN;
44

55
interface Theme {
66
id: string;
@@ -14,70 +14,109 @@ interface Theme {
1414
};
1515
}
1616

17+
// Fonts available for self-hosting (only these have been downloaded)
18+
const SELF_HOSTED_AVAILABLE = ['Fira Code', 'Fira Sans', 'Inter'];
19+
20+
// Helper to get font URL (Google Fonts CDN or self-hosted)
21+
// Only uses self-hosted path if the font family is available locally
22+
const getFontUrl = (fontFamily: string, googleFontsUrl: string) => {
23+
if (USE_SELF_HOSTED_FONTS && SELF_HOSTED_AVAILABLE.includes(fontFamily)) {
24+
return SELF_HOSTED_FONTS_PATH;
25+
}
26+
return googleFontsUrl;
27+
};
28+
1729
const fonts = {
1830
Inter: {
1931
name: 'Inter',
20-
url: `${primaryCdn}Inter:wght@300;400;500;600;700&family=Roboto+Mono:wght@400;500&display=swap`,
32+
url: getFontUrl('Inter', `${primaryCdn}Inter:wght@400;500;600;700&display=swap&subset=latin`),
2133
fallback: 'sans-serif',
2234
},
2335
Poppins: {
2436
name: 'Poppins',
25-
url: `${primaryCdn}Lora:ital,wght@0,400..700;1,400..700&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap`,
26-
fallback: 'sans',
37+
url: getFontUrl(
38+
'Poppins',
39+
`${primaryCdn}Poppins:wght@400;500;600;700&family=Lora:wght@400;500;600;700&display=swap&subset=latin`,
40+
),
41+
fallback: 'sans-serif',
2742
},
2843
Montserrat: {
2944
name: 'Montserrat',
30-
url: `${primaryCdn}Montserrat:wght@300;400;500;600;700;800;900&family=IBM+Plex+Mono:wght@400;500;600;700&display=swap`,
45+
url: getFontUrl(
46+
'Montserrat',
47+
`${primaryCdn}Montserrat:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap&subset=latin`,
48+
),
3149
fallback: 'sans-serif',
3250
},
3351
Raleway: {
3452
name: 'Raleway',
35-
url: `${primaryCdn}Raleway:wght@300;400;500;600;700;800&family=Source+Code+Pro:wght@400;500;600;700&display=swap`,
53+
url: getFontUrl(
54+
'Raleway',
55+
`${primaryCdn}Raleway:wght@400;500;600;700&family=Source+Code+Pro:wght@400;500;600&display=swap&subset=latin`,
56+
),
3657
fallback: 'sans-serif',
3758
},
3859
Orbitron: {
3960
name: 'Orbitron',
40-
url: `${primaryCdn}Orbitron:wght@400;500;600;700;800;900&family=Share+Tech+Mono&display=swap`,
61+
url: getFontUrl(
62+
'Orbitron',
63+
`${primaryCdn}Orbitron:wght@400;500;600;700&family=Share+Tech+Mono&display=swap&subset=latin`,
64+
),
4165
fallback: 'monospace',
4266
},
4367
JetBrainsMono: {
4468
name: 'JetBrains Mono',
45-
url: `${primaryCdn}JetBrains+Mono:wght@300;400;500;600;700;800&display=swap`,
69+
url: getFontUrl('JetBrains Mono', `${primaryCdn}JetBrains+Mono:wght@400;500;600;700&display=swap&subset=latin`),
4670
fallback: 'monospace',
4771
},
4872
SourceCodePro: {
4973
name: 'Source Code Pro',
50-
url: `${primaryCdn}Source+Code+Pro:wght@300;400;500;600;700&family=Open+Sans:wght@300;400;500;600;700&display=swap`,
74+
url: getFontUrl(
75+
'Source Code Pro',
76+
`${primaryCdn}Source+Code+Pro:wght@400;500;600&family=Open+Sans:wght@400;500;600&display=swap&subset=latin`,
77+
),
5178
fallback: 'monospace',
5279
},
5380
Inconsolata: {
5481
name: 'Inconsolata',
55-
url: `${primaryCdn}Inconsolata:wght@300;400;500;600;700&family=Lato:wght@300;400;700&display=swap`,
82+
url: getFontUrl(
83+
'Inconsolata',
84+
`${primaryCdn}Inconsolata:wght@400;500;600&family=Lato:wght@400;700&display=swap&subset=latin`,
85+
),
5686
fallback: 'monospace',
5787
},
5888
IBMPlexSans: {
5989
name: 'IBM Plex Sans',
60-
url: `${primaryCdn}IBM+Plex+Sans:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@300;400;500;600&display=swap`,
90+
url: getFontUrl(
91+
'IBM Plex Sans',
92+
`${primaryCdn}IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500;600&display=swap&subset=latin`,
93+
),
6194
fallback: 'sans-serif',
6295
},
6396
Ubuntu: {
6497
name: 'Ubuntu',
65-
url: `${primaryCdn}Ubuntu:wght@300;400;500;700&family=Ubuntu+Mono:wght@400;500;700&display=swap`,
98+
url: getFontUrl(
99+
'Ubuntu',
100+
`${primaryCdn}Ubuntu:wght@400;500;700&family=Ubuntu+Mono:wght@400;500;700&display=swap&subset=latin`,
101+
),
66102
fallback: 'monospace',
67103
},
68104
FiraCode: {
69105
name: 'Fira Code',
70-
url: `${primaryCdn}Fira+Sans:wght@300;400;500;600;700&family=Fira+Code:wght@300;400;500;600;700&display=swap`,
106+
url: getFontUrl(
107+
'Fira Code',
108+
`${primaryCdn}Fira+Sans:wght@400;500;600&family=Fira+Code:wght@400;500;600;700&display=swap&subset=latin`,
109+
),
71110
fallback: 'monospace',
72111
},
73112
Outfit: {
74113
name: 'Outfit',
75-
url: `${primaryCdn}Outfit:wght@300;400;500;600;700&display=swap`,
114+
url: getFontUrl('Outfit', `${primaryCdn}Outfit:wght@400;500;600&display=swap&subset=latin`),
76115
fallback: 'sans-serif',
77116
},
78117
Nunito: {
79118
name: 'Nunito',
80-
url: `${primaryCdn}Nunito:wght@300;400;500;600;700;800&display=swap`,
119+
url: getFontUrl('Nunito', `${primaryCdn}Nunito:wght@400;500;600;700&display=swap&subset=latin`),
81120
fallback: 'sans-serif',
82121
},
83122
};

src/lib/stores/theme.ts

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const STORAGE_KEY = 'theme';
2626
// Track loaded fonts to avoid duplicates
2727
const loadedFonts = new Set<string>();
2828

29-
// Helper function to load custom fonts
29+
// Helper function to load custom fonts with Font Loading API tracking
3030
function loadCustomFont(fontConfig: { name: string; url: string; fallback?: string }) {
3131
if (!browser || loadedFonts.has(fontConfig.url)) return;
3232

@@ -35,9 +35,65 @@ function loadCustomFont(fontConfig: { name: string; url: string; fallback?: stri
3535
link.href = fontConfig.url;
3636
link.crossOrigin = 'anonymous';
3737

38-
// Add fallback handling
38+
// Add loading state class
39+
document.documentElement.classList.add('fonts-loading');
40+
41+
// Error handling
3942
link.onerror = () => {
40-
logger.warn(`Failed to load font from ${fontConfig.url}`, { url: fontConfig.url, font: fontConfig.name });
43+
logger.warn(`Failed to load font from ${fontConfig.url}`, {
44+
url: fontConfig.url,
45+
font: fontConfig.name,
46+
component: 'ThemeStore',
47+
});
48+
document.documentElement.classList.remove('fonts-loading');
49+
};
50+
51+
// Success handling with Font Loading API
52+
link.onload = () => {
53+
// Wait for fonts to be ready using Font Loading API
54+
if (document.fonts) {
55+
document.fonts.ready
56+
.then(() => {
57+
// Extract font family names from the config
58+
const fontFamily = fontConfig.name;
59+
60+
// Check if font is loaded (try both regular and bold weights)
61+
const fontLoaded =
62+
document.fonts.check(`1em "${fontFamily}"`) || document.fonts.check(`700 1em "${fontFamily}"`);
63+
64+
if (fontLoaded) {
65+
document.documentElement.classList.remove('fonts-loading');
66+
document.documentElement.classList.add('fonts-loaded');
67+
logger.debug(`Font loaded successfully: ${fontFamily}`, {
68+
font: fontFamily,
69+
component: 'ThemeStore',
70+
});
71+
} else {
72+
// Font stylesheet loaded but font not available yet
73+
logger.debug(`Font stylesheet loaded, waiting for font: ${fontFamily}`, {
74+
font: fontFamily,
75+
component: 'ThemeStore',
76+
});
77+
78+
// Set a timeout fallback (3 seconds)
79+
setTimeout(() => {
80+
document.documentElement.classList.remove('fonts-loading');
81+
}, 3000);
82+
}
83+
})
84+
.catch((err) => {
85+
logger.warn('Font loading check failed', {
86+
error: err,
87+
font: fontConfig.name,
88+
component: 'ThemeStore',
89+
});
90+
document.documentElement.classList.remove('fonts-loading');
91+
});
92+
} else {
93+
// Fallback for browsers without Font Loading API
94+
document.documentElement.classList.remove('fonts-loading');
95+
document.documentElement.classList.add('fonts-loaded');
96+
}
4197
};
4298

4399
document.head.appendChild(link);

src/styles/base.scss

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300;400;500;600;700&display=swap');
1+
// Self-hosted fonts (Fira Code, Fira Sans, Inter)
2+
// Other theme fonts load dynamically via theme.ts
3+
@import url('/fonts/fonts.css');
24

35
/* Reset */
46
*,

src/styles/variables.scss

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@
5656
--spacing-2xl: 3rem;
5757

5858
/* Font categories */
59-
--font-body: 'Roboto Mono', 'Monaco', 'Menlo', 'Consolas', monospace;
60-
--font-heading: 'Roboto Mono', 'Monaco', 'Menlo', 'Consolas', monospace;
61-
--font-mono: 'Roboto Mono', 'Monaco', 'Menlo', 'Consolas', monospace;
59+
--font-body: 'Fira Code', 'Monaco', 'Menlo', 'Consolas', monospace;
60+
--font-heading: 'Fira Code', 'Monaco', 'Menlo', 'Consolas', monospace;
61+
--font-mono: 'Fira Code', 'Monaco', 'Menlo', 'Consolas', monospace;
6262
--font-size-2xs: 0.6rem;
6363
--font-size-xs: 0.75rem;
6464
--font-size-sm: 0.875rem;
22.8 KB
Binary file not shown.
22.8 KB
Binary file not shown.
22.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)