diff --git a/apps/demos-static/public/shared.css b/apps/demos-static/public/shared.css
index f2ba8aa..68e0876 100644
--- a/apps/demos-static/public/shared.css
+++ b/apps/demos-static/public/shared.css
@@ -385,3 +385,142 @@ pre {
flex-direction: column;
}
}
+
+body.theme-dark {
+ background: #000;
+ color: #39ff14;
+ font-family: "Courier New", "Lucida Console", Monaco, monospace;
+ font-size: 1rem;
+ line-height: 1.56;
+}
+
+body.theme-light {
+ background: #fffff0;
+ color: #1b1610;
+ font-family: "Brush Script MT", "Segoe Script", "Snell Roundhand", "Apple Chancery", cursive;
+ font-size: 1.12rem;
+ line-height: 1.68;
+}
+
+body.theme-dark .topline a,
+body.theme-dark .site-theme-toolbar,
+body.theme-dark .kicker,
+body.theme-dark h1,
+body.theme-dark h2,
+body.theme-dark h3,
+body.theme-dark label,
+body.theme-dark strong,
+body.theme-dark a {
+ color: #7cff7c;
+ font-family: inherit;
+}
+
+body.theme-dark p,
+body.theme-dark span,
+body.theme-dark li,
+body.theme-dark .note,
+body.theme-dark .catalog-card p,
+body.theme-dark .result-row span,
+body.theme-dark .stats span {
+ color: #39ff14;
+}
+
+body.theme-dark .hero,
+body.theme-dark .panel,
+body.theme-dark .catalog-card,
+body.theme-dark .drop-zone,
+body.theme-dark .result-row,
+body.theme-dark .stats span {
+ background: #000;
+ border-color: #1fbf3a;
+ color: #39ff14;
+ box-shadow: 0 0 16px rgba(57, 255, 20, 0.16);
+}
+
+body.theme-dark button,
+body.theme-dark .download-link,
+body.theme-dark .site-theme-option {
+ background: #000;
+ border: 1px solid #39ff14;
+ color: #39ff14;
+ font-family: inherit;
+}
+
+body.theme-dark button:hover,
+body.theme-dark .download-link:hover,
+body.theme-dark .site-theme-option.is-active {
+ background: #062b06;
+ color: #b6ffb6;
+}
+
+body.theme-dark select,
+body.theme-dark input,
+body.theme-dark textarea,
+body.theme-dark canvas,
+body.theme-dark .output,
+body.theme-dark pre {
+ background: #000;
+ border-color: #1fbf3a;
+ color: #39ff14;
+ font-family: inherit;
+}
+
+body.theme-light .topline a,
+body.theme-light .site-theme-toolbar,
+body.theme-light .kicker,
+body.theme-light h1,
+body.theme-light h2,
+body.theme-light h3,
+body.theme-light label,
+body.theme-light strong,
+body.theme-light a,
+body.theme-light p,
+body.theme-light span,
+body.theme-light li,
+body.theme-light .note,
+body.theme-light .catalog-card p,
+body.theme-light .result-row span,
+body.theme-light .stats span {
+ color: #1b1610;
+ font-family: inherit;
+}
+
+body.theme-light .hero,
+body.theme-light .panel,
+body.theme-light .catalog-card,
+body.theme-light .drop-zone,
+body.theme-light .result-row,
+body.theme-light .stats span {
+ background: #fffff0;
+ border-color: #1b1610;
+ color: #1b1610;
+ box-shadow: none;
+}
+
+body.theme-light button,
+body.theme-light .download-link,
+body.theme-light .site-theme-option {
+ background: #fffff0;
+ border: 1px solid #1b1610;
+ color: #1b1610;
+ font-family: inherit;
+}
+
+body.theme-light button:hover,
+body.theme-light .download-link:hover,
+body.theme-light .site-theme-option.is-active {
+ background: #1b1610;
+ color: #fffff0;
+}
+
+body.theme-light select,
+body.theme-light input,
+body.theme-light textarea,
+body.theme-light canvas,
+body.theme-light .output,
+body.theme-light pre {
+ background: #fffff0;
+ border-color: #1b1610;
+ color: #1b1610;
+ font-family: inherit;
+}
diff --git a/apps/demos-static/public/theme.js b/apps/demos-static/public/theme.js
index 88dbd75..cddaaa4 100644
--- a/apps/demos-static/public/theme.js
+++ b/apps/demos-static/public/theme.js
@@ -1,32 +1,40 @@
-const demoThemeButtons = [];
const demoThemeStorageKey = 'site-theme';
+const legacyDemoThemeStorageKey = 'cv-theme';
function normalizeDemoTheme(theme) {
- return theme === 'light' ? 'light' : 'dark';
+ return theme === 'light' || theme === 'fancy' ? 'light' : 'dark';
}
function getStoredDemoTheme() {
try {
- return localStorage.getItem(demoThemeStorageKey);
+ return localStorage.getItem(demoThemeStorageKey) || localStorage.getItem(legacyDemoThemeStorageKey);
} catch (_error) {
return null;
}
}
function storeDemoTheme(theme) {
+ const nextTheme = normalizeDemoTheme(theme);
try {
- localStorage.setItem(demoThemeStorageKey, theme);
+ localStorage.setItem(demoThemeStorageKey, nextTheme);
+ localStorage.removeItem(legacyDemoThemeStorageKey);
} catch (_error) {
}
}
+function getDemoThemeButtons() {
+ return [...document.querySelectorAll('[data-site-theme]')];
+}
+
function setDemoTheme(theme) {
const nextTheme = normalizeDemoTheme(theme);
+ document.documentElement.classList.toggle('theme-light', nextTheme === 'light');
+ document.documentElement.classList.toggle('theme-dark', nextTheme === 'dark');
document.body.classList.toggle('theme-light', nextTheme === 'light');
document.body.classList.toggle('theme-dark', nextTheme === 'dark');
- demoThemeButtons.forEach((button) => {
- const active = button.dataset.siteTheme === nextTheme;
+ getDemoThemeButtons().forEach((button) => {
+ const active = normalizeDemoTheme(button.dataset.siteTheme) === nextTheme;
button.classList.toggle('is-active', active);
button.setAttribute('aria-pressed', active ? 'true' : 'false');
});
@@ -43,12 +51,13 @@ function installDemoThemeToolbar() {
toolbar.setAttribute('aria-label', 'Theme');
toolbar.innerHTML = 'Theme|';
nav.appendChild(toolbar);
-
- demoThemeButtons.push(...toolbar.querySelectorAll('[data-site-theme]'));
- demoThemeButtons.forEach((button) => {
- button.addEventListener('click', () => setDemoTheme(button.dataset.siteTheme));
- });
}
+document.addEventListener('click', (event) => {
+ const button = event.target.closest('[data-site-theme]');
+ if (!button) return;
+ setDemoTheme(button.dataset.siteTheme);
+});
+
installDemoThemeToolbar();
setDemoTheme(getStoredDemoTheme() || 'dark');
diff --git a/apps/website/blog.php b/apps/website/blog.php
index a701f31..b7d67f0 100644
--- a/apps/website/blog.php
+++ b/apps/website/blog.php
@@ -890,7 +890,7 @@ function renderStackSourceLinks(string $stackKey, array $sourceLinks, string $so
diff --git a/apps/website/cv-theme.js b/apps/website/cv-theme.js
index 13a0c30..404a9ec 100644
--- a/apps/website/cv-theme.js
+++ b/apps/website/cv-theme.js
@@ -1,4 +1,3 @@
-const siteThemeButtons = [...document.querySelectorAll('[data-site-theme]')];
const cvPortrait = document.getElementById('cv-portrait-orbit');
const siteThemeStorageKey = 'site-theme';
const legacyCvThemeStorageKey = 'cv-theme';
@@ -16,20 +15,27 @@ function getStoredSiteTheme() {
}
function storeSiteTheme(theme) {
+ const nextTheme = normalizeSiteTheme(theme);
try {
- localStorage.setItem(siteThemeStorageKey, theme);
+ localStorage.setItem(siteThemeStorageKey, nextTheme);
localStorage.removeItem(legacyCvThemeStorageKey);
} catch (_error) {
}
}
+function getSiteThemeButtons() {
+ return [...document.querySelectorAll('[data-site-theme]')];
+}
+
function setSiteTheme(theme) {
const nextTheme = normalizeSiteTheme(theme);
+ document.documentElement.classList.toggle('theme-light', nextTheme === 'light');
+ document.documentElement.classList.toggle('theme-dark', nextTheme === 'dark');
document.body.classList.toggle('theme-light', nextTheme === 'light');
document.body.classList.toggle('theme-dark', nextTheme === 'dark');
- siteThemeButtons.forEach((button) => {
- const active = button.dataset.siteTheme === nextTheme;
+ getSiteThemeButtons().forEach((button) => {
+ const active = normalizeSiteTheme(button.dataset.siteTheme) === nextTheme;
button.classList.toggle('is-active', active);
button.setAttribute('aria-pressed', active ? 'true' : 'false');
});
@@ -37,8 +43,10 @@ function setSiteTheme(theme) {
storeSiteTheme(nextTheme);
}
-siteThemeButtons.forEach((button) => {
- button.addEventListener('click', () => setSiteTheme(button.dataset.siteTheme));
+document.addEventListener('click', (event) => {
+ const button = event.target.closest('[data-site-theme]');
+ if (!button) return;
+ setSiteTheme(button.dataset.siteTheme);
});
document.addEventListener('pointermove', (event) => {
diff --git a/apps/website/cv.php b/apps/website/cv.php
index 7a7e15e..d71b48e 100644
--- a/apps/website/cv.php
+++ b/apps/website/cv.php
@@ -259,7 +259,7 @@ $recruiterKeys = [
diff --git a/apps/website/index.php b/apps/website/index.php
index 86ba17e..c68b781 100644
--- a/apps/website/index.php
+++ b/apps/website/index.php
@@ -155,7 +155,7 @@ $homeProofCards = [
diff --git a/apps/website/partials/translation_ui.php b/apps/website/partials/translation_ui.php
index 6bad0f1..37ddeb8 100644
--- a/apps/website/partials/translation_ui.php
+++ b/apps/website/partials/translation_ui.php
@@ -44,6 +44,6 @@
const SAVE_URL = '/save_lang.php';
const CURRENT_LANG = '';
const STATIC_LANGS = ;
- if (typeof OTHER_PAGES === 'undefined') { var OTHER_PAGES = []; }
+ window.OTHER_PAGES = Array.isArray(window.OTHER_PAGES) ? window.OTHER_PAGES : [];
diff --git a/apps/website/styles.css b/apps/website/styles.css
index d8ab0bf..ba9fe2f 100644
--- a/apps/website/styles.css
+++ b/apps/website/styles.css
@@ -2019,3 +2019,235 @@ body.theme-light .source-links a {
font-size: 2.35rem;
}
}
+
+body.theme-dark {
+ background: #000;
+ color: #39ff14;
+ font-family: "Courier New", "Lucida Console", Monaco, monospace;
+ font-size: 1rem;
+ line-height: 1.56;
+}
+
+body.theme-light {
+ background: #fffff0;
+ color: #1b1610;
+ font-family: "Brush Script MT", "Segoe Script", "Snell Roundhand", "Apple Chancery", cursive;
+ font-size: 1.12rem;
+ line-height: 1.68;
+}
+
+body.theme-dark .top-nav,
+body.theme-dark .site-theme-toolbar,
+body.theme-dark .nav-left,
+body.theme-dark .nav-right a,
+body.theme-dark a,
+body.theme-dark .blog-kicker,
+body.theme-dark .section-kicker,
+body.theme-dark .demo-label,
+body.theme-dark h1,
+body.theme-dark h2,
+body.theme-dark h3,
+body.theme-dark h4,
+body.theme-dark strong {
+ color: #7cff7c;
+ font-family: inherit;
+}
+
+body.theme-dark p,
+body.theme-dark li,
+body.theme-dark label,
+body.theme-dark span,
+body.theme-dark .blog-subtitle,
+body.theme-dark .hero-role,
+body.theme-dark .bio-intro,
+body.theme-dark .bio-story,
+body.theme-dark .cta,
+body.theme-dark .diagram-caption,
+body.theme-dark .source-links span,
+body.theme-dark .visitor-idea-card footer {
+ color: #39ff14;
+}
+
+body.theme-dark .container,
+body.theme-dark.home-page .hero,
+body.theme-dark .blog-hero,
+body.theme-dark .demos-hero,
+body.theme-dark .architecture-section,
+body.theme-dark .case-studies,
+body.theme-dark .activity-log,
+body.theme-dark .homelab-todo,
+body.theme-dark .tech-notes,
+body.theme-dark .home-proof-card,
+body.theme-dark .case-card,
+body.theme-dark .demo-card,
+body.theme-dark .message,
+body.theme-dark .todo-list li,
+body.theme-dark .activity-list li,
+body.theme-dark .visitor-idea-card,
+body.theme-dark .idea-status,
+body.theme-dark .source-links a,
+body.theme-dark .cv-impact-grid p,
+body.theme-dark .cv-skill-grid article,
+body.theme-dark .cv-recruiter-panel,
+body.theme-dark .cv-proof-section,
+body.theme-dark .cv-skill-section {
+ background: #000;
+ border-color: #1fbf3a;
+ color: #39ff14;
+ box-shadow: 0 0 16px rgba(57, 255, 20, 0.16);
+}
+
+body.theme-dark .hero-actions a,
+body.theme-dark .cv-recruiter-actions a,
+body.theme-dark .case-card a,
+body.theme-dark .idea-form button,
+body.theme-dark .site-theme-option {
+ background: #000;
+ border-color: #39ff14;
+ color: #39ff14;
+ font-family: inherit;
+}
+
+body.theme-dark .site-theme-option.is-active,
+body.theme-dark .hero-actions a:hover,
+body.theme-dark .cv-recruiter-actions a:hover,
+body.theme-dark .case-card a:hover,
+body.theme-dark .idea-form button:hover {
+ background: #062b06;
+ color: #b6ffb6;
+}
+
+body.theme-dark .idea-form input,
+body.theme-dark .idea-form textarea,
+body.theme-dark .diagram-shell {
+ background: #000;
+ border-color: #1fbf3a;
+ color: #39ff14;
+ font-family: inherit;
+}
+
+body.theme-dark .diagram-zone,
+body.theme-dark .diagram-node rect {
+ fill: #000;
+ stroke: #1fbf3a;
+}
+
+body.theme-dark .diagram-zone-title,
+body.theme-dark .diagram-node text,
+body.theme-dark .diagram-link-label,
+body.theme-dark .diagram-node .diagram-small,
+body.theme-dark .diagram-small {
+ fill: #39ff14;
+}
+
+body.theme-dark .diagram-link {
+ stroke: #39ff14;
+}
+
+body.theme-light .top-nav,
+body.theme-light .site-theme-toolbar,
+body.theme-light .nav-left,
+body.theme-light .nav-right a,
+body.theme-light a,
+body.theme-light .blog-kicker,
+body.theme-light .section-kicker,
+body.theme-light .demo-label,
+body.theme-light h1,
+body.theme-light h2,
+body.theme-light h3,
+body.theme-light h4,
+body.theme-light strong {
+ color: #1b1610;
+ font-family: inherit;
+}
+
+body.theme-light p,
+body.theme-light li,
+body.theme-light label,
+body.theme-light span,
+body.theme-light .blog-subtitle,
+body.theme-light .hero-role,
+body.theme-light .bio-intro,
+body.theme-light .bio-story,
+body.theme-light .cta,
+body.theme-light .diagram-caption,
+body.theme-light .source-links span,
+body.theme-light .visitor-idea-card footer {
+ color: #1b1610;
+}
+
+body.theme-light .container,
+body.theme-light.home-page .hero,
+body.theme-light .blog-hero,
+body.theme-light .demos-hero,
+body.theme-light .architecture-section,
+body.theme-light .case-studies,
+body.theme-light .activity-log,
+body.theme-light .homelab-todo,
+body.theme-light .tech-notes,
+body.theme-light .home-proof-card,
+body.theme-light .case-card,
+body.theme-light .demo-card,
+body.theme-light .message,
+body.theme-light .todo-list li,
+body.theme-light .activity-list li,
+body.theme-light .visitor-idea-card,
+body.theme-light .idea-status,
+body.theme-light .source-links a,
+body.theme-light .cv-impact-grid p,
+body.theme-light .cv-skill-grid article,
+body.theme-light .cv-recruiter-panel,
+body.theme-light .cv-proof-section,
+body.theme-light .cv-skill-section {
+ background: #fffff0;
+ border-color: #1b1610;
+ color: #1b1610;
+ box-shadow: none;
+}
+
+body.theme-light .hero-actions a,
+body.theme-light .cv-recruiter-actions a,
+body.theme-light .case-card a,
+body.theme-light .idea-form button,
+body.theme-light .site-theme-option {
+ background: #fffff0;
+ border-color: #1b1610;
+ color: #1b1610;
+ font-family: inherit;
+}
+
+body.theme-light .site-theme-option.is-active,
+body.theme-light .hero-actions a:hover,
+body.theme-light .cv-recruiter-actions a:hover,
+body.theme-light .case-card a:hover,
+body.theme-light .idea-form button:hover {
+ background: #1b1610;
+ color: #fffff0;
+}
+
+body.theme-light .idea-form input,
+body.theme-light .idea-form textarea,
+body.theme-light .diagram-shell {
+ background: #fffff0;
+ border-color: #1b1610;
+ color: #1b1610;
+ font-family: inherit;
+}
+
+body.theme-light .diagram-zone,
+body.theme-light .diagram-node rect {
+ fill: #fffff0;
+ stroke: #1b1610;
+}
+
+body.theme-light .diagram-zone-title,
+body.theme-light .diagram-node text,
+body.theme-light .diagram-link-label,
+body.theme-light .diagram-node .diagram-small,
+body.theme-light .diagram-small {
+ fill: #1b1610;
+}
+
+body.theme-light .diagram-link {
+ stroke: #1b1610;
+}