From d1311245158c90fe07a603edb542b36d00be7e72 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 18 Sep 2026 12:44:39 +0200 Subject: [PATCH] feat(theme): animate public background with aurora and particles, polish landing pages Add background_effect (aurora/particles), background_overlay tint and opacity to the theme manager, rendered site-wide by ThemeVars on every public page. Polish the home and register pages (hero mascot, live stat pulse, date pills, photo strip, CTA band, theme-aware register intro, i18n for home/register section). --- src/actions/theme-manager.ts | 52 +++++++ src/app/(site)/page.tsx | 116 ++++++++++++-- src/app/(site)/register/page.tsx | 22 +-- src/app/globals.css | 142 ++++++++++++++++++ .../admin/theme/theme-manager-panel.tsx | 119 +++++++++++++++ src/components/theme-vars.tsx | 76 ++++++++++ src/lib/theme-bg.ts | 31 ++++ src/messages/en.json | 16 +- src/messages/it.json | 24 ++- 9 files changed, 573 insertions(+), 25 deletions(-) diff --git a/src/actions/theme-manager.ts b/src/actions/theme-manager.ts index 0c0050ea..105bd5af 100644 --- a/src/actions/theme-manager.ts +++ b/src/actions/theme-manager.ts @@ -12,10 +12,12 @@ import { siteSettings } from "@/lib/services/site-settings"; import { logStaffActivity } from "@/lib/services/staff-activity"; import { BACKGROUND_DEFAULTS, + BACKGROUND_EFFECTS, BACKGROUND_POSITIONS, BACKGROUND_REPEATS, BACKGROUND_SIZES, isSafeBackgroundUrl, + OVERLAY_OPACITY_MAX, } from "@/lib/theme-bg"; import { type CustomTheme, @@ -187,6 +189,17 @@ export async function saveBackground(formData: FormData): Promise { const repeat = String(formData.get("background_repeat") ?? "") .normalize("NFC") .trim(); + const effect = String(formData.get("background_effect") ?? "") + .normalize("NFC") + .trim(); + const overlay = String(formData.get("background_overlay") ?? "") + .normalize("NFC") + .trim(); + const overlayOpacity = String( + formData.get("background_overlay_opacity") ?? "", + ) + .normalize("NFC") + .trim(); if (image && !isSafeBackgroundUrl(image)) redirect(MANAGER_PATH); @@ -210,6 +223,22 @@ export async function saveBackground(formData: FormData): Promise { ? repeat : BACKGROUND_DEFAULTS.background_repeat, ); + await writeSetting( + "background_effect", + (BACKGROUND_EFFECTS as readonly string[]).includes(effect) + ? effect + : BACKGROUND_DEFAULTS.background_effect, + ); + await writeSetting( + "background_overlay", + /^#[0-9a-fA-F]{3,8}$/.test(overlay) ? overlay : "", + ); + await writeSetting( + "background_overlay_opacity", + /^\d{1,2}$/.test(overlayOpacity) + ? String(Math.min(parseInt(overlayOpacity, 10), OVERLAY_OPACITY_MAX)) + : "0", + ); siteSettings.reload(); revalidatePath("/", "layout"); revalidatePath(MANAGER_PATH); @@ -344,6 +373,29 @@ export async function getThemeManagerData() { ) ? (backgroundValues.background_repeat as string) : BACKGROUND_DEFAULTS.background_repeat, + effect: (BACKGROUND_EFFECTS as readonly string[]).includes( + backgroundValues.background_effect ?? "", + ) + ? (backgroundValues.background_effect as string) + : BACKGROUND_DEFAULTS.background_effect, + overlay: /^#[0-9a-fA-F]{3,8}$/.test( + backgroundValues.background_overlay ?? "", + ) + ? (backgroundValues.background_overlay as string) + : "", + overlayOpacity: /^\d{1,2}$/.test( + backgroundValues.background_overlay_opacity ?? "", + ) + ? String( + Math.min( + parseInt( + backgroundValues.background_overlay_opacity as string, + 10, + ), + OVERLAY_OPACITY_MAX, + ), + ) + : "0", }, }; } diff --git a/src/app/(site)/page.tsx b/src/app/(site)/page.tsx index 0e3540b8..f1242c01 100644 --- a/src/app/(site)/page.tsx +++ b/src/app/(site)/page.tsx @@ -249,6 +249,23 @@ export default async function Home() { />