From 72d4f04526685623bc0a6481a6d3ed0e66397286 Mon Sep 17 00:00:00 2001
From: KoalaDev <6156589+Shik3i@users.noreply.github.com>
Date: Sat, 11 Jul 2026 15:48:09 +0200
Subject: [PATCH] Split landing CSS by render priority
---
docs/AI_INIT.md | 4 +-
website/README.md | 3 +-
website/app.js | 23 +
website/build.cjs | 68 +-
website/style.css | 6594 +-------------------------
website/style.legacy.css | 6576 +++++++++++++++++++++++++
website/styles/alternatives.css | 23 +
website/styles/demo.css | 1652 +++++++
website/styles/foundation.css | 1418 ++++++
website/styles/hero.css | 911 ++++
website/styles/join-spinner.css | 41 +
website/styles/join.css | 141 +
website/styles/landing-controls.css | 332 ++
website/styles/landing-demo-gate.css | 14 +
website/styles/landing-faq.css | 213 +
website/styles/landing-primary.css | 1215 +++++
website/styles/landing-sections.css | 462 ++
website/styles/legal.css | 168 +
website/template.html | 6 +-
19 files changed, 13281 insertions(+), 6583 deletions(-)
create mode 100644 website/style.legacy.css
create mode 100644 website/styles/alternatives.css
create mode 100644 website/styles/demo.css
create mode 100644 website/styles/foundation.css
create mode 100644 website/styles/hero.css
create mode 100644 website/styles/join-spinner.css
create mode 100644 website/styles/join.css
create mode 100644 website/styles/landing-controls.css
create mode 100644 website/styles/landing-demo-gate.css
create mode 100644 website/styles/landing-faq.css
create mode 100644 website/styles/landing-primary.css
create mode 100644 website/styles/landing-sections.css
create mode 100644 website/styles/legal.css
diff --git a/docs/AI_INIT.md b/docs/AI_INIT.md
index 8a6674c..13b4593 100644
--- a/docs/AI_INIT.md
+++ b/docs/AI_INIT.md
@@ -22,7 +22,7 @@ KoalaSync is a specialized tool for **synchronized video playback** across multi
- `server/`: Node.js Relay Server using Socket.IO (WebSocket-only).
- `website/`: **Landing Page** & Invitation Bridge (Marketing, Tutorials, and Downloads).
- **`build.cjs`**: Zero-dependency static site compiler. Translates `template.html` + `locales/*.json` → `www/`. Also minifies CSS/JS automatically.
- - **`www/` is auto-generated**: Never edit files in `www/` directly. Always edit source files (`template.html`, `style.css`, `app.js`, `lang-init.js`, `locales/*.json`) and run `node website/build.cjs` to regenerate. CSS/JS are output as `.min.*` files — a built-in cleanup step removes stale artifacts on each build.
+ - **`www/` is auto-generated**: Never edit files in `www/` directly. Always edit source files (`template.html`, `style.css`, `styles/*.css`, `app.js`, `lang-init.js`, `locales/*.json`) and run `node website/build.cjs` to regenerate. `style.css` is the development manifest; `style.legacy.css` is an unloaded, byte-identical migration reference. Production creates page-specific and render-priority CSS bundles from `styles/*.css`; CSS/JS are output as `.min.*` files and stale generated assets are removed on each build.
- `shared/`: **Single Source of Truth** for protocol constants, event names, blacklist data, and generated usernames.
- `scripts/`: Development utilities (e.g., `build-extension.cjs`).
- `docker-compose.yml`: Root-level orchestration for the relay server.
@@ -149,7 +149,7 @@ Before starting any task, committing, or pushing, you **MUST** run `git pull --r
3. Implement the handler in `server/index.js` and `background.js`.
### Making Website Changes
-1. Edit source files in `website/` (`template.html`, `style.css`, `app.js`, `lang-init.js`, or `locales/*.json`).
+1. Edit source files in `website/` (`template.html`, `style.css`, `styles/*.css`, `app.js`, `lang-init.js`, or `locales/*.json`).
2. Run the compiler: `node website/build.cjs`. This generates the multilingual pages in `www/` and minifies CSS/JS.
3. Verify the output: `node --check website/www/app.min.js && node --check website/www/lang-init.min.js`.
4. Test locally: `npx serve website/www` or `python3 -m http.server 8080 -d website/www`.
diff --git a/website/README.md b/website/README.md
index d9b640c..b982136 100644
--- a/website/README.md
+++ b/website/README.md
@@ -4,7 +4,7 @@ This directory contains the static KoalaSync website. It is both the public mark
## Where You Are
-- `template.html`, `style.css`, `app.js`, `lang-init.js`, and `locales/*.json` are source files.
+- `template.html`, `style.css`, `styles/*.css`, `app.js`, `lang-init.js`, and `locales/*.json` are source files. `style.css` is the local-development manifest; production concatenates the modules in the order declared by `website/build.cjs`.
- `build.cjs` compiles the static site into `website/www/`.
- `website/www/` is generated output. Do not edit files there directly.
- `join.html` handles room-invite links and communicates with the extension through `bridge.js`.
@@ -33,6 +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.
- **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 f11d24a..6de2394 100644
--- a/website/app.js
+++ b/website/app.js
@@ -1,6 +1,27 @@
// 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',
@@ -1311,6 +1332,8 @@ 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 748a142..cc09301 100644
--- a/website/build.cjs
+++ b/website/build.cjs
@@ -132,11 +132,62 @@ async function compile() {
// ── 1. Minify CSS/JS (must happen first so hashes go into HTML) ──
console.log('Minifying CSS/JS...');
- const styleRaw = fs.readFileSync(path.join(websiteDir, 'style.css'), 'utf8');
+ const styleModules = [
+ 'styles/foundation.css',
+ 'styles/hero.css',
+ 'styles/landing-primary.css',
+ 'styles/legal.css',
+ 'styles/landing-controls.css',
+ 'styles/join-spinner.css',
+ 'styles/landing-sections.css',
+ 'styles/join.css',
+ 'styles/landing-faq.css',
+ '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'
+ ];
+ const styleRaw = styleModules
+ .map(relativePath => fs.readFileSync(path.join(websiteDir, relativePath), 'utf8'))
+ .join('');
const styleMin = minifyCSS(styleRaw);
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 appRaw = fs.readFileSync(path.join(websiteDir, 'app.js'), 'utf8');
const appMin = await minifyJS(appRaw);
@@ -154,6 +205,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)`);
console.log(` App: ${appName} (${(appMin.length/1024).toFixed(1)} KB, -${appPct}%)`);
console.log(` Lang: ${langName} (${(langMin.length/1024).toFixed(1)} KB, -${langPct}%)`);
@@ -167,6 +221,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);
fs.writeFileSync(path.join(wwwDir, appName), appMin);
fs.writeFileSync(path.join(wwwDir, langName), langMin);
@@ -463,6 +520,15 @@ 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}"`;
+ });
html = html.replace(/(