Vibrant Mode, Brand Mark Wrapper, and Animated Grain Overlay
Differentiated vibrant mode with a distinct background color, added mode-aware brand mark switching in the header, introduced an animated SVG grain noise overlay with per-mode intensity, added --fx-flare-color token for mode-adaptive canvas elements, and extended the --fx-* effect token system.
Changelog - 2026-04-20 (03)
Vibrant Mode, Brand Mark Wrapper, and Animated Grain Overlay
Overview
Three related improvements to the banner-site's visual identity and mode system: vibrant mode now has a visually distinct background, the header brand mark swaps automatically based on light/dark/vibrant mode, and the homepage has an animated static grain noise overlay inspired by the existing bannervc.com Squarespace site.
New Named Brand Colors
Added two new named colors to the emblem theme's :root block in emblem-theme.css:
--color-navy-blue: #0059DE— deep vivid blue--color-blue--fierce: #040bf4— intense electric indigo-blue
These follow the existing convention of named brand colors defined once in :root and referenced by mode-specific token selectors.
Vibrant Mode Background
Previously, vibrant mode shared the same --color-haiti-blue (#141531) background as dark mode, making the two modes nearly indistinguishable. Vibrant mode now uses --color-blue--fierce (#040bf4) as its background — a saturated indigo-blue that immediately communicates the mode switch.
SiteBrandMarkModeWrapper Component
New component at src/components/ui/SiteBrandMarkModeWrapper.astro — renders both a light-background and dark-background brand mark image, toggling visibility via CSS based on the active data-mode attribute on <html>.
How it works:
Renders two
<img>tags inside a height-constrained wrapperCSS selectors
html[data-mode="dark"]andhtml[data-mode="vibrant"]hide the light variant and show the dark variantLight mode shows the light variant by default
No JavaScript required — pure CSS driven by the same
data-modecontract as all theme tokens
Design decisions (improvements over the TWF reference implementation):
Accepts
lightSrc/darkSrcas props instead of hardcoding pathsSupports vibrant mode (TWF only handled light/dark)
No
!important—html[data-mode]ancestor specificity is sufficientNo
.darkclass fallback — banner-site usesdata-modeexclusivelyHeight-constrained wrapper with
h-full w-autoimages for consistent sizing
Header integration:
Light mode shows
trademarks__BannerVC--Brand-Blue.webp(blue mark on light background)Dark and vibrant modes show
trademarks__BannerVC.webp(white/light mark on dark background)
Animated Grain Noise Overlay
Added an SVG feTurbulence-based animated grain effect to the homepage, replicating the static noise texture from the existing bannervc.com Squarespace site.
Implementation:
An inline SVG defines a
<filter id="grain">withfeTurbulence(fractal noise) andfeColorMatrix(desaturation)<animate>cycles the turbulenceseedfrom 0 to 100 over 1.5 seconds on repeat, creating the flickering grainA
::beforepseudo-element on.homepagereferences the filter viafilter: url(#grain)Fixed positioning covers the full viewport;
pointer-events: nonekeeps it non-interactive
Tunable parameters:
baseFrequency="0.40"— grain particle size (lower = larger, higher = finer)numOctaves="3"— noise detail layersdur="6.0s"— flicker animation speed (slowed from 1.5s for subtlety)
Per-mode grain intensity (--fx-grain-opacity):
The grain overlay opacity is now a theme token rather than a hardcoded value. Each mode gets the right intensity for its background:
| Mode | --fx-grain-opacity | Rationale |
| Light | 0.18 | Bright backgrounds wash out noise — needs more punch |
| Dark | 0.12 | Natural contrast on dark navy — already visible |
| Vibrant | 0.20 | Saturated blue bg competes with the grain — extra intensity |
The overlay covers the entire viewport including header and footer (z-index: 9999, pointer-events: none), giving the page a unified film-grain texture across all elements.
Mode-Adaptive Flare Color (--fx-flare-color)
Three.js canvas elements (like FlagPixel) resolve CSS variables once at construction time and bake the color into the WebGL material. When the user toggles modes, the CSS variable changes but the canvas doesn't re-read it.
Solution: Added --fx-flare-color token per mode, plus a MutationObserver in FlagPixel that watches data-mode on <html> and re-resolves the CSS variable to update the Three.js material live.
| Mode | --fx-flare-color | Contrast against background |
| Light | --color-brand-blue-deep (#003DB3) | Dark on ivory |
| Dark | --color-brand-blue-bright (#2B7AFF) | Bright on haiti-blue |
| Vibrant | --color-electric (#60A5FA) | Light blue on fierce blue |
The homepage flare now uses var(--fx-flare-color) instead of var(--color-primary), so the flag is always visible regardless of mode.
Blueprint Documentation
Updated context-v/blueprints/Maintain-Themes-Mode-Across-CSS-Tailwind.md:
Section 8: SiteBrandMarkModeWrapper pattern — motivation, code snippets, integration with the mode system, design decisions, and a porting checklist.
Section 9: Effect tokens (
--fx-*) — the canonical token naming convention, per-mode intensity scaling, how CSS and Three.js components consume the tokens differently, and the porting strategy for new sites.
