mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-07-26 12:08:15 +00:00
Restore landing CSS cascade stability
This commit is contained in:
+1
-1
@@ -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/`.
|
||||
|
||||
@@ -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;
|
||||
|
||||
+42
-50
@@ -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(/(<link\b[^>]*?\bhref=")((?:\.\.\/)*\/?)style\.min\.css"/g, (m, before, prefix) => {
|
||||
return `${before}${prefix}${styleName}" integrity="${styleSRI}" crossorigin="anonymous"`;
|
||||
});
|
||||
html = html.replace(/(<link\b[^>]*?\bhref=")((?:\.\.\/)*\/?)landing\.min\.css"/g, (m, before, prefix) => {
|
||||
return `${before}${prefix}${landingStyleName}" integrity="${landingStyleSRI}" crossorigin="anonymous"`;
|
||||
});
|
||||
html = html.replace(/(<link\b[^>]*?\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(`(<link\\b[^>]*?\\bhref=")((?:\\.\\.\\/)*\\/?)${placeholder.replaceAll('.', '\\.') }"`, 'g'), (m, before, prefix) => {
|
||||
return `${before}${prefix}${bundle.name}" integrity="${bundle.sri}" crossorigin="anonymous"`;
|
||||
});
|
||||
}
|
||||
html = html.replace(/(<script\b[^>]*?\bsrc=")((?:\.\.\/)*\/?)app\.min\.js"/g, (m, before, prefix) => {
|
||||
return `${before}${prefix}${appName}" integrity="${appSRI}" crossorigin="anonymous"`;
|
||||
});
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/* Initial mobile shell for the deferred interactive hero demo. */
|
||||
@media (max-width: 900px) {
|
||||
.hero-grid > .hero-mockup-wrapper {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.hero-github-cta {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.btn-demo-mobile {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
.selfhost-mascot {
|
||||
display: block;
|
||||
width: 300px;
|
||||
height: auto;
|
||||
margin: 0 auto;
|
||||
filter: drop-shadow(0 4px 12px rgba(0,0,0,0.15));
|
||||
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
.selfhost-mascot:hover {
|
||||
transform: scale(1.05) translateY(-4px) rotate(-1deg);
|
||||
}
|
||||
@@ -11,7 +11,6 @@
|
||||
<link rel="dns-prefetch" href="https://github.com">
|
||||
|
||||
<link rel="stylesheet" href="{{ASSET_PATH}}landing.min.css">
|
||||
<link rel="stylesheet" href="{{ASSET_PATH}}landing-demo.min.css" media="(min-width: 769px)" data-landing-demo>
|
||||
<link rel="preload" as="image" href="{{ASSET_PATH}}assets/LookDownKoala.avif" imagesrcset="{{ASSET_PATH}}assets/LookDownKoala-1x.avif 90w, {{ASSET_PATH}}assets/LookDownKoala.avif 205w" imagesizes="90px" type="image/avif" fetchpriority="high">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ASSET_PATH}}assets/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ASSET_PATH}}assets/favicon-32x32.png">
|
||||
@@ -209,7 +208,7 @@
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body data-deferred-css="{{ASSET_PATH}}landing-deferred.min.css">
|
||||
<body>
|
||||
<div class="scroll-progress-bar"></div>
|
||||
|
||||
<a class="skip-link" href="#main-content">Skip to main content</a>
|
||||
|
||||
Reference in New Issue
Block a user