Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/generators/web/generate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,15 @@ import { writeFile } from '../../utils/file.mjs';
export async function generate(input) {
const config = getConfig('web');

const template = await readFile(config.templatePath, 'utf-8');
let template = await readFile(config.templatePath, 'utf-8');

// Read the theme script from UI folder
const themeScriptPath = join(import.meta.dirname, 'ui', 'theme-script.mjs');
const themeScriptContent = await readFile(themeScriptPath, 'utf-8');

// Wrap theme script in script tag and replace the placeholder
const inlinedThemeScript = `<script>${themeScriptContent}</script>`;
template = template.replace('${themeScript}', inlinedThemeScript);

// Process all entries: convert JSX to HTML/CSS/JS
const { results, css, chunks } = await processJSXEntries(input, template);
Expand Down
8 changes: 6 additions & 2 deletions src/generators/web/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&family=Open+Sans:ital,wght@0,300..800;1,300..800" />

<!-- Apply theme before paint to avoid Flash of Unstyled Content -->
<script>document.documentElement.setAttribute("data-theme", document.documentElement.style.colorScheme = localStorage.getItem("theme") || (matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"));</script>
<!--
This placeholder is dynamically replaced by generator.mjs.
It injects an inline script that initializes the theme (light/dark)
before the page renders to avoid Flash of Unstyled Content (FOUC).
-->
${themeScript}
<script type="importmap">${importMap}</script>
<script type="speculationrules">${speculationRules}</script>
</head>
Expand Down
36 changes: 36 additions & 0 deletions src/generators/web/ui/theme-script.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use strict';

/**
* This script is designed to be inlined in the <head> of the HTML template.
* It must execute BEFORE the body renders to prevent a Flash of Unstyled Content (FOUC).
*/
(function initializeTheme() {
const THEME_STORAGE_KEY = 'theme';
const THEME_DATA_ATTRIBUTE = 'data-theme';
const DARK_QUERY = '(prefers-color-scheme: dark)';

// 1. Retrieve the user's preference from localStorage
const savedUserPreference = localStorage.getItem(THEME_STORAGE_KEY);

// 2. Determine if the system/browser is currently set to dark mode
const systemSupportsDarkMode = window.matchMedia(DARK_QUERY).matches;

/**
* 3. Logic to determine if 'dark' should be applied:
* - User explicitly saved 'dark'
* - User set preference to 'system' AND system is dark
* - No preference exists yet AND system is dark
*/
const shouldApplyDark =
savedUserPreference === 'dark' ||
(savedUserPreference === 'system' && systemSupportsDarkMode) ||
(!savedUserPreference && systemSupportsDarkMode);

const themeToApply = shouldApplyDark ? 'dark' : 'light';

// 4. Apply the theme attribute to the document element (<html>)
document.documentElement.setAttribute(THEME_DATA_ATTRIBUTE, themeToApply);

// 5. Set color-scheme to ensure browser UI (scrollbars, etc.) matches the theme
document.documentElement.style.colorScheme = themeToApply;
})();
Loading