Changelog - 2025-12-04 (#3)
Dark Matter Site: Mode-Aware Hero Components and Astro CSS Scoping Fix
Overview
Refactored hero section variants (v5, v6, v7) to support all three visual modes (light, dark, vibrant) with dynamic switching. The key breakthrough was discovering that Astro's CSS scoping prevents [data-mode] selectors from working without the :global() wrapper.
Changes by Area
1. Mode-Aware Hero Components
Reorganized hero variants into two folders:
src/layouts/sections/narratives/
├── vibrant/ # Original vibrant-only designs (preserved)
│ ├── WhyNowWhyUs-v5.astro
│ ├── WhyNowWhyUs-v6.astro
│ └── WhyNowWhyUs-v7.astro
└── needs-modes/ # Mode-aware versions with light/dark/vibrant support
├── WhyNowWhyUs-v5.astro
└── WhyNowWhyUs-v6.astro
Mode Support Features:
- Light mode: Clean white background, subtle purple accents, dark text
- Dark mode: Deep void background, moderate glows, light text
- Vibrant mode: Glowing purple borders, text shadows, animated effects, gradient backgrounds
2. Astro CSS Scoping Fix (The Breakthrough)
Problem: Mode-specific CSS selectors weren't applying. Components rendered flat with no borders/glows even when data-mode="vibrant" was set.
Root Cause: Astro scopes <style> blocks by adding unique attributes to selectors. When writing:
[data-mode="vibrant"] .card-inner { ... }
Astro transforms it to:
[data-mode="vibrant"][data-astro-cid-xyz] .card-inner[data-astro-cid-xyz] { ... }
Since data-mode is on <html> (outside the component), <html> doesn't have the component's scope attribute, so the selector never matches.
Solution: Wrap mode selectors with :global():
/* Before - doesn't work */
[data-mode="vibrant"] .card-inner { border: 1px solid purple; }
/* After - works! */
:global([data-mode="vibrant"]) .card-inner { border: 1px solid purple; }
Applied bulk replacement across v5, v6, v7:
[data-mode="light"]→:global([data-mode="light"])[data-mode="dark"]→:global([data-mode="dark"])[data-mode="vibrant"]→:global([data-mode="vibrant"])
3. Mode-Aware Orb Component
Updated ImageAbstract--Orb--Half.astro to respond to mode changes:
-
Added
--fx-orb-colorCSS token tomatter-theme.cssfor each mode:- Light:
var(--color-marjorelle-purple)(dark purple visible on light bg) - Dark:
var(--color-alabaster)(white visible on dark void) - Vibrant:
#ffffff(bright white for maximum contrast)
- Light:
-
Changed default
colorprop from'#ffffff'to'auto' -
Added
getOrbColor()function that reads--fx-orb-colorCSS variable -
Added MutationObserver watching
data-modeattribute changes on<html> -
Three.js color uniform updates in real-time when mode switches
4. Effect Tokens in Theme
Added --fx-* effect tokens to src/styles/themes/matter-theme.css:
/* Per-mode effect tokens */
--fx-glow-opacity: 0.08 | 0.25 | 0.5;
--fx-glow-spread: 10px | 25px | 50px;
--fx-glow-color: rgba(102, 67, 226, X);
--fx-text-shadow: none | 0 0 40px... | 0 0 80px...;
--fx-text-glow: ...;
--fx-hero-gradient: ...;
--fx-orbit-color: ...;
--fx-orbit-glow: ...;
--fx-node-glow: ...;
--fx-card-bg: ...;
--fx-card-border: ...;
--fx-card-border-hover: ...;
--fx-card-shadow: ...;
--fx-card-shadow-hover: ...;
--fx-headline-gradient: ...;
--fx-orb-color: ...;
5. Design System Pages
Created two design system pages for comparison:
/design-system/vibrant— Shows original vibrant-only designs/design-system/needs-modes— Shows mode-aware versions with mode switcher buttons
6. Documentation
Created issue resolution document at:
context-v/Resolving-Mode-Switching-Across-Multiple-Components.md
Documents the problem, failed attempts, root cause discovery, and solution for future reference.
Files Changed Summary
New Files
src/layouts/sections/narratives/vibrant/WhyNowWhyUs-v5.astro— Original v5 preservedsrc/layouts/sections/narratives/vibrant/WhyNowWhyUs-v6.astro— Original v6 preservedsrc/layouts/sections/narratives/vibrant/WhyNowWhyUs-v7.astro— Original v7 preservedsrc/layouts/sections/narratives/needs-modes/WhyNowWhyUs-v5.astro— Mode-aware v5src/layouts/sections/narratives/needs-modes/WhyNowWhyUs-v6.astro— Mode-aware v6src/pages/design-system/vibrant.astro— Vibrant showcase pagesrc/pages/design-system/needs-modes.astro— Mode switcher test pagecontext-v/Resolving-Mode-Switching-Across-Multiple-Components.md— Documentation
Modified Files
src/components/flare/ImageAbstract--Orb--Half.astro— Mode-aware color handlingsrc/styles/themes/matter-theme.css— Added--fx-*effect tokens and--fx-orb-color
Key Lessons Learned
-
Astro CSS Scoping: When targeting attributes on
<html>or<body>from within a component, use:global()to prevent Astro from scoping that part of the selector. -
Start From Working Code: When adding modes to a component, start from the working vibrant version and add light/dark support, rather than abstracting everything and rebuilding.
-
Three.js + CSS Variables: WebGL components can't use CSS directly but can read computed CSS variable values via JavaScript and update uniforms. Use MutationObserver to watch for attribute changes.
Follow-Ups
- Apply same
:global()pattern to any future mode-aware components - Consider extracting the mode-switching pattern into a reusable mixin or utility
- Test mode transitions on mobile devices
- Finalize which hero variant to use for production