basis-css
Version:
Professional CSS framework with adaptive color system
821 lines (717 loc) • 30.6 kB
JavaScript
// Professional CSS Framework - Adaptive Color System
// Color system state
let colorSystem = {
mode: 'monochrome', // 'monochrome', 'one-color', 'two-colors', 'three-colors'
colors: {
accent: '#3b82f6',
primary: '#3b82f6',
secondary: '#8b5cf6',
tertiary: '#ec4899'
}
};
// Framework name data
const frameworkNames = {
'basis': 'Basis CSS',
'basiscss': 'BasisCSS',
'basis-framework': 'Basis Framework',
'basis-design': 'Basis Design',
'basiscss-ui': 'BasisCSS UI',
'basis-ui': 'Basis UI'
};
// Color mode descriptions
const modeDescriptions = {
'monochrome': 'Sophisticated offblack and offwhite palette. Timeless, professional, works in any industry.',
'one-color': 'Monochrome foundation with one strategic highlight color. Used sparingly for impact.',
'two-colors': 'Natural hierarchy with primary and secondary colors creating balanced design.',
'three-colors': 'Rich layered design with primary, secondary, and tertiary spread throughout.'
};
// Neutral baselines for light and dark themes
const BASE_NEUTRALS = {
light: {
background: '#fdfefe',
surface: '#ffffff',
surfaceRaised: '#f6f8fb',
surfaceMuted: '#eef2f7',
border: '#dce3ef',
borderSubtle: '#edf1f8',
borderStrong: '#c5cfdd',
text: '#0f172a',
textSecondary: '#4b5563',
textMuted: '#6b7280'
},
dark: {
background: '#05070b',
surface: '#090c11',
surfaceRaised: '#0f141d',
surfaceMuted: '#161c27',
border: '#1f2633',
borderSubtle: '#171d27',
borderStrong: '#2a323f',
text: '#f1f5f9',
textSecondary: '#c7d2e2',
textMuted: '#94a3b8'
}
};
// Baseline semantic colors that will be shifted toward the active palette
const STATUS_BASE = {
success: '#10b981',
warning: '#f59e0b',
danger: '#ef4444',
info: '#3b82f6'
};
// Utility helpers -----------------------------------------------------------
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
function hexToRgb(hex) {
const clean = hex.replace('#', '');
const bigint = parseInt(clean, 16);
return {
r: (bigint >> 16) & 255,
g: (bigint >> 8) & 255,
b: bigint & 255
};
}
function rgbToHex({ r, g, b }) {
const toHex = (n) => clamp(Math.round(n), 0, 255).toString(16).padStart(2, '0');
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
function rgbToHsl({ r, g, b }) {
const nr = r / 255;
const ng = g / 255;
const nb = b / 255;
const max = Math.max(nr, ng, nb);
const min = Math.min(nr, ng, nb);
const delta = max - min;
let h = 0;
if (delta !== 0) {
switch (max) {
case nr:
h = ((ng - nb) / delta) % 6;
break;
case ng:
h = (nb - nr) / delta + 2;
break;
default:
h = (nr - ng) / delta + 4;
break;
}
h *= 60;
if (h < 0) h += 360;
}
const l = (max + min) / 2;
const s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
return { h, s, l };
}
function hslToRgb({ h, s, l }) {
const c = (1 - Math.abs(2 * l - 1)) * s;
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
const m = l - c / 2;
let r = 0;
let g = 0;
let b = 0;
if (0 <= h && h < 60) {
r = c;
g = x;
} else if (60 <= h && h < 120) {
r = x;
g = c;
} else if (120 <= h && h < 180) {
g = c;
b = x;
} else if (180 <= h && h < 240) {
g = x;
b = c;
} else if (240 <= h && h < 300) {
r = x;
b = c;
} else {
r = c;
b = x;
}
const to255 = (n) => clamp(Math.round((n + m) * 255), 0, 255);
return { r: to255(r), g: to255(g), b: to255(b) };
}
function adjustLightness(hex, amount) {
const hsl = rgbToHsl(hexToRgb(hex));
hsl.l = clamp(hsl.l + amount / 100, 0, 1);
return rgbToHex(hslToRgb(hsl));
}
function adjustSaturation(hex, amount) {
const hsl = rgbToHsl(hexToRgb(hex));
hsl.s = clamp(hsl.s + amount / 100, 0, 1);
return rgbToHex(hslToRgb(hsl));
}
function shiftHue(hex, amount) {
const hsl = rgbToHsl(hexToRgb(hex));
hsl.h = (hsl.h + amount) % 360;
if (hsl.h < 0) hsl.h += 360;
return rgbToHex(hslToRgb(hsl));
}
function mixColors(colorA, colorB, weight) {
const w = clamp(weight, 0, 1);
const rgbA = hexToRgb(colorA);
const rgbB = hexToRgb(colorB);
return rgbToHex({
r: rgbA.r * (1 - w) + rgbB.r * w,
g: rgbA.g * (1 - w) + rgbB.g * w,
b: rgbA.b * (1 - w) + rgbB.b * w
});
}
function hexToRgba(hex, alpha) {
const { r, g, b } = hexToRgb(hex);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
function hexToRgbString(hex) {
const { r, g, b } = hexToRgb(hex);
return `${r}, ${g}, ${b}`;
}
function getRelativeLuminance(hex) {
const { r, g, b } = hexToRgb(hex);
const channel = (value) => {
const v = value / 255;
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
};
const R = channel(r);
const G = channel(g);
const B = channel(b);
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
}
function getReadableTextColor(hex, theme) {
const luminance = getRelativeLuminance(hex);
const darkContrast = (luminance + 0.05) / 0.05;
const lightContrast = 1.05 / (luminance + 0.05);
const prefersLightText = theme === 'dark' || lightContrast > darkContrast;
return prefersLightText ? '#ffffff' : '#0b1011';
}
function getTintWeights(count) {
switch (count) {
case 0:
return { light: 0, dark: 0 };
case 1:
return { light: 0.035, dark: 0.05 };
case 2:
return { light: 0.048, dark: 0.065 };
default:
return { light: 0.058, dark: 0.075 };
}
}
function tintNeutralBase(basePalette, colors, weight, theme) {
if (!colors.length || weight <= 0) {
return { ...basePalette };
}
const themeScalar = theme === 'light' ? 0.4 : 0.5;
const baseline = clamp(weight * themeScalar, 0, theme === 'light' ? 0.08 : 0.12);
const surfaceWeight = clamp(baseline + (theme === 'light' ? 0.004 : 0.007), 0, theme === 'light' ? 0.1 : 0.14);
const raisedWeight = clamp(baseline + (theme === 'light' ? 0.008 : 0.012), 0, theme === 'light' ? 0.12 : 0.16);
const subtleWeight = clamp(baseline - 0.005, 0, 0.08);
const borderWeight = clamp(baseline - 0.008, 0, 0.06);
const borderStrongWeight = clamp(baseline, 0, 0.1);
const textSecondaryWeight = clamp(baseline - 0.01, 0, 0.06);
const tint = (color, w) =>
colors.reduce((acc, accent) => mixColors(acc, accent, w), color);
const backgroundWeight = clamp(baseline * (theme === 'light' ? 0.65 : 0.8), 0, theme === 'light' ? 0.08 : 0.12);
return {
background: tint(basePalette.background, backgroundWeight),
surface: tint(basePalette.surface, surfaceWeight),
surfaceRaised: tint(basePalette.surfaceRaised, raisedWeight),
surfaceMuted: tint(basePalette.surfaceMuted, raisedWeight),
border: tint(basePalette.border, borderWeight),
borderSubtle: tint(basePalette.borderSubtle, subtleWeight),
borderStrong: tint(basePalette.borderStrong, borderStrongWeight),
text: basePalette.text,
textSecondary: tint(basePalette.textSecondary, textSecondaryWeight),
textMuted: tint(basePalette.textMuted, textSecondaryWeight)
};
}
function buildIntentPalette(hex, themeNeutrals, theme) {
if (!hex) {
return null;
}
const baseColor =
theme === 'light'
? adjustLightness(hex, 0)
: adjustLightness(hex, -8);
const hoverColor =
theme === 'light'
? adjustLightness(baseColor, -6)
: adjustLightness(baseColor, -5);
const activeColor =
theme === 'light'
? adjustLightness(baseColor, -12)
: adjustLightness(baseColor, -9);
const contrastText = getReadableTextColor(baseColor, theme);
const alphaBase = theme === 'light' ? 0.14 : 0.22;
const alphaHover = theme === 'light' ? 0.22 : 0.28;
const alphaActive = theme === 'light' ? 0.28 : 0.34;
const softBase = mixColors(themeNeutrals.surfaceMuted, baseColor, theme === 'light' ? 0.12 : 0.18);
const softBorder = mixColors(themeNeutrals.border, baseColor, theme === 'light' ? 0.18 : 0.24);
return {
base: baseColor,
hover: hoverColor,
active: activeColor,
contrast: contrastText,
softHex: softBase,
softBorderHex: softBorder,
soft: hexToRgba(baseColor, alphaBase),
softHover: hexToRgba(baseColor, alphaHover),
softActive: hexToRgba(baseColor, alphaActive)
};
}
function computeActiveColors(mode, colors) {
const defaultAccent = '#3b82f6';
const suppliedPrimary = colors.primary || colors.accent || defaultAccent;
const suppliedSecondary = colors.secondary || shiftHue(suppliedPrimary, 35);
const suppliedTertiary = colors.tertiary || shiftHue(suppliedSecondary, -55);
switch (mode) {
case 'monochrome':
return {
accent: '#1e293b',
primary: '#1e293b',
secondary: '#cbd5e1',
tertiary: '#e2e8f0'
};
case 'one-color': {
const accent = colors.accent || suppliedPrimary;
return {
accent,
primary: accent,
secondary: adjustSaturation(adjustLightness(accent, 6), -25),
tertiary: adjustLightness(accent, 22)
};
}
case 'two-colors':
return {
accent: suppliedPrimary,
primary: suppliedPrimary,
secondary: suppliedSecondary,
tertiary: mixColors(suppliedPrimary, suppliedSecondary, 0.5)
};
case 'three-colors':
default:
return {
accent: suppliedPrimary,
primary: suppliedPrimary,
secondary: colors.secondary || suppliedSecondary,
tertiary: colors.tertiary || suppliedTertiary
};
}
}
function generatePalette(mode, colors) {
const active = computeActiveColors(mode, colors);
const colorPool = [active.primary, active.secondary, active.tertiary].filter(Boolean);
const tintWeights = getTintWeights(colorPool.length);
const lightNeutrals = tintNeutralBase(BASE_NEUTRALS.light, colorPool, tintWeights.light, 'light');
const darkNeutrals = tintNeutralBase(BASE_NEUTRALS.dark, colorPool, tintWeights.dark, 'dark');
const lightPrimary = buildIntentPalette(active.primary, lightNeutrals, 'light');
const darkPrimary = buildIntentPalette(active.primary, darkNeutrals, 'dark');
const lightSecondary = buildIntentPalette(active.secondary, lightNeutrals, 'light');
const darkSecondary = buildIntentPalette(active.secondary, darkNeutrals, 'dark');
const lightTertiary = buildIntentPalette(active.tertiary, lightNeutrals, 'light');
const darkTertiary = buildIntentPalette(active.tertiary, darkNeutrals, 'dark');
const accentForFocus = active.accent || active.primary || '#2563eb';
const influence = colorPool.length ? 0.12 + colorPool.length * 0.03 : 0.08;
const blendStatus = (baseHex) => mixColors(baseHex, active.primary || baseHex, influence);
return {
light: {
background: lightNeutrals.background,
surface: lightNeutrals.surface,
surfaceRaised: lightNeutrals.surfaceRaised,
surfaceMuted: lightNeutrals.surfaceMuted,
border: lightNeutrals.border,
borderSubtle: lightNeutrals.borderSubtle,
borderStrong: lightNeutrals.borderStrong,
text: lightNeutrals.text,
textSecondary: lightNeutrals.textSecondary,
textMuted: lightNeutrals.textMuted,
primary: lightPrimary?.base || lightNeutrals.text,
primaryHover: lightPrimary?.hover || adjustLightness(lightNeutrals.text, -8),
primaryActive: lightPrimary?.active || adjustLightness(lightNeutrals.text, -16),
primaryContrast: lightPrimary?.contrast || '#ffffff',
primarySoft: lightPrimary?.soft || hexToRgba(lightNeutrals.text, 0.12),
primarySoftHover: lightPrimary?.softHover || hexToRgba(lightNeutrals.text, 0.2),
primarySoftActive: lightPrimary?.softActive || hexToRgba(lightNeutrals.text, 0.26),
primarySoftBorder: lightPrimary?.softBorderHex || mixColors(lightNeutrals.border, lightNeutrals.text, 0.25),
secondary: lightSecondary?.soft || hexToRgba(lightNeutrals.textSecondary, 0.12),
secondaryHover: lightSecondary?.softHover || hexToRgba(lightNeutrals.textSecondary, 0.18),
secondaryActive: lightSecondary?.softActive || hexToRgba(lightNeutrals.textSecondary, 0.22),
secondaryContrast: lightSecondary?.contrast || lightNeutrals.text,
secondarySolid: lightSecondary?.base || lightNeutrals.textSecondary,
secondarySolidHover: lightSecondary?.hover || adjustLightness(lightNeutrals.textSecondary, -6),
secondarySolidActive: lightSecondary?.active || adjustLightness(lightNeutrals.textSecondary, -10),
tertiary: lightTertiary?.soft || hexToRgba(lightNeutrals.textMuted, 0.14),
tertiaryHover: lightTertiary?.softHover || hexToRgba(lightNeutrals.textMuted, 0.2),
tertiaryActive: lightTertiary?.softActive || hexToRgba(lightNeutrals.textMuted, 0.26),
tertiaryContrast: lightTertiary?.contrast || lightNeutrals.text,
tertiarySolid: lightTertiary?.base || lightNeutrals.textMuted,
tertiarySolidHover: lightTertiary?.hover || adjustLightness(lightNeutrals.textMuted, -6),
tertiarySolidActive: lightTertiary?.active || adjustLightness(lightNeutrals.textMuted, -10),
focusRing: hexToRgba(accentForFocus, 0.4),
selectCaret: lightNeutrals.text,
success: blendStatus(STATUS_BASE.success),
warning: blendStatus(STATUS_BASE.warning),
danger: blendStatus(STATUS_BASE.danger),
info: blendStatus(STATUS_BASE.info),
cardBorder: hexToRgba(lightNeutrals.border, 0.12),
cardBorderInner: hexToRgba(lightNeutrals.borderSubtle, 0.1)
},
dark: {
background: darkNeutrals.background,
surface: darkNeutrals.surface,
surfaceRaised: darkNeutrals.surfaceRaised,
surfaceMuted: darkNeutrals.surfaceMuted,
border: darkNeutrals.border,
borderSubtle: darkNeutrals.borderSubtle,
borderStrong: darkNeutrals.borderStrong,
text: darkNeutrals.text,
textSecondary: darkNeutrals.textSecondary,
textMuted: darkNeutrals.textMuted,
primary: darkPrimary?.base || adjustLightness(darkNeutrals.text, -10),
primaryHover: darkPrimary?.hover || adjustLightness(darkNeutrals.text, -6),
primaryActive: darkPrimary?.active || adjustLightness(darkNeutrals.text, -2),
primaryContrast: darkPrimary?.contrast || '#0d1214',
primarySoft: darkPrimary?.soft || hexToRgba(darkNeutrals.text, 0.22),
primarySoftHover: darkPrimary?.softHover || hexToRgba(darkNeutrals.text, 0.28),
primarySoftActive: darkPrimary?.softActive || hexToRgba(darkNeutrals.text, 0.34),
primarySoftBorder: darkPrimary?.softBorderHex || mixColors(darkNeutrals.border, darkNeutrals.text, 0.3),
secondary: darkSecondary?.soft || hexToRgba(darkNeutrals.textSecondary, 0.18),
secondaryHover: darkSecondary?.softHover || hexToRgba(darkNeutrals.textSecondary, 0.24),
secondaryActive: darkSecondary?.softActive || hexToRgba(darkNeutrals.textSecondary, 0.3),
secondaryContrast: darkSecondary?.contrast || darkNeutrals.text,
secondarySolid: darkSecondary?.base || darkNeutrals.textSecondary,
secondarySolidHover: darkSecondary?.hover || adjustLightness(darkNeutrals.textSecondary, 8),
secondarySolidActive: darkSecondary?.active || adjustLightness(darkNeutrals.textSecondary, 12),
tertiary: darkTertiary?.soft || hexToRgba(darkNeutrals.textMuted, 0.22),
tertiaryHover: darkTertiary?.softHover || hexToRgba(darkNeutrals.textMuted, 0.28),
tertiaryActive: darkTertiary?.softActive || hexToRgba(darkNeutrals.textMuted, 0.34),
tertiaryContrast: darkTertiary?.contrast || darkNeutrals.text,
tertiarySolid: darkTertiary?.base || darkNeutrals.textMuted,
tertiarySolidHover: darkTertiary?.hover || adjustLightness(darkNeutrals.textMuted, 8),
tertiarySolidActive: darkTertiary?.active || adjustLightness(darkNeutrals.textMuted, 12),
focusRing: hexToRgba(accentForFocus, 0.45),
selectCaret: darkNeutrals.text,
success: blendStatus(STATUS_BASE.success),
warning: blendStatus(STATUS_BASE.warning),
danger: blendStatus(STATUS_BASE.danger),
info: blendStatus(STATUS_BASE.info),
cardBorder: hexToRgba(darkNeutrals.border, 0.22),
cardBorderInner: hexToRgba(darkNeutrals.borderSubtle, 0.2)
}
};
}
function getActiveScheme() {
const root = document.documentElement;
const forcedScheme = root.getAttribute('data-color-scheme');
if (forcedScheme === 'light' || forcedScheme === 'dark') {
return forcedScheme;
}
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
}
function applyActiveSchemeTokens(root, schemePalette) {
if (!schemePalette) return;
const set = (name, value) => {
if (value !== undefined && value !== null) {
root.style.setProperty(name, value);
}
};
set('--color-background', schemePalette.background);
set('--color-surface', schemePalette.surface);
set('--color-surface-raised', schemePalette.surfaceRaised);
set('--color-surface-muted', schemePalette.surfaceMuted);
set('--color-text', schemePalette.text);
set('--color-text-secondary', schemePalette.textSecondary);
set('--color-text-muted', schemePalette.textMuted);
set('--color-border', schemePalette.border);
set('--color-border-light', schemePalette.borderSubtle);
set('--color-border-secondary', schemePalette.borderStrong);
set('--color-primary', schemePalette.primary);
set('--color-primary-hover', schemePalette.primaryHover);
set('--color-primary-active', schemePalette.primaryActive);
set('--color-primary-soft', schemePalette.primarySoft);
set('--color-primary-soft-hover', schemePalette.primarySoftHover);
set('--color-primary-soft-active', schemePalette.primarySoftActive);
set('--color-primary-soft-border', schemePalette.primarySoftBorder);
set('--color-btn-primary-text', schemePalette.primaryContrast);
set('--color-secondary', schemePalette.secondary);
set('--color-secondary-hover', schemePalette.secondaryHover);
set('--color-secondary-active', schemePalette.secondaryActive);
set('--color-secondary-solid', schemePalette.secondarySolid);
set('--color-secondary-solid-hover', schemePalette.secondarySolidHover);
set('--color-secondary-solid-active', schemePalette.secondarySolidActive);
set('--color-tertiary', schemePalette.tertiary);
set('--color-tertiary-hover', schemePalette.tertiaryHover);
set('--color-tertiary-active', schemePalette.tertiaryActive);
set('--color-tertiary-solid', schemePalette.tertiarySolid);
set('--color-tertiary-solid-hover', schemePalette.tertiarySolidHover);
set('--color-tertiary-solid-active', schemePalette.tertiarySolidActive);
set('--color-focus-ring', schemePalette.focusRing);
set('--color-select-caret', schemePalette.selectCaret);
set('--color-card-border', schemePalette.cardBorder);
set('--color-card-border-inner', schemePalette.cardBorderInner);
set('--color-success', schemePalette.success);
set('--color-warning', schemePalette.warning);
set('--color-error', schemePalette.danger);
set('--color-danger', schemePalette.danger);
set('--color-info', schemePalette.info);
set('--color-success-rgb', hexToRgbString(schemePalette.success));
set('--color-warning-rgb', hexToRgbString(schemePalette.warning));
set('--color-error-rgb', hexToRgbString(schemePalette.danger));
set('--color-info-rgb', hexToRgbString(schemePalette.info));
set('--color-mode', `'${colorSystem.mode}'`);
}
// Initialize the demo
document.addEventListener('DOMContentLoaded', function() {
initializeColorMode();
initializeColorInputs();
updateColorSystem();
const schemeMedia = window.matchMedia('(prefers-color-scheme: dark)');
if (schemeMedia) {
const schemeListener = () => updateColorSystem();
if (schemeMedia.addEventListener) {
schemeMedia.addEventListener('change', schemeListener);
} else if (schemeMedia.addListener) {
schemeMedia.addListener(schemeListener);
}
}
// Add event listeners for color mode buttons
const modeButtons = document.querySelectorAll('.mode-btn');
modeButtons.forEach(btn => {
btn.addEventListener('click', function() {
const mode = this.dataset.mode;
switchColorMode(mode);
});
});
// Add event listeners for color inputs
['accent', 'primary', 'secondary', 'tertiary'].forEach(colorType => {
const colorInput = document.getElementById(colorType + 'Color');
const textInput = document.getElementById(colorType + 'Color-text');
if (colorInput && textInput) {
colorInput.addEventListener('input', (e) => {
textInput.value = e.target.value;
updateColorValue(colorType, e.target.value);
});
textInput.addEventListener('input', (e) => {
if (isValidHexColor(e.target.value)) {
colorInput.value = e.target.value;
updateColorValue(colorType, e.target.value);
}
});
}
});
});
// Initialize color mode to monochrome
function initializeColorMode() {
colorSystem.mode = 'monochrome';
const modeButtons = document.querySelectorAll('.mode-btn');
modeButtons.forEach(btn => {
btn.classList.toggle('active', btn.dataset.mode === 'monochrome');
});
}
// Initialize color inputs with default values
function initializeColorInputs() {
const colorInputs = {
'accent': '#3b82f6',
'primary': '#3b82f6',
'secondary': '#8b5cf6',
'tertiary': '#ec4899'
};
Object.entries(colorInputs).forEach(([type, color]) => {
const colorInput = document.getElementById(type + 'Color');
const textInput = document.getElementById(type + 'Color-text');
if (colorInput && textInput) {
colorInput.value = color;
textInput.value = color;
colorSystem.colors[type] = color;
}
});
}
// Framework name switcher functionality
function initializeFrameworkNameSwitcher() {
const nameCards = document.querySelectorAll('.name-card');
const frameworkNameEl = document.getElementById('currentName');
nameCards.forEach(card => {
card.addEventListener('click', function() {
// Remove active class from all cards
nameCards.forEach(c => c.classList.remove('active'));
// Add active class to clicked card
this.classList.add('active');
// Update framework name display
const name = this.dataset.name;
frameworkNameEl.textContent = frameworkNames[name];
});
});
}
// Validate hex color format
function isValidHexColor(hex) {
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(hex);
}
// Switch color mode
function switchColorMode(mode) {
colorSystem.mode = mode;
// Update active button
const modeButtons = document.querySelectorAll('.mode-btn');
modeButtons.forEach(btn => {
btn.classList.toggle('active', btn.dataset.mode === mode);
});
// Show/hide appropriate color inputs
showColorInputsForMode(mode);
// Update description
updateModeDescription(mode);
// Update the entire color system
updateColorSystem();
}
// Show appropriate color inputs for current mode
function showColorInputsForMode(mode) {
const allGroups = ['accent-group', 'primary-group', 'secondary-group', 'tertiary-group'];
// Hide all groups first
allGroups.forEach(groupId => {
const group = document.getElementById(groupId);
if (group) group.style.display = 'none';
});
// Show relevant groups based on mode
switch (mode) {
case 'monochrome':
// No color inputs shown
break;
case 'one-color':
document.getElementById('accent-group').style.display = 'block';
break;
case 'two-colors':
document.getElementById('primary-group').style.display = 'block';
document.getElementById('secondary-group').style.display = 'block';
break;
case 'three-colors':
document.getElementById('primary-group').style.display = 'block';
document.getElementById('secondary-group').style.display = 'block';
document.getElementById('tertiary-group').style.display = 'block';
break;
}
}
// Update mode description
function updateModeDescription(mode) {
const descElement = document.getElementById('modeDescription');
if (descElement && modeDescriptions[mode]) {
descElement.textContent = modeDescriptions[mode];
}
}
// Update specific color value
function updateColorValue(colorType, color) {
colorSystem.colors[colorType] = color;
updateColorSystem();
}
// Reset to monochrome
function resetToMonochrome() {
switchColorMode('monochrome');
}
// Update the CSS custom properties based on color system
function updateColorSystem() {
const root = document.documentElement;
const palette = generatePalette(colorSystem.mode, colorSystem.colors);
Object.entries(palette.light).forEach(([token, value]) => {
root.style.setProperty(`--basis-light-${token}`, value);
});
Object.entries(palette.dark).forEach(([token, value]) => {
root.style.setProperty(`--basis-dark-${token}`, value);
});
const activeScheme = getActiveScheme();
applyActiveSchemeTokens(root, palette[activeScheme]);
// Update tertiary button visibility
updateTertiaryElements();
// Update preview swatches
updatePreviewSwatches(palette, activeScheme);
}
// Update elements that should only show in certain modes
function updateTertiaryElements() {
const tertiaryButtons = document.querySelectorAll('.btn-tertiary');
const tertiaryAlerts = document.querySelectorAll('.alert-tertiary');
const showTertiary = colorSystem.mode === 'three-colors';
[...tertiaryButtons, ...tertiaryAlerts].forEach(el => {
el.style.display = showTertiary ? '' : 'none';
});
}
// Update preview color swatches
function updatePreviewSwatches(palette, scheme) {
if (!palette) return;
const current = palette[scheme] || palette.light;
const swatches = document.querySelectorAll('.preview-swatch');
swatches.forEach((swatch) => {
const token = swatch.dataset.color;
if (!token) return;
switch (token) {
case 'bg':
swatch.style.background = current.background;
swatch.style.color = current.text;
swatch.textContent = scheme === 'dark' ? 'Background · Dark' : 'Background · Light';
break;
case 'surface':
swatch.style.background = current.surfaceRaised;
swatch.style.color = current.text;
swatch.textContent = scheme === 'dark' ? 'Surface · Dark' : 'Surface · Light';
break;
case 'text':
swatch.style.background = current.primary;
swatch.style.color = current.primaryContrast;
swatch.textContent = 'Primary Accent';
break;
default:
break;
}
});
}
// Modal functionality
function openModal() {
const modal = document.getElementById('exampleModal');
if (modal) {
modal.classList.add('active');
document.body.style.overflow = 'hidden';
}
}
function closeModal() {
const modal = document.getElementById('exampleModal');
if (modal) {
modal.classList.remove('active');
document.body.style.overflow = 'auto';
}
}
// Close modal when clicking outside
document.addEventListener('click', function(e) {
if (e.target.classList.contains('modal-backdrop')) {
closeModal();
}
});
// Close modal with Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeModal();
}
});
// Copy code snippet functionality
function copyCodeSnippet(element) {
const code = element.querySelector('code');
if (code) {
const text = code.textContent;
navigator.clipboard.writeText(text).then(() => {
// Show brief success feedback
const originalText = element.textContent;
element.textContent = 'Copied!';
setTimeout(() => {
element.innerHTML = originalText;
}, 1000);
});
}
}
// Add click handlers to code snippets for copying
document.addEventListener('DOMContentLoaded', function() {
const codeSnippets = document.querySelectorAll('.code-snippet');
codeSnippets.forEach(snippet => {
snippet.style.cursor = 'pointer';
snippet.title = 'Click to copy code';
snippet.addEventListener('click', () => copyCodeSnippet(snippet));
});
});
// Export functions to global scope for HTML onclick handlers
window.resetToMonochrome = resetToMonochrome;
window.openModal = openModal;
window.closeModal = closeModal;
window.switchColorMode = switchColorMode;