This commit is contained in:
1 parent
8efd032cc6
commit
df38dccbf1
735 files changed
+128321
-120870
No files matched your search
+215
-175
@@ -1,177 +1,217 @@
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
import {
|
||||
deriveAdminPalette,
|
||||
derivePublicForegrounds,
|
||||
readableColor,
|
||||
} from "@/lib/theme-contrast";
|
||||
import { adminPaletteCss, themePaletteCss } from "@/lib/theme-css";
|
||||
import { FONTS, PRESETS, THEME_COLOR_KEYS, type ThemePalette } from "@/lib/theme-presets";
|
||||
import { deriveAdminPalette, derivePublicForegrounds, readableColor } from "@/lib/theme-contrast";
|
||||
import {
|
||||
FONTS,
|
||||
PRESETS,
|
||||
THEME_COLOR_KEYS,
|
||||
type ThemePalette,
|
||||
} from "@/lib/theme-presets";
|
||||
|
||||
// Injects the DB-driven CSS custom properties into :root, exactly like
|
||||
// AtomCMS's app.blade.php. Falls back to the atom defaults when no DB. Covers
|
||||
// colours, typography, buttons, links + an optional raw custom-CSS block.
|
||||
export async function ThemeVars() {
|
||||
const g = (k: string, d: string) => siteSettings.get(k, d);
|
||||
const [
|
||||
primary,
|
||||
background,
|
||||
surface,
|
||||
dropdown,
|
||||
navbar,
|
||||
navbarText,
|
||||
text,
|
||||
textMuted,
|
||||
accent,
|
||||
success,
|
||||
warning,
|
||||
error,
|
||||
info,
|
||||
buttonColor,
|
||||
buttonText,
|
||||
buttonSecondary,
|
||||
buttonSecondaryText,
|
||||
buttonDanger,
|
||||
buttonDangerText,
|
||||
buttonOutline,
|
||||
buttonOutlineText,
|
||||
linkColor,
|
||||
linkHover,
|
||||
borderColor,
|
||||
gradientFrom,
|
||||
gradientTo,
|
||||
borderRadius,
|
||||
fontKey,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
customCss,
|
||||
adminCanvas,
|
||||
adminSurface,
|
||||
adminText,
|
||||
adminTextMuted,
|
||||
adminBorder,
|
||||
adminSidebarBg,
|
||||
] = await Promise.all([
|
||||
g("color_primary", "#f59e0b"),
|
||||
g("color_background", "#f8fafc"),
|
||||
g("color_surface", "#ffffff"),
|
||||
g("color_dropdown", "#ffffff"),
|
||||
g("color_navbar", "#ffffff"),
|
||||
g("color_navbar_text", "#1e293b"),
|
||||
g("color_text", "#0f172a"),
|
||||
g("color_text_muted", "#64748b"),
|
||||
g("color_accent", "#10b981"),
|
||||
g("color_success", "#16a34a"),
|
||||
g("color_warning", "#eab308"),
|
||||
g("color_error", "#ef4444"),
|
||||
g("color_info", "#0ea5e9"),
|
||||
g("button_primary_color", "#f59e0b"),
|
||||
g("button_text_color", "#1e293b"),
|
||||
g("button_secondary_color", "#22c55e"),
|
||||
g("button_secondary_text_color", "#ffffff"),
|
||||
g("button_danger_color", "#ef4444"),
|
||||
g("button_danger_text_color", "#ffffff"),
|
||||
g("button_outline_color", "#eeb425"),
|
||||
g("button_outline_text_color", "#1a1a2e"),
|
||||
g("link_color", "#eeb425"),
|
||||
g("link_hover_color", "#cf9d15"),
|
||||
g("border_color", "#eeb425"),
|
||||
g("gradient_from", "#f59e0b"),
|
||||
g("gradient_to", "#10b981"),
|
||||
g("border_radius", "12"),
|
||||
g("font_family", "nunito"),
|
||||
g("size_heading_h1", "30"),
|
||||
g("size_heading_h2", "22"),
|
||||
g("size_heading_h3", "18"),
|
||||
g("custom_css", ""),
|
||||
g("admin_canvas", ""),
|
||||
g("admin_surface", ""),
|
||||
g("admin_text", ""),
|
||||
g("admin_text_muted", ""),
|
||||
g("admin_border", ""),
|
||||
g("admin_sidebar_bg", ""),
|
||||
]);
|
||||
const g = (k: string, d: string) => siteSettings.get(k, d);
|
||||
const [
|
||||
primary,
|
||||
background,
|
||||
surface,
|
||||
dropdown,
|
||||
navbar,
|
||||
navbarText,
|
||||
text,
|
||||
textMuted,
|
||||
accent,
|
||||
success,
|
||||
warning,
|
||||
error,
|
||||
info,
|
||||
buttonColor,
|
||||
buttonText,
|
||||
buttonSecondary,
|
||||
buttonSecondaryText,
|
||||
buttonDanger,
|
||||
buttonDangerText,
|
||||
buttonOutline,
|
||||
buttonOutlineText,
|
||||
linkColor,
|
||||
linkHover,
|
||||
borderColor,
|
||||
gradientFrom,
|
||||
gradientTo,
|
||||
borderRadius,
|
||||
fontKey,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
customCss,
|
||||
adminCanvas,
|
||||
adminSurface,
|
||||
adminText,
|
||||
adminTextMuted,
|
||||
adminBorder,
|
||||
adminSidebarBg,
|
||||
] = await Promise.all([
|
||||
g("color_primary", "#f59e0b"),
|
||||
g("color_background", "#f8fafc"),
|
||||
g("color_surface", "#ffffff"),
|
||||
g("color_dropdown", "#ffffff"),
|
||||
g("color_navbar", "#ffffff"),
|
||||
g("color_navbar_text", "#1e293b"),
|
||||
g("color_text", "#0f172a"),
|
||||
g("color_text_muted", "#64748b"),
|
||||
g("color_accent", "#10b981"),
|
||||
g("color_success", "#16a34a"),
|
||||
g("color_warning", "#eab308"),
|
||||
g("color_error", "#ef4444"),
|
||||
g("color_info", "#0ea5e9"),
|
||||
g("button_primary_color", "#f59e0b"),
|
||||
g("button_text_color", "#1e293b"),
|
||||
g("button_secondary_color", "#22c55e"),
|
||||
g("button_secondary_text_color", "#ffffff"),
|
||||
g("button_danger_color", "#ef4444"),
|
||||
g("button_danger_text_color", "#ffffff"),
|
||||
g("button_outline_color", "#eeb425"),
|
||||
g("button_outline_text_color", "#1a1a2e"),
|
||||
g("link_color", "#eeb425"),
|
||||
g("link_hover_color", "#cf9d15"),
|
||||
g("border_color", "#eeb425"),
|
||||
g("gradient_from", "#f59e0b"),
|
||||
g("gradient_to", "#10b981"),
|
||||
g("border_radius", "12"),
|
||||
g("font_family", "nunito"),
|
||||
g("size_heading_h1", "30"),
|
||||
g("size_heading_h2", "22"),
|
||||
g("size_heading_h3", "18"),
|
||||
g("custom_css", ""),
|
||||
g("admin_canvas", ""),
|
||||
g("admin_surface", ""),
|
||||
g("admin_text", ""),
|
||||
g("admin_text_muted", ""),
|
||||
g("admin_border", ""),
|
||||
g("admin_sidebar_bg", ""),
|
||||
]);
|
||||
|
||||
// Only hex/keyword color values are admin-controlled — sanitize defensively.
|
||||
const safe = (v: string | null, d: string) => (v && /^[#a-zA-Z0-9(),.\s%-]+$/.test(v) ? v : d);
|
||||
const px = (v: string | null, d: string) => (/^\d{1,3}$/.test(v ?? "") ? (v as string) : d);
|
||||
// Only hex/keyword color values are admin-controlled — sanitize defensively.
|
||||
const safe = (v: string | null, d: string) =>
|
||||
v && /^[#a-zA-Z0-9(),.\s%-]+$/.test(v) ? v : d;
|
||||
const px = (v: string | null, d: string) =>
|
||||
/^\d{1,3}$/.test(v ?? "") ? (v as string) : d;
|
||||
|
||||
const safeBackground = safe(background, "#f8fafc");
|
||||
const safeSurface = safe(surface, "#ffffff");
|
||||
const publicBackgrounds = [safeBackground, safeSurface];
|
||||
const readableText = readableColor(safe(text, "#0f172a"), publicBackgrounds);
|
||||
const readableMuted = readableColor(safe(textMuted, "#64748b"), publicBackgrounds);
|
||||
const readablePrimary = readableColor(safe(primary, "#f59e0b"), publicBackgrounds);
|
||||
const readableAccent = readableColor(safe(accent, "#10b981"), publicBackgrounds);
|
||||
const readableLink = readableColor(safe(linkColor, "#eeb425"), publicBackgrounds);
|
||||
const readableLinkHover = readableColor(safe(linkHover, "#cf9d15"), publicBackgrounds);
|
||||
const readableButtonText = readableColor(safe(buttonText, "#1e293b"), [safe(buttonColor, "#f59e0b")]);
|
||||
const readableSecondaryText = readableColor(safe(buttonSecondaryText, "#ffffff"), [
|
||||
safe(buttonSecondary, "#22c55e"),
|
||||
]);
|
||||
const readableDangerText = readableColor(safe(buttonDangerText, "#ffffff"), [
|
||||
safe(buttonDanger, "#ef4444"),
|
||||
]);
|
||||
const readableNavbarText = readableColor(safe(navbarText, "#1e293b"), [safe(navbar, "#ffffff")]);
|
||||
const readablePrimaryForeground = readableColor(safe(buttonText, "#1e293b"), [safe(primary, "#f59e0b")]);
|
||||
const readableAccentForeground = readableColor("#ffffff", [safe(accent, "#10b981")]);
|
||||
const adminOverrides = {
|
||||
...(adminCanvas ? { canvas: safe(adminCanvas, "") } : {}),
|
||||
...(adminSurface ? { surface: safe(adminSurface, "") } : {}),
|
||||
...(adminText ? { text: safe(adminText, "") } : {}),
|
||||
...(adminTextMuted ? { muted: safe(adminTextMuted, "") } : {}),
|
||||
...(adminBorder ? { border: safe(adminBorder, "") } : {}),
|
||||
...(adminSidebarBg ? { sidebar: safe(adminSidebarBg, "") } : {}),
|
||||
};
|
||||
const safeBackground = safe(background, "#f8fafc");
|
||||
const safeSurface = safe(surface, "#ffffff");
|
||||
const publicBackgrounds = [safeBackground, safeSurface];
|
||||
const readableText = readableColor(safe(text, "#0f172a"), publicBackgrounds);
|
||||
const readableMuted = readableColor(
|
||||
safe(textMuted, "#64748b"),
|
||||
publicBackgrounds,
|
||||
);
|
||||
const readablePrimary = readableColor(
|
||||
safe(primary, "#f59e0b"),
|
||||
publicBackgrounds,
|
||||
);
|
||||
const readableAccent = readableColor(
|
||||
safe(accent, "#10b981"),
|
||||
publicBackgrounds,
|
||||
);
|
||||
const readableLink = readableColor(
|
||||
safe(linkColor, "#eeb425"),
|
||||
publicBackgrounds,
|
||||
);
|
||||
const readableLinkHover = readableColor(
|
||||
safe(linkHover, "#cf9d15"),
|
||||
publicBackgrounds,
|
||||
);
|
||||
const readableButtonText = readableColor(safe(buttonText, "#1e293b"), [
|
||||
safe(buttonColor, "#f59e0b"),
|
||||
]);
|
||||
const readableSecondaryText = readableColor(
|
||||
safe(buttonSecondaryText, "#ffffff"),
|
||||
[safe(buttonSecondary, "#22c55e")],
|
||||
);
|
||||
const readableDangerText = readableColor(safe(buttonDangerText, "#ffffff"), [
|
||||
safe(buttonDanger, "#ef4444"),
|
||||
]);
|
||||
const readableNavbarText = readableColor(safe(navbarText, "#1e293b"), [
|
||||
safe(navbar, "#ffffff"),
|
||||
]);
|
||||
const readablePrimaryForeground = readableColor(safe(buttonText, "#1e293b"), [
|
||||
safe(primary, "#f59e0b"),
|
||||
]);
|
||||
const readableAccentForeground = readableColor("#ffffff", [
|
||||
safe(accent, "#10b981"),
|
||||
]);
|
||||
const adminOverrides = {
|
||||
...(adminCanvas ? { canvas: safe(adminCanvas, "") } : {}),
|
||||
...(adminSurface ? { surface: safe(adminSurface, "") } : {}),
|
||||
...(adminText ? { text: safe(adminText, "") } : {}),
|
||||
...(adminTextMuted ? { muted: safe(adminTextMuted, "") } : {}),
|
||||
...(adminBorder ? { border: safe(adminBorder, "") } : {}),
|
||||
...(adminSidebarBg ? { sidebar: safe(adminSidebarBg, "") } : {}),
|
||||
};
|
||||
|
||||
const semantic = derivePublicForegrounds({
|
||||
color_background: safeBackground,
|
||||
color_surface: safeSurface,
|
||||
color_dropdown: safe(dropdown, "#ffffff"),
|
||||
color_navbar: safe(navbar, "#ffffff"),
|
||||
color_navbar_text: safe(navbarText, "#1e293b"),
|
||||
color_text: safe(text, "#0f172a"),
|
||||
color_text_muted: safe(textMuted, "#64748b"),
|
||||
color_primary: safe(primary, "#f59e0b"),
|
||||
color_accent: safe(accent, "#10b981"),
|
||||
button_primary_color: safe(buttonColor, "#f59e0b"),
|
||||
button_text_color: safe(buttonText, "#1e293b"),
|
||||
button_secondary_color: safe(buttonSecondary, "#22c55e"),
|
||||
button_secondary_text_color: safe(buttonSecondaryText, "#ffffff"),
|
||||
button_danger_color: safe(buttonDanger, "#ef4444"),
|
||||
button_danger_text_color: safe(buttonDangerText, "#ffffff"),
|
||||
button_outline_color: safe(buttonOutline, "#eeb425"),
|
||||
button_outline_text_color: safe(buttonOutlineText, "#1a1a2e"),
|
||||
link_color: safe(linkColor, "#eeb425"),
|
||||
link_hover_color: safe(linkHover, "#cf9d15"),
|
||||
});
|
||||
const semantic = derivePublicForegrounds({
|
||||
color_background: safeBackground,
|
||||
color_surface: safeSurface,
|
||||
color_dropdown: safe(dropdown, "#ffffff"),
|
||||
color_navbar: safe(navbar, "#ffffff"),
|
||||
color_navbar_text: safe(navbarText, "#1e293b"),
|
||||
color_text: safe(text, "#0f172a"),
|
||||
color_text_muted: safe(textMuted, "#64748b"),
|
||||
color_primary: safe(primary, "#f59e0b"),
|
||||
color_accent: safe(accent, "#10b981"),
|
||||
button_primary_color: safe(buttonColor, "#f59e0b"),
|
||||
button_text_color: safe(buttonText, "#1e293b"),
|
||||
button_secondary_color: safe(buttonSecondary, "#22c55e"),
|
||||
button_secondary_text_color: safe(buttonSecondaryText, "#ffffff"),
|
||||
button_danger_color: safe(buttonDanger, "#ef4444"),
|
||||
button_danger_text_color: safe(buttonDangerText, "#ffffff"),
|
||||
button_outline_color: safe(buttonOutline, "#eeb425"),
|
||||
button_outline_text_color: safe(buttonOutlineText, "#1a1a2e"),
|
||||
link_color: safe(linkColor, "#eeb425"),
|
||||
link_hover_color: safe(linkHover, "#cf9d15"),
|
||||
});
|
||||
|
||||
const font = FONTS[fontKey ?? "nunito"] ?? FONTS.nunito;
|
||||
const font = FONTS[fontKey ?? "nunito"] ?? FONTS.nunito;
|
||||
|
||||
const presetName = (await g("theme_preset", "Atom (golden)")) ?? "Atom (golden)";
|
||||
const preset = PRESETS[presetName] ?? PRESETS["Atom (golden)"];
|
||||
const darkPalette = Object.fromEntries(
|
||||
await Promise.all(
|
||||
THEME_COLOR_KEYS.map(
|
||||
async (key) => [key, safe(await g(`${key}_dark`, preset.dark[key]), preset.dark[key])] as const,
|
||||
),
|
||||
),
|
||||
) as ThemePalette;
|
||||
const darkCss = themePaletteCss("html.dark", darkPalette, adminOverrides);
|
||||
const presetName =
|
||||
(await g("theme_preset", "Atom (golden)")) ?? "Atom (golden)";
|
||||
const preset = PRESETS[presetName] ?? PRESETS["Atom (golden)"];
|
||||
const darkPalette = Object.fromEntries(
|
||||
await Promise.all(
|
||||
THEME_COLOR_KEYS.map(
|
||||
async (key) =>
|
||||
[
|
||||
key,
|
||||
safe(await g(`${key}_dark`, preset.dark[key]), preset.dark[key]),
|
||||
] as const,
|
||||
),
|
||||
),
|
||||
) as ThemePalette;
|
||||
const darkCss = themePaletteCss("html.dark", darkPalette, adminOverrides);
|
||||
|
||||
const lightAdmin = deriveAdminPalette(
|
||||
{
|
||||
color_background: safeBackground,
|
||||
color_surface: safeSurface,
|
||||
color_text: safe(text, "#0f172a"),
|
||||
color_text_muted: safe(textMuted, "#64748b"),
|
||||
color_primary: safe(primary, "#f59e0b"),
|
||||
color_accent: safe(accent, "#10b981"),
|
||||
color_success: safe(success, "#16a34a"),
|
||||
color_warning: safe(warning, "#eab308"),
|
||||
color_error: safe(error, "#ef4444"),
|
||||
color_info: safe(info, "#0ea5e9"),
|
||||
} as ThemePalette,
|
||||
adminOverrides,
|
||||
);
|
||||
const lightAdmin = deriveAdminPalette(
|
||||
{
|
||||
color_background: safeBackground,
|
||||
color_surface: safeSurface,
|
||||
color_text: safe(text, "#0f172a"),
|
||||
color_text_muted: safe(textMuted, "#64748b"),
|
||||
color_primary: safe(primary, "#f59e0b"),
|
||||
color_accent: safe(accent, "#10b981"),
|
||||
color_success: safe(success, "#16a34a"),
|
||||
color_warning: safe(warning, "#eab308"),
|
||||
color_error: safe(error, "#ef4444"),
|
||||
color_info: safe(info, "#0ea5e9"),
|
||||
} as ThemePalette,
|
||||
adminOverrides,
|
||||
);
|
||||
|
||||
const css = `:root{${adminPaletteCss(lightAdmin)}
|
||||
const css = `:root{${adminPaletteCss(lightAdmin)}
|
||||
--color-primary:${safe(primary, "#f59e0b")};
|
||||
--color-background:${safeBackground};
|
||||
--color-surface:${safeSurface};
|
||||
@@ -223,20 +263,20 @@ export async function ThemeVars() {
|
||||
--size-heading-h3:${px(h3, "18")}px;
|
||||
}${darkCss}`;
|
||||
|
||||
const googleHref = font.google
|
||||
? `https://fonts.googleapis.com/css2?family=${font.google}&display=swap`
|
||||
: null;
|
||||
const googleHref = font.google
|
||||
? `https://fonts.googleapis.com/css2?family=${font.google}&display=swap`
|
||||
: null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{googleHref ? <link rel="stylesheet" href={googleHref} /> : null}
|
||||
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: server-built CSS from sanitized admin settings */}
|
||||
<style dangerouslySetInnerHTML={{ __html: css }} />
|
||||
{customCss && customCss.trim() ? (
|
||||
// Staff-authored custom CSS (housekeeping → Theme). Trusted input.
|
||||
// biome-ignore lint/security/noDangerouslySetInnerHtml: staff-authored theme CSS
|
||||
<style dangerouslySetInnerHTML={{ __html: customCss }} />
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{googleHref ? <link rel="stylesheet" href={googleHref} /> : null}
|
||||
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: server-built CSS from sanitized admin settings */}
|
||||
<style dangerouslySetInnerHTML={{ __html: css }} />
|
||||
{customCss?.trim() ? (
|
||||
// Staff-authored custom CSS (housekeeping → Theme). Trusted input.
|
||||
// biome-ignore lint/security/noDangerouslySetInnerHtml: staff-authored theme CSS
|
||||
<style dangerouslySetInnerHTML={{ __html: customCss }} />
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user