- custom_frames: per-user frames with full 9-slot palette (JSONB),
unique names; designer frame picker lists them alongside starters,
click to use, edit/delete via inline editor with live preview
- custom_symbols: upload cost icons (PNG/WebP/SVG, 2MB) keyed by short
code; re-uploading a code replaces the old icon; ManaPips renders
icon pips for {CODE} tokens with graceful text fallback
- custom_cards.custom_frame_id links designs to custom frames; API GETs
join and nest the palette; deleting a frame falls back to starter
- migration 1787700511000
38 lines
1.2 KiB
JavaScript
38 lines
1.2 KiB
JavaScript
/**
|
|
* Palette contract for card frames. Starter and custom frames share the
|
|
* same slots; CardFrame resolves a palette from these keys.
|
|
*/
|
|
export const PALETTE_SLOTS = [
|
|
{ key: 'outer', label: 'Card body' },
|
|
{ key: 'border', label: 'Frame border' },
|
|
{ key: 'titleBar', label: 'Title bar' },
|
|
{ key: 'typeBar', label: 'Type bar' },
|
|
{ key: 'textBox', label: 'Text box' },
|
|
{ key: 'artBacking', label: 'Art backing' },
|
|
{ key: 'text', label: 'Body text' },
|
|
{ key: 'titleText', label: 'Title text' },
|
|
{ key: 'accent', label: 'Accent' },
|
|
];
|
|
|
|
const HEX_RE = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
|
|
/**
|
|
* Validate/normalize a palette object. Returns { palette } with all nine
|
|
* slots as lowercase hex, or { error } when a slot is missing/invalid.
|
|
*/
|
|
export function validatePalette(input) {
|
|
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
return { error: 'Palette must be an object' };
|
|
}
|
|
|
|
const palette = {};
|
|
for (const { key } of PALETTE_SLOTS) {
|
|
const value = input[key];
|
|
if (typeof value !== 'string' || !HEX_RE.test(value.trim())) {
|
|
return { error: `Invalid or missing color for "${key}"` };
|
|
}
|
|
palette[key] = value.trim().toLowerCase();
|
|
}
|
|
|
|
return { palette };
|
|
}
|