Self-host the three web fonts and drop the render-blocking Google Fonts CSS
This commit is contained in:
BIN
frontend/fonts/BricolageGrotesque-latin-ext.woff2
Normal file
BIN
frontend/fonts/BricolageGrotesque-latin-ext.woff2
Normal file
Binary file not shown.
BIN
frontend/fonts/BricolageGrotesque-latin.woff2
Normal file
BIN
frontend/fonts/BricolageGrotesque-latin.woff2
Normal file
Binary file not shown.
BIN
frontend/fonts/HankenGrotesk-latin-ext.woff2
Normal file
BIN
frontend/fonts/HankenGrotesk-latin-ext.woff2
Normal file
Binary file not shown.
BIN
frontend/fonts/HankenGrotesk-latin.woff2
Normal file
BIN
frontend/fonts/HankenGrotesk-latin.woff2
Normal file
Binary file not shown.
BIN
frontend/fonts/JetBrainsMono-latin-ext.woff2
Normal file
BIN
frontend/fonts/JetBrainsMono-latin-ext.woff2
Normal file
Binary file not shown.
BIN
frontend/fonts/JetBrainsMono-latin.woff2
Normal file
BIN
frontend/fonts/JetBrainsMono-latin.woff2
Normal file
Binary file not shown.
51
frontend/fonts/fonts.css
Normal file
51
frontend/fonts/fonts.css
Normal file
@@ -0,0 +1,51 @@
|
||||
/* Generated by scripts/fetch-fonts.js — do not edit by hand. */
|
||||
@font-face {
|
||||
font-family: 'Bricolage Grotesque';
|
||||
font-style: normal;
|
||||
font-weight: 600 800;
|
||||
font-stretch: 100%;
|
||||
font-display: swap;
|
||||
src: url(BricolageGrotesque-latin-ext.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Bricolage Grotesque';
|
||||
font-style: normal;
|
||||
font-weight: 600 800;
|
||||
font-stretch: 100%;
|
||||
font-display: swap;
|
||||
src: url(BricolageGrotesque-latin.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Hanken Grotesk';
|
||||
font-style: normal;
|
||||
font-weight: 400 700;
|
||||
font-display: swap;
|
||||
src: url(HankenGrotesk-latin-ext.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Hanken Grotesk';
|
||||
font-style: normal;
|
||||
font-weight: 400 700;
|
||||
font-display: swap;
|
||||
src: url(HankenGrotesk-latin.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400 700;
|
||||
font-display: swap;
|
||||
src: url(JetBrainsMono-latin-ext.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400 700;
|
||||
font-display: swap;
|
||||
src: url(JetBrainsMono-latin.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@@ -13,16 +13,13 @@
|
||||
<meta name="apple-mobile-web-app-title" content="YT Player" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; img-src 'self' https: data: asset: http://asset.localhost blob:; media-src 'self' https: blob: asset: http://asset.localhost; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com data:; script-src 'self'; connect-src 'self' https: ipc: http://ipc.localhost blob:; worker-src 'self';"
|
||||
content="default-src 'self'; img-src 'self' https: data: asset: http://asset.localhost blob:; media-src 'self' https: blob: asset: http://asset.localhost; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; script-src 'self'; connect-src 'self' https: ipc: http://ipc.localhost blob:; worker-src 'self';"
|
||||
/>
|
||||
<title>YT Player</title>
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,600;12..96,700;12..96,800&family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="preload" href="fonts/BricolageGrotesque-latin.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="fonts/HankenGrotesk-latin.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="stylesheet" href="fonts/fonts.css" />
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -68,6 +68,13 @@ const SHELL = [
|
||||
'/manifest.webmanifest',
|
||||
'/icons/icon-192.png',
|
||||
'/icons/icon-512.png',
|
||||
'/fonts/fonts.css',
|
||||
'/fonts/BricolageGrotesque-latin-ext.woff2',
|
||||
'/fonts/BricolageGrotesque-latin.woff2',
|
||||
'/fonts/HankenGrotesk-latin-ext.woff2',
|
||||
'/fonts/HankenGrotesk-latin.woff2',
|
||||
'/fonts/JetBrainsMono-latin-ext.woff2',
|
||||
'/fonts/JetBrainsMono-latin.woff2',
|
||||
];
|
||||
|
||||
// ---- Install: pre-cache the app shell ----
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"update-ytdlp": "node scripts/setup-ytdlp.js --force",
|
||||
"make-icon": "node scripts/make-icon.js",
|
||||
"make-pwa-icons": "node scripts/make-pwa-icons.js",
|
||||
"fetch-fonts": "node scripts/fetch-fonts.js",
|
||||
"tauri": "tauri",
|
||||
"tauri:dev": "tauri dev",
|
||||
"tauri:build": "tauri build",
|
||||
|
||||
@@ -10,7 +10,7 @@ green, app boots with no JS errors, P2P on by default, offline boot works).
|
||||
|-----|----|-------|--------|--------|-------|
|
||||
| 001 | 001-perf-timing-marks-105acc | Add startup, search and play timing marks plus yt-dlp duration logs | done | Add startup, search and play timing marks plus yt-dlp duration logs | baseline numbers for the rest |
|
||||
| 002 | 002-compress-and-etag-shell-bd459c | Serve the app shell gzip/brotli-compressed with ETags | done | Serve the app shell gzip/brotli-compressed with ETags | app.js 426 KB → 94 KB (br) |
|
||||
| 003 | 003-self-host-fonts-89466b | Self-host the three web fonts and drop the render-blocking Google Fonts CSS | in-progress | | needs network once; CSP font-src fix |
|
||||
| 003 | 003-self-host-fonts-89466b | Self-host the three web fonts and drop the render-blocking Google Fonts CSS | done | Self-host the three web fonts and drop the render-blocking Google Fonts CSS | needs network once; CSP font-src fix |
|
||||
| 004 | 004-coalesce-stream-resolves-a92d40 | Coalesce concurrent resolveStreams calls for the same video | queued | | |
|
||||
| 005 | 005-warm-streams-on-intent-47b3d3 | Warm the stream cache for likely next plays | queued | | cold play 7.5 s → cached 1.2 s |
|
||||
| 006 | 006-innertube-search-48066b | Answer searches from YouTube InnerTube directly with yt-dlp fallback | queued | | search 4–5 s → ~0.8 s |
|
||||
|
||||
@@ -129,3 +129,9 @@ Output ONLY the following, no other prose:
|
||||
3. `Findings:` — max 10 lines.
|
||||
|
||||
Do not commit. Do not push. Do not touch files outside the Steps.
|
||||
|
||||
## Execution log
|
||||
|
||||
- Executor: in-session Agent (haiku). Attempts: 1. Fix rounds: 0.
|
||||
- Orchestrator re-ran Verification: 6 woff2 + fonts.css present, Google Fonts link count 0, CSP `font-src 'self'` 1, each woff2 precached in sw.js once, 52 frontend tests pass, `/fonts/fonts.css` 200 text/css and woff2 200 font/woff2 from a real server.
|
||||
- Executor Findings (verbatim): Plan executed successfully. All 6 font files generated (3 families x 2 subsets). CSP updated to allow 'self' fonts. SW.js updated to precache all fonts. Google Fonts links completely removed. Server responds correctly to font requests. Unit tests pass.
|
||||
31
scripts/fetch-fonts.js
Normal file
31
scripts/fetch-fonts.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// Downloads the app's Google fonts once (latin + latin-ext subsets) into
|
||||
// frontend/fonts/ and writes frontend/fonts/fonts.css pointing at them.
|
||||
// Re-run only when the font list changes.
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const CSS_URL = 'https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,600..800&family=Hanken+Grotesk:wght@400..700&family=JetBrains+Mono:wght@400..700&display=swap';
|
||||
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36';
|
||||
const OUT = path.join(__dirname, '..', 'frontend', 'fonts');
|
||||
(async () => {
|
||||
fs.mkdirSync(OUT, { recursive: true });
|
||||
const css = await (await fetch(CSS_URL, { headers: { 'User-Agent': UA } })).text();
|
||||
// parts[k] ends with the "/* <subset> */" comment of the block in parts[k+1].
|
||||
const parts = css.split('@font-face');
|
||||
let out = '/* Generated by scripts/fetch-fonts.js — do not edit by hand. */\n';
|
||||
let n = 0;
|
||||
for (let k = 1; k < parts.length; k++) {
|
||||
const subset = (parts[k - 1].match(/\/\*\s*([\w-]+)\s*\*\/\s*$/) || [])[1];
|
||||
const b = parts[k].slice(0, parts[k].indexOf('}') + 1); // just this block
|
||||
if (subset !== 'latin' && subset !== 'latin-ext') continue;
|
||||
const url = (b.match(/url\((https:[^)]+\.woff2)\)/) || [])[1];
|
||||
const fam = (b.match(/font-family:\s*'([^']+)'/) || [])[1];
|
||||
if (!url || !fam) continue;
|
||||
const file = `${fam.replace(/\s+/g, '')}-${subset}.woff2`;
|
||||
const buf = Buffer.from(await (await fetch(url)).arrayBuffer());
|
||||
fs.writeFileSync(path.join(OUT, file), buf);
|
||||
out += '@font-face' + b.replace(url, file).trimEnd() + '\n';
|
||||
n++;
|
||||
}
|
||||
fs.writeFileSync(path.join(OUT, 'fonts.css'), out);
|
||||
console.log(`wrote ${n} font files + fonts.css`);
|
||||
})().catch((e) => { console.error(e); process.exit(1); });
|
||||
Reference in New Issue
Block a user