PageSourceSearch

https://selebox.com/js/theme-accent.js?v=2026-09-20-15-colors-1

js selebox.com collected 2026-10-07 23:56:56 UTC 4,758 bytes, 109 lines download raw bytes

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.