1// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2// Web account accent color â palette, normalization, DOM application, and 3// account persistence adapter. Purple is the default Selebox accent; an 4// account-selected accent remaps the site's purple UI through the shared 5// CSS token layer (body[data-accent="..."] in css/style.css). 6// 7// The 15 keys mirror the production CHECK constraint on 8// public.profiles.accent_color_key (verified 2026-09-20). Account save goes 9// through the SECURITY DEFINER update_accent_color RPC (authenticated-safe). 10// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 11 12export const DEFAULT_ACCENT_KEY = 'purple'; 13export const ACCENT_STORAGE_KEY = 'selebox_accent_color'; 14 15export const ACCENT_THEMES = Object.freeze({ 16 purple: { label: 'Purple', primary: '#8b5cf6' }, 17 pink: { label: 'Pink', primary: '#ec4899' }, 18 blue: { label: 'Blue', primary: '#3b82f6' }, 19 green: { label: 'Green', primary: '#22c55e' }, 20 orange: { label: 'Orange', primary: '#f97316' }, 21 red: { label: 'Red', primary: '#ef4444' }, 22 teal: { label: 'Teal', primary: '#14b8a6' }, 23 indigo: { label: 'Indigo', primary: '#6366f1' }, 24 sky: { label: 'Sky', primary: '#0ea5e9' }, 25 rose: { label: 'Rose', primary: '#f43f5e' }, 26 amber: { label: 'Amber', primary: '#f59e0b' }, 27 slate: { label: 'Slate', primary: '#64748b' }, 28 ink: { label: 'Ink', primary: '#000000' }, 29 sepia: { label: 'Sepia', primary: '#8a5a44' }, 30 crimson:{ label: 'Crimson',primary: '#9e172d' }, 31}); 32 33const ACCENT_ALIASES = Object.freeze({ 34 violet: 'purple', 35 brand: 'purple', 36 default: 'purple', 37 cyan: 'sky', 38 gray: 'slate', 39 grey: 'slate', 40}); 41 42export function normalizeAccentKey(value) { 43 const raw = String(value || '').trim().toLowerCase(); 44 const key = ACCENT_ALIASES[raw] || raw; 45 return Object.prototype.hasOwnProperty.call(ACCENT_THEMES, key) ? key : DEFAULT_ACCENT_KEY; 46} 47 48export function getStoredAccentKey(storage = globalThis.localStorage) { 49 try { 50 return normalizeAccentKey(storage?.getItem?.(ACCENT_STORAGE_KEY)); 51 } catch { 52 return DEFAULT_ACCENT_KEY; 53 } 54} 55 56export function resolveInitialAccent(profile, storage = globalThis.localStorage) { 57 const fromProfile = profile?.accent_color_key || profile?.accentColor || profile?.accent_color; 58 if (fromProfile) return normalizeAccentKey(fromProfile); 59 return getStoredAccentKey(storage); 60} 61 62export function applyAccent(key, { 63 persist = false, 64 root = globalThis.document?.body, 65 storage = globalThis.localStorage, 66} = {}) { 67 const normalized = normalizeAccentKey(key); 68 if (root?.dataset) root.dataset.accent = normalized; 69 if (persist) { 70 try { storage?.setItem?.(ACCENT_STORAGE_KEY, normalized); } catch {} 71 } 72 return normalized; 73} 74 75export function renderAccentOptions(selectedKey) { 76 const active = normalizeAccentKey(selectedKey); 77 return Object.entries(ACCENT_THEMES).map(([key, theme]) => { 78 const checked = key === active; 79 return ` 80 <button 81 type="button" 82 class="edit-accent-option${checked ? ' is-selected' : ''}" 83 data-accent-key="${key}" 84 style="--swatch:${theme.primary}" 85 tabindex="${checked ? 0 : -1}" 86 role="radio" 87 aria-checked="${checked ? 'true' : 'false'}" 88 aria-label="${theme.label}" 89 title="${theme.label}" 90 > 91 <span class="edit-accent-swatch" style="--swatch:${theme.primary}">${checked ? '<span class="edit-accent-check">â</span>' : ''}</span> 92 <span class="edit-accent-label">${theme.label}</span> 93 </button>`; 94 }).join(''); 95} 96 97// Account persistence â SAVE MODE: update_accent_color RPC (confirmed by 98// 2026-07-03 DB discovery). The RPC resolves the canonical profile via 99// auth.uid() server-side and validates the key against the account CHECK, 100// so no profileId is needed from the client. 101export async function persistAccentPreference({ supabase, key }) { 102 const normalized = normalizeAccentKey(key); 103 const { data, error } = await supabase.rpc('update_accent_color', { 104 p_accent_color_key: normalized, 105 }); 106 if (error) return { ok: false, error: error.message || 'accent_save_failed' }; 107 if (data?.ok !== true || data.accent_color_key !== normalized) return { ok: false, error: data?.error || 'Could not confirm the accent. Please try again.' }; 108 return { ok: true, accent_color_key: normalizeAccentKey((data && data.accent_color_key) || normalized) }; 109}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.