// Theme switching with CSS variables
const themeToggle = document.getElementById('theme-toggle');
const root = document.documentElement;
// Load saved theme
const savedTheme = localStorage.getItem('theme') || 'light';
root.setAttribute('data-theme', savedTheme);
themeToggle.addEventListener('click', () => {
const currentTheme = root.getAttribute('data-theme');
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
root.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
});
// Dynamic color adjustment
function setAccentColor(color) {
root.style.setProperty('--color-primary', color);
root.style.setProperty('--color-primary-dark', adjustBrightness(color, -20));
root.style.setProperty('--color-primary-light', adjustBrightness(color, 20));
}
function adjustBrightness(color, percent) {
const num = parseInt(color.replace('#', ''), 16);
const amt = Math.round(2.55 * percent);
const R = (num >> 16) + amt;
const G = (num >> 8 & 0x00FF) + amt;
const B = (num & 0x0000FF) + amt;
return '#' + (
0x1000000 +
(R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
(G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
(B < 255 ? B < 1 ? 0 : B : 255)
).toString(16).slice(1);
}
// Get computed CSS variable value
const primaryColor = getComputedStyle(root).getPropertyValue('--color-primary');
console.log('Primary color:', primaryColor);
// Create color scheme from single color
function createColorScheme(baseColor) {
const colors = {
primary: baseColor,
light: adjustBrightness(baseColor, 20),
dark: adjustBrightness(baseColor, -20),
contrast: getContrastColor(baseColor)
};
Object.entries(colors).forEach(([name, value]) => {
root.style.setProperty(`--color-${name}`, value);
});
}
function getContrastColor(hexColor) {
const r = parseInt(hexColor.substr(1, 2), 16);
const g = parseInt(hexColor.substr(3, 2), 16);
const b = parseInt(hexColor.substr(5, 2), 16);
const yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
return (yiq >= 128) ? '#000000' : '#ffffff';
}
/* Global CSS variables at :root */
:root {
/* Color palette */
--color-primary: #3b82f6;
--color-primary-dark: #2563eb;
--color-primary-light: #60a5fa;
--color-secondary: #8b5cf6;
--color-accent: #f59e0b;
/* Neutral colors */
--color-text: #1f2937;
--color-text-light: #6b7280;
--color-background: #ffffff;
--color-surface: #f9fafb;
--color-border: #e5e7eb;
/* Semantic colors */
--color-success: #10b981;
--color-warning: #f59e0b;
--color-error: #ef4444;
--color-info: #3b82f6;
/* Spacing scale */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
--spacing-2xl: 3rem;
--spacing-3xl: 4rem;
/* Typography */
--font-sans: 'Inter', system-ui, sans-serif;
--font-serif: 'Georgia', serif;
--font-mono: 'Fira Code', monospace;
--font-size-xs: 0.75rem;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--font-size-2xl: 1.5rem;
--font-size-3xl: 2rem;
--line-height-tight: 1.25;
--line-height-normal: 1.5;
--line-height-relaxed: 1.75;
/* Font weights */
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
/* Border radius */
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--radius-lg: 1rem;
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
/* Transitions */
--transition-fast: 150ms ease-in-out;
--transition-base: 250ms ease-in-out;
--transition-slow: 350ms ease-in-out;
/* Layout */
--max-width-sm: 640px;
--max-width-md: 768px;
--max-width-lg: 1024px;
--max-width-xl: 1280px;
/* Z-index scale */
--z-dropdown: 1000;
--z-sticky: 1020;
--z-fixed: 1030;
--z-modal: 1050;
--z-tooltip: 1070;
}
/* Dark theme using data attribute */
[data-theme="dark"] {
--color-text: #f9fafb;
--color-text-light: #d1d5db;
--color-background: #111827;
--color-surface: #1f2937;
--color-border: #374151;
}
/* Using CSS variables */
body {
font-family: var(--font-sans);
font-size: var(--font-size-base);
line-height: var(--line-height-normal);
color: var(--color-text);
background-color: var(--color-background);
}
.container {
max-width: var(--max-width-lg);
margin: 0 auto;
padding: var(--spacing-lg);
}
/* Buttons with variables */
.btn {
padding: var(--spacing-sm) var(--spacing-lg);
font-weight: var(--font-weight-medium);
border-radius: var(--radius-md);
transition: all var(--transition-base);
border: none;
cursor: pointer;
}
.btn-primary {
background-color: var(--color-primary);
color: white;
}
.btn-primary:hover {
background-color: var(--color-primary-dark);
box-shadow: var(--shadow-md);
}
/* Fallback values with var() */
.card {
background: var(--card-bg, var(--color-surface));
padding: var(--card-padding, var(--spacing-lg));
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
}
/* Local scoped variables */
.alert {
--alert-color: var(--color-info);
--alert-bg: color-mix(in srgb, var(--alert-color) 10%, transparent);
background: var(--alert-bg);
border-left: 4px solid var(--alert-color);
padding: var(--spacing-md);
border-radius: var(--radius-md);
}
.alert.success {
--alert-color: var(--color-success);
}
.alert.warning {
--alert-color: var(--color-warning);
}
.alert.error {
--alert-color: var(--color-error);
}
/* Using calc() with variables */
.grid {
display: grid;
gap: var(--spacing-md);
grid-template-columns: repeat(auto-fit, minmax(calc(var(--max-width-sm) / 3), 1fr));
}
.responsive-padding {
padding: calc(var(--spacing-md) * 2) var(--spacing-lg);
}
/* Dynamic spacing scale */
.section {
--section-multiplier: 1;
margin-bottom: calc(var(--spacing-xl) * var(--section-multiplier));
}
.section.large {
--section-multiplier: 2;
}
/* @property for type-safe custom properties */
@property --gradient-angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
.animated-gradient {
background: linear-gradient(
var(--gradient-angle),
var(--color-primary),
var(--color-secondary)
);
animation: rotate 3s linear infinite;
}
@keyframes rotate {
to {
--gradient-angle: 360deg;
}
}
/* Component variants with variables */
.badge {
--badge-size: var(--spacing-md);
--badge-color: var(--color-primary);
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--badge-size);
height: var(--badge-size);
background: var(--badge-color);
color: white;
border-radius: var(--radius-full);
font-size: calc(var(--badge-size) * 0.6);
}
.badge.lg {
--badge-size: calc(var(--spacing-md) * 2);
}
/* Media queries with variables */
@media (min-width: 768px) {
:root {
--spacing-scale: 1.5;
}
.container {
padding: calc(var(--spacing-lg) * var(--spacing-scale));
}
}
/* CSS variables with JavaScript */
/*
// JavaScript
document.documentElement.style.setProperty('--color-primary', '#ff0000');
const primaryColor = getComputedStyle(document.documentElement)
.getPropertyValue('--color-primary');
*/
/* Environment variables for safe areas */
.header {
padding-top: env(safe-area-inset-top);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
/* Color manipulation with color-mix() */
.hover-lighten:hover {
background: color-mix(in srgb, var(--color-primary) 80%, white);
}
.hover-darken:hover {
background: color-mix(in srgb, var(--color-primary) 80%, black);
}