import { FeatureFallbackManager } from '../core/feature-fallbacks'; const fallbackManager = new FeatureFallbackManager(); /** * Generates CSS with automatic fallbacks for modern features */ export function withFallbacks(css: Record): Record { const result: Record = {}; for (const [property, value] of Object.entries(css)) { // Detect paint worklets usage if (value.includes('paint(')) { const paintMatch = value.match(/paint\(([^)]+)\)/); if (paintMatch?.[1]) { const paintName = paintMatch[1]; const fallbackValue = fallbackManager.getHoudiniPaintFallback(paintName); result[property] = fallbackValue; // Add native version as progressive enhancement if (fallbackValue !== value) { result[`${property}--native`] = value; } } } // Detect anchor positioning else if (value.includes('anchor(')) { const fallback = fallbackManager.getBestFallback('anchor-positioning'); if (fallback?.quality !== 'native') { // Convert anchor() to absolute positioning result[property] = value.replace(/anchor\([^)]+\)/g, '0'); result[`${property}--anchor`] = value; } else { result[property] = value; } } // Detect view transitions else if (property === 'view-transition-name') { const fallback = fallbackManager.getBestFallback('view-transitions'); if (fallback?.quality !== 'native') { // Remove view-transition-name if not supported result['transition'] = 'opacity 0.3s ease'; } else { result[property] = value; } } else { result[property] = value; } } return result; } /** * Generates @supports rules for progressive enhancement */ export function generateSupportsRules(feature: string, css: Record): string { const rules: string[] = []; switch (feature) { case 'houdini-paint': rules.push(`@supports (background: paint(test)) {`); for (const [prop, value] of Object.entries(css)) { if (prop.endsWith('--native')) { const baseProp = prop.replace('--native', ''); rules.push(` ${baseProp}: ${value};`); } } rules.push(`}`); break; case 'anchor-positioning': rules.push(`@supports (anchor-name: --test) {`); for (const [prop, value] of Object.entries(css)) { if (prop.endsWith('--anchor')) { const baseProp = prop.replace('--anchor', ''); rules.push(` ${baseProp}: ${value};`); } } rules.push(`}`); break; case 'view-transitions': rules.push(`@supports (view-transition-name: test) {`); for (const [prop, value] of Object.entries(css)) { rules.push(` ${prop}: ${value};`); } rules.push(`}`); break; } return rules.join('\n'); } /** * Applies specific fallbacks for different browsers */ export function applyBrowserSpecificFallbacks(css: Record): Record { const ua = typeof navigator !== 'undefined' ? navigator.userAgent : ''; const isFirefox = /Firefox/.test(ua); const isSafari = /Safari/.test(ua) && !/Chrome/.test(ua); const result = { ...css }; // Firefox does not support Houdini Paint if (isFirefox) { for (const [prop, value] of Object.entries(result)) { if (typeof value === 'string' && value.includes('paint(')) { const paintMatch = value.match(/paint\(([^)]+)\)/); if (paintMatch && paintMatch[1]) { result[prop] = fallbackManager.getHoudiniPaintFallback(paintMatch[1]); } } } } // Safari has limited support for some features if (isSafari) { for (const [prop] of Object.entries(result)) { // Partial view transitions in Safari if (prop === 'view-transition-name') { result['transition'] = 'opacity 0.3s ease'; } } } return result; } /** * Utility to create resilient CSS with multiple fallback layers */ export function createResilientCSS(config: { base: Record; enhanced?: Record; experimental?: Record; }): string { const baseCSS = withFallbacks(config.base); const enhancedCSS = config.enhanced ? withFallbacks(config.enhanced) : {}; const experimentalCSS = config.experimental ? withFallbacks(config.experimental) : {}; const rules: string[] = []; // Base styles (always applied) for (const [prop, value] of Object.entries(baseCSS)) { if (!prop.includes('--')) { rules.push(`${prop}: ${value};`); } } // Enhanced styles with @supports if (Object.keys(enhancedCSS).length > 0) { rules.push(generateSupportsRules('container-queries', enhancedCSS)); } // Experimental styles with @supports if (Object.keys(experimentalCSS).length > 0) { rules.push(generateSupportsRules('houdini-paint', experimentalCSS)); rules.push(generateSupportsRules('anchor-positioning', experimentalCSS)); rules.push(generateSupportsRules('view-transitions', experimentalCSS)); } return rules.filter(rule => rule.trim()).join('\n'); }