diff --git a/website/README.md b/website/README.md
index b982136..af7a637 100644
--- a/website/README.md
+++ b/website/README.md
@@ -33,7 +33,7 @@ The website is 100% static HTML, CSS, and JavaScript.
- **Static i18n compiler**: `build.cjs` combines `template.html` with dictionaries in `locales/`.
- **Build-time minification**: Source CSS/JS stays readable; generated output uses `.min.css` and `.min.js`.
-- **Strangler CSS architecture**: `styles/*.css` supplies page/runtime bundles; `style.legacy.css` is the byte-identical, unloaded reference monolith. The landing ships a critical shell, a desktop/on-demand demo bundle, and idle-loaded below-fold styles. Legal, join, and alternatives rules stay out of landing bundles.
+- **Strangler CSS architecture**: `styles/*.css` supplies page-specific production bundles; `style.legacy.css` is the byte-identical, unloaded reference monolith. The landing bundle preserves the legacy cascade while excluding Join- and Alternatives-only rules. Structural CSS is not activated after paint because that causes layout shifts.
- **Zero backend**: The compiled site can be hosted by any static file server.
- **Zero external assets**: Fonts, icons, scripts, and images must remain self-hosted.
- **Generated SEO/runtime files**: `version.json`, sitemap, robots, clean URLs, localized pages, and minified assets are copied or generated into `www/`.
diff --git a/website/app.js b/website/app.js
index 6de2394..f11d24a 100644
--- a/website/app.js
+++ b/website/app.js
@@ -1,27 +1,6 @@
// KoalaSync Landing Page Logic
document.addEventListener('DOMContentLoaded', () => {
- const deferredCss = document.body.dataset.deferredCss;
- if (deferredCss) {
- const loadDeferredCss = () => {
- if (document.querySelector('link[data-landing-deferred]')) return;
- const link = document.createElement('link');
- link.rel = 'stylesheet';
- link.href = deferredCss;
- link.dataset.landingDeferred = '';
- const integrity = document.body.dataset.deferredCssIntegrity;
- if (integrity) {
- link.integrity = integrity;
- link.crossOrigin = 'anonymous';
- }
- document.head.appendChild(link);
- };
- if ('requestIdleCallback' in window) {
- window.requestIdleCallback(loadDeferredCss, { timeout: 1500 });
- } else {
- window.setTimeout(loadDeferredCss, 250);
- }
- }
// Mockup Video Title Randomization on Load
const SERIES_NAMES = [
'Stranger Things',
@@ -1332,8 +1311,6 @@ document.addEventListener('DOMContentLoaded', () => {
let previousFocus = null;
const openModal = () => {
- const demoStyles = document.querySelector('link[data-landing-demo]');
- if (demoStyles) demoStyles.media = 'all';
previousFocus = document.activeElement && typeof document.activeElement.focus === 'function'
? document.activeElement
: null;
diff --git a/website/build.cjs b/website/build.cjs
index cc09301..d881d91 100644
--- a/website/build.cjs
+++ b/website/build.cjs
@@ -145,23 +145,24 @@ async function compile() {
'styles/alternatives.css',
'styles/demo.css'
];
- // Landing CSS is split by actual render dependency, not by the historical
- // section comments in style.legacy.css. The initial bundle contains every
- // selector used by the background, navigation, hero, interactive mockup,
- // responsive shell, and accessibility states. Below-fold sections load
- // after first paint from app.js. Utility-page rules never reach the landing.
- const landingCriticalModules = [
- 'styles/foundation.css',
- 'styles/hero.css',
- 'styles/legal.css',
- 'styles/landing-controls.css',
- 'styles/landing-demo-gate.css'
- ];
- const landingDemoModules = ['styles/demo.css'];
- const landingDeferredModules = [
- 'styles/landing-primary.css',
- 'styles/landing-sections.css',
- 'styles/landing-faq.css'
+ // Keep the landing cascade equivalent to styleModules while excluding
+ // utility-page-only rules. A single blocking file avoids both request
+ // fan-out and layout shifts from activating structural CSS after paint.
+ const landingBundles = [
+ {
+ key: 'landing',
+ modules: [
+ 'styles/foundation.css',
+ 'styles/hero.css',
+ 'styles/landing-primary.css',
+ 'styles/legal.css',
+ 'styles/landing-controls.css',
+ 'styles/landing-sections.css',
+ 'styles/landing-selfhost.css',
+ 'styles/landing-faq.css',
+ 'styles/demo.css'
+ ]
+ }
];
const styleRaw = styleModules
.map(relativePath => fs.readFileSync(path.join(websiteDir, relativePath), 'utf8'))
@@ -170,24 +171,18 @@ async function compile() {
const styleHash = sha8(styleMin);
const styleName = `style.${styleHash}.min.css`;
const styleSRI = sha384(styleMin);
- const landingStyleRaw = landingCriticalModules
- .map(relativePath => fs.readFileSync(path.join(websiteDir, relativePath), 'utf8'))
- .join('');
- const landingStyleMin = minifyCSS(landingStyleRaw);
- const landingStyleName = `landing.${sha8(landingStyleMin)}.min.css`;
- const landingStyleSRI = sha384(landingStyleMin);
- const landingDemoRaw = landingDemoModules
- .map(relativePath => fs.readFileSync(path.join(websiteDir, relativePath), 'utf8'))
- .join('');
- const landingDemoMin = minifyCSS(landingDemoRaw);
- const landingDemoName = `landing-demo.${sha8(landingDemoMin)}.min.css`;
- const landingDemoSRI = sha384(landingDemoMin);
- const landingDeferredRaw = landingDeferredModules
- .map(relativePath => fs.readFileSync(path.join(websiteDir, relativePath), 'utf8'))
- .join('');
- const landingDeferredMin = minifyCSS(landingDeferredRaw);
- const landingDeferredName = `landing-deferred.${sha8(landingDeferredMin)}.min.css`;
- const landingDeferredSRI = sha384(landingDeferredMin);
+ const compiledLandingBundles = landingBundles.map(bundle => {
+ const raw = bundle.modules
+ .map(relativePath => fs.readFileSync(path.join(websiteDir, relativePath), 'utf8'))
+ .join('');
+ const min = minifyCSS(raw);
+ return {
+ ...bundle,
+ min,
+ name: `${bundle.key === 'landing' ? 'landing' : `landing-${bundle.key}`}.${sha8(min)}.min.css`,
+ sri: sha384(min)
+ };
+ });
const appRaw = fs.readFileSync(path.join(websiteDir, 'app.js'), 'utf8');
const appMin = await minifyJS(appRaw);
@@ -205,9 +200,9 @@ async function compile() {
const appPct = ((1 - appMin.length / appRaw.length) * 100).toFixed(0);
const langPct = ((1 - langMin.length / langRaw.length) * 100).toFixed(0);
console.log(` CSS: ${styleName} (${(styleMin.length/1024).toFixed(1)} KB, -${stylePct}%)`);
- console.log(` Landing CSS: ${landingStyleName} (${(landingStyleMin.length/1024).toFixed(1)} KB)`);
- console.log(` Desktop/modal demo CSS: ${landingDemoName} (${(landingDemoMin.length/1024).toFixed(1)} KB)`);
- console.log(` Deferred landing CSS: ${landingDeferredName} (${(landingDeferredMin.length/1024).toFixed(1)} KB)`);
+ for (const bundle of compiledLandingBundles) {
+ console.log(` Landing ${bundle.key}: ${bundle.name} (${(bundle.min.length/1024).toFixed(1)} KB)`);
+ }
console.log(` App: ${appName} (${(appMin.length/1024).toFixed(1)} KB, -${appPct}%)`);
console.log(` Lang: ${langName} (${(langMin.length/1024).toFixed(1)} KB, -${langPct}%)`);
@@ -221,9 +216,9 @@ async function compile() {
// Write minified files
fs.writeFileSync(path.join(wwwDir, styleName), styleMin);
- fs.writeFileSync(path.join(wwwDir, landingStyleName), landingStyleMin);
- fs.writeFileSync(path.join(wwwDir, landingDemoName), landingDemoMin);
- fs.writeFileSync(path.join(wwwDir, landingDeferredName), landingDeferredMin);
+ for (const bundle of compiledLandingBundles) {
+ fs.writeFileSync(path.join(wwwDir, bundle.name), bundle.min);
+ }
fs.writeFileSync(path.join(wwwDir, appName), appMin);
fs.writeFileSync(path.join(wwwDir, langName), langMin);
@@ -520,15 +515,12 @@ async function compile() {
html = html.replace(/(]*?\bhref=")((?:\.\.\/)*\/?)style\.min\.css"/g, (m, before, prefix) => {
return `${before}${prefix}${styleName}" integrity="${styleSRI}" crossorigin="anonymous"`;
});
- html = html.replace(/(]*?\bhref=")((?:\.\.\/)*\/?)landing\.min\.css"/g, (m, before, prefix) => {
- return `${before}${prefix}${landingStyleName}" integrity="${landingStyleSRI}" crossorigin="anonymous"`;
- });
- html = html.replace(/(]*?\bhref=")((?:\.\.\/)*\/?)landing-demo\.min\.css"/g, (m, before, prefix) => {
- return `${before}${prefix}${landingDemoName}" integrity="${landingDemoSRI}" crossorigin="anonymous"`;
- });
- html = html.replace(/data-deferred-css="((?:\.\.\/)*\/?)landing-deferred\.min\.css"/g, (m, prefix) => {
- return `data-deferred-css="${prefix}${landingDeferredName}" data-deferred-css-integrity="${landingDeferredSRI}"`;
- });
+ for (const bundle of compiledLandingBundles) {
+ const placeholder = bundle.key === 'landing' ? 'landing.min.css' : `landing-${bundle.key}.min.css`;
+ html = html.replace(new RegExp(`(]*?\\bhref=")((?:\\.\\.\\/)*\\/?)${placeholder.replaceAll('.', '\\.') }"`, 'g'), (m, before, prefix) => {
+ return `${before}${prefix}${bundle.name}" integrity="${bundle.sri}" crossorigin="anonymous"`;
+ });
+ }
html = html.replace(/(