import type { TenantThemeData } from '../../app.d.js'; /** Validates a theme background image URL is safe for CSS injection. */ function isSafeCssUrl(url: string): boolean { return /^\/api\/branding\/[0-9a-f-]+\/[a-z-]+$/.test(url); } /** * Client-side CSS variable mapping — mirrors server-side theme-css.ts */ const FIELD_MAP: Record = {}; const vars = [ ['PageBg', '--color-page-bg'], ['NavBg', '--color-nav-bg'], ['ColumnBg', '--color-column-bg'], ['ColumnBorder', '--color-column-border'], ['ColumnText', '--color-column-text'], ['CardBg', '--color-card-bg'], ['CardBorder', '--color-card-border'], ['CardText', '--color-card-text'], ['CardRadius', '--color-card-radius'], ['Primary', '--color-primary'], ['PrimaryHover', '--color-primary-hover'], ['Danger', '--color-danger'], ['DangerHover', '--color-danger-hover'], ['Success', '--color-success'], ['Warning', '--color-warning'] ] as const; for (const [suffix, cssVar] of vars) { FIELD_MAP[`light${suffix}`] = { cssVar, mode: 'light' }; FIELD_MAP[`dark${suffix}`] = { cssVar, mode: 'dark' }; } function hexToRgba(hex: string, opacity: number): string { const h = hex.replace('#', ''); const r = parseInt(h.substring(0, 2), 16); const g = parseInt(h.substring(2, 4), 16); const b = parseInt(h.substring(4, 6), 16); return `rgba(${r}, ${g}, ${b}, ${opacity})`; } /** * Generates CSS from theme data and writes it into the