diff --git a/apps/docs/src/components/docs/Demo.astro b/apps/docs/src/components/docs/Demo.astro index cf70046..ce1618b 100644 --- a/apps/docs/src/components/docs/Demo.astro +++ b/apps/docs/src/components/docs/Demo.astro @@ -10,19 +10,51 @@ interface Props { showCode?: boolean; /** Language for syntax highlighting (default: html) */ lang?: 'html' | 'css' | 'js'; + /** + * Force a specific theme for this demo ("dark" | "light" | "split") + * - "dark": Demo preview uses dark theme context + * - "light": Demo preview uses light theme context + * - "split": Shows both light and dark side-by-side + * - undefined: Follows system/page theme + */ + theme?: 'dark' | 'light' | 'split'; } -const { code, title, showCode = true, lang = 'html' } = Astro.props; +const { code, title, showCode = true, lang = 'html', theme } = Astro.props; // Clean up the code for display (trim whitespace) const cleanCode = code.trim(); + +// Determine container classes +const previewClass = theme === 'split' ? 'demo-preview demo-preview--split' : 'demo-preview'; ---
{title &&

{title}

} -
- -
+ {theme === 'split' ? ( +
+
+
Light
+
+ +
+
+
+
Dark
+
+ +
+
+
+ ) : theme ? ( +
+ +
+ ) : ( +
+ +
+ )} {showCode && (
@@ -52,6 +84,71 @@ const cleanCode = code.trim(); background: var(--s-surface-default); } + /* Theme island support - forced dark/light contexts */ + .demo-preview[s-theme='dark'] { + background: oklch(12% 0.025 220); + color: oklch(95% 0.01 220); + } + + .demo-preview[s-theme='light'] { + background: oklch(98% 0.005 220); + color: oklch(8% 0.02 220); + } + + /* Split view - side-by-side light and dark */ + .demo-preview--split { + display: grid; + grid-template-columns: 1fr 1fr; + padding: 0; + gap: 1px; + background: var(--s-border-default); + } + + .demo-preview-pane { + display: flex; + flex-direction: column; + } + + .demo-preview-pane[s-theme='light'] { + background: oklch(98% 0.005 220); + color: oklch(8% 0.02 220); + } + + .demo-preview-pane[s-theme='dark'] { + background: oklch(12% 0.025 220); + color: oklch(95% 0.01 220); + } + + .demo-pane-header { + padding: 0.375rem 0.75rem; + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + } + + /* Ensure accessible contrast for labels */ + .demo-preview-pane[s-theme='light'] .demo-pane-header { + color: oklch(45% 0.02 220); + border-bottom: 1px solid oklch(85% 0.01 220); + } + + .demo-preview-pane[s-theme='dark'] .demo-pane-header { + color: oklch(75% 0.02 220); + border-bottom: 1px solid oklch(25% 0.02 220); + } + + .demo-pane-content { + padding: var(--s-space-6); + } + + /* Responsive: stack on mobile */ + @media (max-width: 640px) { + .demo-preview--split { + grid-template-columns: 1fr; + } + } + .demo-code { border-top: 1px solid var(--s-border-default); background: var(--s-surface-sunken); diff --git a/apps/docs/src/content/docs/utilities/functions.mdx b/apps/docs/src/content/docs/utilities/functions.mdx index 948256f..70f351f 100644 --- a/apps/docs/src/content/docs/utilities/functions.mdx +++ b/apps/docs/src/content/docs/utilities/functions.mdx @@ -251,12 +251,76 @@ Respects reduced motion preferences: ### s-dark-aware -Adapts background to color scheme: +A sophisticated dark-awareness system that goes beyond simple color swaps. It provides: + +1. **Container-Relative Theming** - Uses CSS style queries to detect parent theme context, not just system preference +2. **OKLCH Perceptual Contrast** - Auto-computes readable text color based on background lightness +3. **Visual Adaptations** - Subtle border glow prevents bleeding, font weight adjusts for halation + + +

Self-Healing Card

+

Text contrast, border glow, and font weight all adapt automatically.

+
`} +/> + +#### Theme Containers + +Create "dark islands" on light pages (or vice versa) with `s-theme`: + +```html + + + + +
Falls back to OS preference
+``` + + +
+
+ Light Island +

Forced light context

+
+
+
+
+ Dark Island +

Forced dark context

+
+
+
`} +/> + +#### Variants ```html -
Background changes in dark mode
+ +
Full adaptation
+ + +
For inline elements
+ + +
Maximum visual separation
``` +#### Technical Details + +| Feature | Light Mode | Dark Mode | +|---------|------------|-----------| +| Text Color | OKLCH L=0.15 | OKLCH L=0.95 | +| Border Glow | None | 0.5px white @ 8% | +| Font Weight | Base | Base - 50 | + +The font weight reduction counters **halation** - the optical illusion where light text on dark backgrounds appears "fatter" due to light bleed in human vision. + ## Creating Custom Functions Define your own reusable CSS functions: diff --git a/packages/core/src/utils/functions.css b/packages/core/src/utils/functions.css index 78a6a5d..cfe1b9d 100644 --- a/packages/core/src/utils/functions.css +++ b/packages/core/src/utils/functions.css @@ -208,18 +208,152 @@ } /** - * Dark mode aware styling using if() + * Dark Mode Aware Styling + * + * A sophisticated dark-awareness system that goes beyond simple color swaps: + * + * 1. CONTAINER-RELATIVE THEMING + * Uses CSS style queries to detect parent theme context, not just system preference. + * A dark sidebar on a light-mode site will correctly theme its children. + * + * 2. PERCEPTUAL OKLCH CONTRAST + * Uses OKLCH lightness math to ensure text readability on any background. + * No manual light/dark text color definitions needed. + * + * 3. VISUAL ADAPTATION BEYOND COLOR + * - Border glow: Subtle inner highlight prevents dark elements from "bleeding" + * - Font weight: Reduces weight ~50 units to counter halation (light-on-dark bloat) + * + * Usage: + *
+ *
Auto-adapts!
+ *
+ * + *
+ * Content here + *
*/ + +/* ============================================================================= + THEME CONTAINER SETUP + Containers that establish a theme context for descendants + ============================================================================= */ + +/** + * Theme containers - establish dark/light context for style queries + * These create a "theme boundary" that s-dark-aware elements can detect + */ +[s-theme] { + container-name: theme; +} + +[s-theme="dark"] { + --_theme-is-dark: 1; + color-scheme: dark; + background-color: var(--s-surface-sunken, oklch(12% 0.02 220)); + color: var(--s-text-primary, oklch(95% 0.01 220)); +} + +[s-theme="light"] { + --_theme-is-dark: 0; + color-scheme: light; + background-color: var(--s-surface-raised, oklch(98% 0.005 220)); + color: var(--s-text-primary, oklch(15% 0.02 220)); +} + +/* ============================================================================= + DARK-AWARE COMPONENT + Self-healing elements that adapt to their theme context + ============================================================================= */ + [s-dark-aware] { - /* Fallback for non-supporting browsers */ - --_bg: var(--s-surface-raised); - background-color: var(--_bg); + /** + * LAYER 1: System preference baseline (universal fallback) + * Works in all browsers, detects OS-level dark mode + */ + --_da-is-dark: 0; + --_da-bg: var(--s-surface-raised); + + @media (prefers-color-scheme: dark) { + --_da-is-dark: 1; + --_da-bg: var(--s-surface-sunken); + } - --_bg: if( - media(prefers-color-scheme: dark): var(--s-surface-sunken); - else: var(--s-surface-raised); - ); - background-color: var(--_bg); + /** + * LAYER 2: Container-relative theming via style queries + * If inside an [s-theme] container, inherit its theme regardless of system preference + * This enables "dark islands" on light sites and vice versa + */ + @supports (container-type: inline-size) { + @container theme style(--_theme-is-dark: 1) { + --_da-is-dark: 1; + --_da-bg: var(--s-surface-sunken); + } + + @container theme style(--_theme-is-dark: 0) { + --_da-is-dark: 0; + --_da-bg: var(--s-surface-raised); + } + } + + /* Apply computed background */ + background-color: var(--_da-bg); + + /** + * LAYER 3: OKLCH Perceptual Contrast Text + * Automatically compute readable text color based on background lightness + * Uses L=0.55 threshold for WCAG 2.1 AA 4.5:1 contrast + */ + color: var(--s-text-primary); /* Fallback */ + + @supports (color: oklch(from red l c h)) { + color: oklch( + from var(--_da-bg) + /* If background L < 0.55: use light text (L=0.95), else dark text (L=0.15) */ + clamp(0.15, calc((0.55 - l) * 1000 + 0.15), 0.95) + 0 0 + ); + } + + /** + * LAYER 4: Border Glow (Dark Mode Anti-Bleed) + * In dark mode, elements can "bleed" into dark backgrounds. + * A subtle inner highlight (0.5px at 8% white) creates visual separation. + * Only applies when --_da-is-dark is 1. + */ + --_da-glow-opacity: calc(var(--_da-is-dark) * 0.08); + box-shadow: inset 0 0 0 0.5px oklch(1 0 0 / var(--_da-glow-opacity)); + + /** + * LAYER 5: Font Weight Compensation (Halation Correction) + * Light text on dark backgrounds appears "fatter" due to light bleed (halation). + * We reduce font-weight by 50 units in dark mode to maintain visual consistency. + * Uses @property for type-safe font-weight interpolation. + */ + --_da-weight-base: var(--s-font-weight-normal, 400); + --_da-weight-adjustment: calc(var(--_da-is-dark) * 50); + font-weight: calc(var(--_da-weight-base) - var(--_da-weight-adjustment)); +} + +/** + * Variant: s-dark-aware="subtle" + * Less aggressive adaptations for inline/text elements + */ +[s-dark-aware="subtle"] { + /* No background change, just text and weight */ + background-color: transparent; + box-shadow: none; +} + +/** + * Variant: s-dark-aware="strong" + * More pronounced dark mode effects for hero sections + */ +[s-dark-aware="strong"] { + --_da-glow-opacity: calc(var(--_da-is-dark) * 0.12); + box-shadow: + inset 0 0 0 0.5px oklch(1 0 0 / var(--_da-glow-opacity)), + inset 0 1px 0 0 oklch(1 0 0 / calc(var(--_da-is-dark) * 0.05)); } /* =============================================================================