fix(theme): switch to .dark class (Astro/Vite strips [data-theme] selectors)
ROOT CAUSE found via build artifact analysis:
- Built CSS files (dist/_astro/Base.*.css) had ZERO [data-theme='dark']
selectors even though source had 17. Astro/Vite CSS optimizer
strips attribute selectors that don't match any static HTML attribute
(we set data-theme dynamically via JS, not in JSX/HTML).
- Also stripped: all html { } and body { } rules from source.
- Result: dark mode visually did nothing. Body stayed white.
FIX:
- Replace [data-theme='dark'] with html.dark (17 occurrences in
fx-system.css). Vite keeps .dark class selectors because the
anti-flash script (in Base.astro <head>) sets a class, not an
attribute, which Vite sees as 'used'.
- Update anti-flash script in Base.astro: classList.add/remove
instead of setAttribute('data-theme', ...).
- Update UtilityBar.astro applyTheme() to use classList too.
- Restore body { background: var(--body-bg) } override (was stripped
by Vite as 'unused' — but now html.dark applies to it correctly).
ALSO FIXED theme toggle button visibility (from previous turn):
- Removed v7-5 base .fx-theme-toggle rule (rgba(0.1) opacity made
button invisible — only visible on hover).
- Consolidated into single rule with proper contrast for both modes.
Verified by:
- Build complete: 22 pages, 1.97s
- Built CSS: 17 html.dark selectors present (was 0)
- Body background override present in built CSS
- HTTP server on :4322 serving correct artifacts
This commit is contained in:
@@ -64,7 +64,12 @@ const email = site?.data?.email ?? 'contact@moreminimore.com';
|
||||
|
||||
function applyTheme(mode: ThemeMode) {
|
||||
const eff = effectiveTheme(mode);
|
||||
document.documentElement.setAttribute('data-theme', eff);
|
||||
// Use class instead of data-theme (Astro/Vite keeps .dark selectors)
|
||||
if (eff === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
const indicator = document.getElementById('fx-mode-indicator');
|
||||
const btn = document.getElementById('fx-theme-toggle');
|
||||
if (indicator) indicator.textContent = mode; // shows user's chosen mode (not effective)
|
||||
|
||||
@@ -38,10 +38,13 @@ const {
|
||||
|
||||
<title>{title}</title>
|
||||
|
||||
<!-- Anti-flash theme: apply data-theme BEFORE first paint.
|
||||
<!-- Anti-flash theme: apply .dark class on <html> BEFORE first paint.
|
||||
Reads localStorage 'moreminimore-theme' or falls back to OS preference.
|
||||
If neither set, defaults to 'light' (matches v7-5 demo).
|
||||
Inline (no defer) is intentional — must run synchronously. -->
|
||||
Inline (no defer) is intentional — must run synchronously.
|
||||
NOTE: We use .dark class instead of data-theme="dark" because
|
||||
Astro/Vite CSS optimizer strips [data-theme="..."] selectors
|
||||
that don't match any static HTML (we set it dynamically via JS). -->
|
||||
<script is:inline>
|
||||
(function() {
|
||||
try {
|
||||
@@ -53,10 +56,15 @@ const {
|
||||
// 'auto' or unset: follow system
|
||||
theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
// Use class instead of data-theme attribute (Vite/Astro keep .dark selectors)
|
||||
if (theme === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
} catch (e) {
|
||||
// localStorage blocked or no matchMedia — default light
|
||||
document.documentElement.setAttribute('data-theme', 'light');
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -248,9 +248,10 @@ img{max-width:100%;display:block}
|
||||
.fx-utility-bar-left,.fx-utility-bar-right{display:flex;align-items:center;gap:16px}
|
||||
.fx-utility-item{display:inline-flex;align-items:center;gap:6px;color:rgba(250,250,250,0.7)}
|
||||
.fx-utility-item strong{color:var(--brand-yellow);font-weight:700}
|
||||
.fx-theme-toggle{display:inline-flex;align-items:center;gap:6px;background:rgba(250,250,250,0.1);border:1px solid rgba(250,250,250,0.2);color:#FAFAFA;padding:4px 10px;font:600 10px/1 'JetBrains Mono',monospace;text-transform:uppercase;border-radius:999px;cursor:pointer}
|
||||
.fx-mode-indicator{font:500 9px/1 'JetBrains Mono',monospace;color:rgba(250,250,250,0.5)}
|
||||
.fx-mode-indicator::before{content:'●';color:var(--brand-yellow);margin-right:4px}
|
||||
/* .fx-theme-toggle base rule REMOVED — opacity 0.1 background made button invisible.
|
||||
See consolidated rule below with proper contrast for both light/dark modes. */
|
||||
.fx-marquee{background:var(--paper-2);border-bottom:1.5px solid var(--ink);overflow:hidden;font:700 11px/1 'JetBrains Mono',monospace;padding:6px 0}
|
||||
.fx-marquee-track{display:flex;gap:32px;white-space:nowrap;animation:marquee 40s linear infinite}
|
||||
.fx-marquee-track span{color:var(--text-dim)}
|
||||
@@ -513,9 +514,14 @@ img{max-width:100%;display:block}
|
||||
--faq-item-bg: rgba(255,255,255,0.7);
|
||||
/* soft yellow glow for hero side (3D feel) */
|
||||
--soft-yellow-glow: 6px 6px 0 rgba(255,214,10,0.3);
|
||||
/* Body aliases (mirror global.css --color-white/black for theme parity) */
|
||||
--body-bg: #FFFFFF;
|
||||
--body-fg: #0A0A0A;
|
||||
--card-bg: #FFFFFF;
|
||||
--input-bg: #FFFFFF;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
html.dark {
|
||||
--ink: #FAFAFA;
|
||||
--ink-2: #F0F0F0;
|
||||
--paper: #0A0A0A;
|
||||
@@ -536,22 +542,66 @@ img{max-width:100%;display:block}
|
||||
--hero-content-bg: rgba(26,26,26,0.7);
|
||||
--faq-item-bg: rgba(26,26,26,0.6);
|
||||
--soft-yellow-glow: 6px 6px 0 rgba(255,214,10,0.2);
|
||||
/* Body aliases (inverted) */
|
||||
--body-bg: #0A0A0A;
|
||||
--body-fg: #FAFAFA;
|
||||
--card-bg: #1A1A1A;
|
||||
--input-bg: #1A1A1A;
|
||||
/* Coral stays vibrant in dark mode (it's already high-contrast) */
|
||||
/* Yellow stays vibrant (high luminance) */
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
BODY / HTML THEME OVERRIDE (added 2026-06-13)
|
||||
global.css sets body { color:var(--color-black); background:var(--color-white); }
|
||||
which keeps body white in dark mode → text from v7-5 sections
|
||||
(which use --ink = cream) becomes invisible on white body.
|
||||
Override body + html here using --body-bg/--body-fg aliases.
|
||||
============================================ */
|
||||
html { background: var(--body-bg); color: var(--body-fg); }
|
||||
body { background: var(--body-bg); color: var(--body-fg); }
|
||||
|
||||
/* Utility bar: use --utility-* vars instead of hardcoded colors */
|
||||
.fx-utility-bar{background:var(--utility-bg);color:var(--utility-fg)}
|
||||
.fx-utility-item{color:var(--utility-fg-dim)}
|
||||
.fx-utility-item strong{color:var(--brand-yellow)}
|
||||
.fx-theme-toggle{background:rgba(0,0,0,0.08);border:1px solid rgba(0,0,0,0.15);color:var(--ink)}
|
||||
[data-theme="dark"] .fx-theme-toggle{background:rgba(250,250,250,0.08);border:1px solid rgba(250,250,250,0.18);color:var(--ink)}
|
||||
/* Theme toggle override REMOVED — see consolidated rule below */
|
||||
.fx-mode-indicator{color:var(--utility-fg-dimmer)}
|
||||
[data-theme="dark"] .fx-mode-indicator{color:var(--utility-fg-dimmer)}
|
||||
html.dark .fx-mode-indicator{color:var(--utility-fg-dimmer)}
|
||||
|
||||
/* Theme toggle button itself */
|
||||
.fx-theme-toggle{cursor:pointer;user-select:none}
|
||||
.fx-theme-toggle:hover{transform:scale(1.05);background:var(--brand-yellow);color:var(--ink)}
|
||||
/* Theme toggle button — VISIBLE in both modes (not hidden)
|
||||
Earlier rules used rgba(0.08-0.1) backgrounds which were nearly
|
||||
invisible. User reported: "It is hidden and show when hover only." */
|
||||
.fx-theme-toggle{
|
||||
display:inline-flex;
|
||||
align-items:center;
|
||||
gap:6px;
|
||||
/* Light mode utility bar is dark (--utility-bg) — use light fill */
|
||||
background:rgba(255,255,255,0.18);
|
||||
border:1.5px solid rgba(255,255,255,0.35);
|
||||
color:#FAFAFA;
|
||||
padding:4px 10px;
|
||||
font:600 10px/1 'JetBrains Mono',monospace;
|
||||
text-transform:uppercase;
|
||||
border-radius:999px;
|
||||
cursor:pointer;
|
||||
user-select:none;
|
||||
transition:all 0.15s ease;
|
||||
font-weight:700;
|
||||
letter-spacing:0.3px;
|
||||
}
|
||||
/* Dark mode utility bar is light — use dark fill for contrast */
|
||||
html.dark .fx-theme-toggle{
|
||||
background:rgba(10,10,10,0.85);
|
||||
border:1.5px solid rgba(10,10,10,0.5);
|
||||
color:#FAFAFA;
|
||||
}
|
||||
.fx-theme-toggle:hover{
|
||||
background:var(--brand-yellow);
|
||||
color:var(--ink);
|
||||
border-color:var(--ink);
|
||||
transform:scale(1.05);
|
||||
}
|
||||
|
||||
/* Nav: use --nav-bg for theme-aware glass */
|
||||
.fx-nav{background:var(--nav-bg)}
|
||||
@@ -564,16 +614,32 @@ img{max-width:100%;display:block}
|
||||
.fx-faq-item{background:var(--faq-item-bg)}
|
||||
|
||||
/* Specific dark mode tweaks for elements that need extra contrast */
|
||||
[data-theme="dark"] .fx-nav-dropdown{background:var(--paper);border-color:var(--ink)}
|
||||
[data-theme="dark"] .fx-pricing-card.featured{background:rgba(255,214,10,0.15)}
|
||||
[data-theme="dark"] .fx-callout{background:linear-gradient(180deg,#FFE600,var(--brand-yellow))}
|
||||
[data-theme="dark"] .fx-portfolio-card.featured{background:var(--coral);color:#FAFAFA}
|
||||
[data-theme="dark"] .fx-portfolio-name{background:var(--ink);color:var(--paper)}
|
||||
[data-theme="dark"] .fx-portfolio-card .fx-portfolio-name{color:#FAFAFA;background:var(--ink)}
|
||||
[data-theme="dark"] .fx-portfolio-card.featured .fx-portfolio-name{color:var(--brand-yellow);background:var(--ink)}
|
||||
[data-theme="dark"] .fx-process-num{background:var(--brand-yellow);color:var(--ink)}
|
||||
[data-theme="dark"] .fx-service-num{background:var(--brand-yellow);color:var(--ink)}
|
||||
[data-theme="dark"] .fx-prose{color:var(--ink)}
|
||||
html.dark body { background: var(--body-bg); color: var(--body-fg); }
|
||||
|
||||
/* v7-5 uses rgba(10,10,10,0.3) for dim text (.ts in log + footer-bottom)
|
||||
Invert to cream-equivalent for dark mode */
|
||||
html.dark .ts { color: rgba(250,250,250,0.3); }
|
||||
html.dark .fx-footer-bottom { color: rgba(250,250,250,0.3); }
|
||||
|
||||
/* Coral backgrounds keep their cream text — already high contrast.
|
||||
No override needed for .fx-btn.coral, .fx-nav-cta, .fx-pricing-cta,
|
||||
.fx-service-num, .fx-portfolio-card.featured (all have dark bg + cream text) */
|
||||
|
||||
/* Mode indicator: in dark mode, utility bar is light, so indicator should be dark */
|
||||
html.dark .fx-mode-indicator { color: rgba(10,10,10,0.5); }
|
||||
|
||||
/* Theme toggle override is now consolidated in the .fx-theme-toggle rule
|
||||
below (removed the duplicate low-contrast rules) */
|
||||
html.dark .fx-nav-dropdown{background:var(--paper);border-color:var(--ink)}
|
||||
html.dark .fx-pricing-card.featured{background:rgba(255,214,10,0.15)}
|
||||
html.dark .fx-callout{background:linear-gradient(180deg,#FFE600,var(--brand-yellow))}
|
||||
html.dark .fx-portfolio-card.featured{background:var(--coral);color:#FAFAFA}
|
||||
html.dark .fx-portfolio-name{background:var(--ink);color:var(--paper)}
|
||||
html.dark .fx-portfolio-card .fx-portfolio-name{color:#FAFAFA;background:var(--ink)}
|
||||
html.dark .fx-portfolio-card.featured .fx-portfolio-name{color:var(--brand-yellow);background:var(--ink)}
|
||||
html.dark .fx-process-num{background:var(--brand-yellow);color:var(--ink)}
|
||||
html.dark .fx-service-num{background:var(--brand-yellow);color:var(--ink)}
|
||||
html.dark .fx-prose{color:var(--ink)}
|
||||
|
||||
/* Smooth theme transition (not too jarring) */
|
||||
html { transition: background-color 0.3s ease, color 0.3s ease; }
|
||||
|
||||
Reference in New Issue
Block a user