Changelog - 2026-01-19 (#1)
Emblem Theme System Implementation
Overview
Implemented a complete multi-mode theming system for banner-site (Banner VC) using the established dark-matter pattern. The theme is named "emblem" to keep the client name out of the codebase. Supports three modes: light (clean, professional), dark (bold, confident), and vibrant (high-impact expressive).
Architecture
Theme Token Hierarchy
:root (Brand-level named colors)
├── --color-crimson (#B91C1C) Primary red
├── --color-crimson-bright (#DC2626) Brighter red for dark modes
├── --color-haiti-blue (#141531) Dark mode background
├── --color-navy (#1D4ED8) Secondary blue
├── --color-navy-bright (#2563EB) Brighter blue for dark modes
├── --color-amber (#B45309) Accent warm
├── --color-electric (#60A5FA) Electric blue for vibrant
├── --color-ivory (#F9FAFB) Light mode background
├── --color-midnight (#172554) Alternative dark background
├── --color-charcoal (#171717) Light mode text
├── --color-snow (#FFFFFF) Dark mode text
└── --color-graphite-* (50-950) Neutral grey scale
[data-theme="emblem"][data-mode="light|dark|vibrant"]
├── --color-background Core background
├── --color-foreground Core text color
├── --color-surface Card/panel backgrounds
├── --color-surface-muted Subdued surfaces
├── --color-primary Primary action color
├── --color-primary-foreground Text on primary
├── --color-secondary Secondary action color
├── --color-accent Accent highlights
├── --color-muted Muted backgrounds
├── --color-muted-foreground Subdued text
├── --color-border Border color
├── --color-input Input backgrounds
├── --color-ring Focus ring color
└── Effect tokens (--fx-*)
├── --fx-glow-opacity
├── --fx-glow-spread
├── --fx-glow-color
├── --fx-text-shadow
├── --fx-hero-gradient
├── --fx-hero-bg
├── --fx-card-bg
├── --fx-card-border
├── --fx-card-shadow
└── --fx-headline-gradient
Key Technical Decisions
1. Tailwind 4 Integration
Used @tailwindcss/vite plugin (not deprecated @astrojs/tailwind):
// astro.config.mjs
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
vite: {
plugins: [tailwindcss()],
},
});
2. Custom Color Utilities with @theme inline
Tailwind 4 requires explicit registration of custom colors for utility generation. Used @theme inline to enable utilities like bg-background/80:
@theme inline {
--color-background: var(--color-background);
--color-foreground: var(--color-foreground);
--color-surface: var(--color-surface);
/* ... etc */
}
3. color-mix() for Opacity Variants
Since Tailwind can't compute opacity on CSS variable colors, we defined custom utilities using color-mix():
@layer utilities {
.bg-background\/80 {
background-color: color-mix(in srgb, var(--color-background) 80%, transparent);
}
.border-border\/20 {
border-color: color-mix(in srgb, var(--color-border) 20%, transparent);
}
}
4. Derived Colors from Background
To ensure changing --color-background propagates throughout the theme, surface and effect colors are derived using color-mix():
[data-theme="emblem"][data-mode="dark"] {
--color-background: var(--color-haiti-blue);
/* Derived from background */
--color-surface: color-mix(in srgb, var(--color-background) 85%, white);
--color-surface-muted: color-mix(in srgb, var(--color-background) 70%, black);
--color-muted: color-mix(in srgb, var(--color-background) 80%, white);
--color-border: color-mix(in srgb, var(--color-background) 70%, white);
/* Effect tokens also derived */
--fx-hero-bg: var(--color-background);
--fx-card-bg: color-mix(in srgb, var(--color-background) 80%, transparent);
}
Files Created
src/styles/global.css
Base Tailwind 4 setup with:
@import "tailwindcss";@theme inlineblock for custom color registration- Breakpoint CSS variables
- Typography system variables
- Transition variables for smooth theme changes
- Custom utility classes for semantic colors
- Responsive visibility utilities
src/styles/themes/emblem-theme.css
Complete theme definition with:
:rootblock with brand-level named colors- Light mode semantic tokens and effect tokens
- Dark mode semantic tokens and effect tokens
- Vibrant mode semantic tokens and effect tokens
- Helper classes (
.card,.btn-primary,.btn-secondary) - Hero section helpers (
.hero-shell,.hero-panel) - Glass morphism variant (
.card-glass) - Gradient text effect (
.gradient-text)
src/layouts/BaseThemeLayout.astro
Layout wrapper that:
- Sets
<html class="theme-emblem" data-theme="emblem" data-mode={mode}> - Imports global.css and emblem-theme.css
- Includes Header component
- Props:
title,description,mode,hideHeader
src/components/ui/ModeToggle.astro
Mode toggle component with:
- Three icons: sun (light), moon (dark), star (vibrant)
- LocalStorage persistence via
emblem-modekey - System preference detection on initial load
- Click cycles through modes: light → dark → vibrant → light
src/components/basics/Header.astro
Navigation header with:
- Sticky positioning with backdrop blur
- Brand logo and site title
- Desktop navigation with dropdown menu
- Mobile hamburger menu with slide-in animation
- Mode toggle integration
- Emblem-specific styling (red/blue gradient underlines)
src/pages/test.astro
Test page demonstrating:
- All three theme modes
- Color token swatches
- Card components
- Button variants
- Glass morphism card
- Hero section
- Gradient text effect
Mode Characteristics
| Mode | Background | Primary | Effects |
|---|---|---|---|
| Light | Ivory (#F9FAFB) | Crimson (#B91C1C) | Minimal shadows |
| Dark | Haiti Blue (#141531) | Crimson Bright (#DC2626) | Moderate glow |
| Vibrant | Haiti Blue (#141531) | Loud Red (#EF4444) | Maximum glow, dramatic shadows |
Usage
Basic Page
---
import BaseThemeLayout from '../layouts/BaseThemeLayout.astro';
---
<BaseThemeLayout title="My Page" mode="dark">
<div class="hero-shell">
<section class="container mx-auto px-4 py-16">
<h1 class="gradient-text text-4xl font-bold">Hello</h1>
<div class="card p-6">Card content</div>
<button class="btn-primary">Action</button>
</section>
</div>
</BaseThemeLayout>
Changing Theme Background
Edit the dark mode --color-background in emblem-theme.css:
[data-theme="emblem"][data-mode="dark"] {
--color-background: var(--color-haiti-blue); /* Change this */
}
All derived colors (surface, muted, border, card backgrounds) will automatically update.
Known Issues / Future Work
Hardcoded Colors to Address
Several effect tokens still use hardcoded rgba values instead of referencing theme variables:
-
Light mode glow colors:
rgba(185, 28, 28, ...)should usecolor-mix(in srgb, var(--color-crimson) X%, transparent) -
Vibrant mode primary colors:
#EF4444,#3B82F6,#FBBF24should be defined as named variables (--color-crimson-loud,--color-navy-loud,--color-amber-loud) -
Muted foreground in dark mode:
#94A3B8should reference graphite scale -
Hero panel gradients: Use
var(--color-primary)andvar(--color-secondary)with color-mix instead of hardcoded rgba
Dependencies
tailwindcss: ^4.1.18@tailwindcss/vite: ^4.1.18astro: ^5.16.11
No additional npm packages required. Theme is pure CSS with CSS variables.