From caab40d9a4abf7482e534b816311890e807211d7 Mon Sep 17 00:00:00 2001 From: Kunthawat Greethong Date: Sun, 14 Jun 2026 09:41:32 +0700 Subject: [PATCH] fix(theme): switch to .dark class (Astro/Vite strips [data-theme] selectors) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 ) 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 --- src/components/UtilityBar.astro | 7 ++- src/layouts/Base.astro | 16 +++-- src/styles/fx-system.css | 102 ++++++++++++++++++++++++++------ 3 files changed, 102 insertions(+), 23 deletions(-) diff --git a/src/components/UtilityBar.astro b/src/components/UtilityBar.astro index 277ab87..3be6039 100644 --- a/src/components/UtilityBar.astro +++ b/src/components/UtilityBar.astro @@ -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) diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro index f7e2b92..fc67f2f 100644 --- a/src/layouts/Base.astro +++ b/src/layouts/Base.astro @@ -38,10 +38,13 @@ const { {title} - + 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). --> diff --git a/src/styles/fx-system.css b/src/styles/fx-system.css index 43703dd..89118d2 100644 --- a/src/styles/fx-system.css +++ b/src/styles/fx-system.css @@ -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; }