From 69aed6d9050e9028fc0a4df27cba92587aa48172 Mon Sep 17 00:00:00 2001 From: Joshua Lau Date: Mon, 1 Dec 2025 15:50:01 -0500 Subject: [PATCH 01/11] Custom background colors --- apps/web/src/app.pcss | 82 +++- .../general/style/ThemePanel.svelte | 382 +++++++++++++++++- apps/web/src/lib/components/recal/Top.svelte | 3 +- .../src/lib/components/ui/NoiseFilter.svelte | 20 + .../src/lib/components/ui/SidePanel.svelte | 11 +- apps/web/src/lib/scripts/ReCal+/palettes.ts | 362 ++++++++++------- apps/web/src/lib/scripts/convert.ts | 22 + apps/web/src/lib/stores/styles.ts | 146 ++++++- apps/web/src/routes/(apps)/+layout.svelte | 11 +- .../src/routes/(apps)/recalplus/+page.svelte | 3 +- .../src/routes/(apps)/reqtree/+page.svelte | 2 +- apps/web/src/routes/+layout.svelte | 52 ++- 12 files changed, 930 insertions(+), 166 deletions(-) create mode 100644 apps/web/src/lib/components/ui/NoiseFilter.svelte diff --git a/apps/web/src/app.pcss b/apps/web/src/app.pcss index 0b46222f..924236eb 100644 --- a/apps/web/src/app.pcss +++ b/apps/web/src/app.pcss @@ -3,15 +3,91 @@ @tailwind components; @tailwind utilities; +:root { + --bg-light: hsl(0, 0%, 100%); + --bg-dark: hsl(240, 6%, 10%); + --noise-opacity: 0; + --noise-frequency: 1.5; + --glow-opacity: 0; + --glow-color-1: 255, 149, 0; + --glow-color-2: 0, 122, 255; +} + .std-area { - @apply overflow-auto + @apply overflow-auto focus:outline-std-blue - bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 - border-zinc-200 dark:border-zinc-700; + border-zinc-200 dark:border-zinc-700; + background-color: var(--bg-light); border-width: 1px; } +.dark .std-area { + background-color: var(--bg-dark); +} + +/* Background effects class for noise and glows */ +.bg-effects { + position: relative; + isolation: isolate; +} + +/* Gradient glows layer */ +.bg-effects::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: radial-gradient( + ellipse at 0% 15%, + rgba(var(--glow-color-1), var(--glow-opacity)) 0%, + transparent 30% + ), + radial-gradient( + ellipse at 100% 30%, + rgba(var(--glow-color-2), var(--glow-opacity)) 0%, + transparent 40% + ); + pointer-events: none; + z-index: -2; +} + +/* Noise texture layer */ +.bg-effects::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.8); + filter: url(#pageNoise); + opacity: var(--noise-opacity); + pointer-events: none; + z-index: -1; +} + +/* Darker, more vibrant glows for dark mode */ +.dark .bg-effects::before { + background: radial-gradient( + ellipse at 0% 15%, + rgba(var(--glow-color-1), calc(var(--glow-opacity) * 1.33)) 0%, + transparent 30% + ), + radial-gradient( + ellipse at 100% 30%, + rgba(var(--glow-color-2), calc(var(--glow-opacity) * 1.33)) 0%, + transparent 40% + ); +} + +/* Subtler noise background for dark mode */ +.dark .bg-effects::after { + background: rgba(0, 0, 0, 0.1); +} + body.waiting { cursor: wait; } diff --git a/apps/web/src/lib/components/general/style/ThemePanel.svelte b/apps/web/src/lib/components/general/style/ThemePanel.svelte index 92fec56a..9d9148a0 100644 --- a/apps/web/src/lib/components/general/style/ThemePanel.svelte +++ b/apps/web/src/lib/components/general/style/ThemePanel.svelte @@ -5,9 +5,15 @@ darkTheme, calColors, DEFAULT_RCARD_COLORS, - type CalColors + bgColors, + DEFAULT_BG_COLORS, + bgEffects, + DEFAULT_BG_EFFECTS, + type CalColors, + type BgColors, + type BackgroundEffects } from "$lib/stores/styles"; - import { colorPalettes } from "$lib/scripts/ReCal+/palettes"; + import { colorPalettes, type Palette } from "$lib/scripts/ReCal+/palettes"; import { rgbToHSL, hslToRGB } from "$lib/scripts/convert"; $: open = $panelStore === "theme"; @@ -54,8 +60,8 @@ /** * Apply a preset palette */ - const applyPalette = (name: string, colors: CalColors) => { - const hslColors: CalColors = Object.entries(colors) + const applyPalette = (name: string, palette: Palette) => { + const hslColors: CalColors = Object.entries(palette.colors) .map(([key, value]) => [key, rgbToHSL(value)]) .reduce( (acc, [key, value]) => ({ ...acc, [key]: value }), @@ -65,6 +71,12 @@ calColors.set(hslColors); lastSelectedTheme = { name, colors: hslColors }; + // Apply background colors from palette + bgColors.set({ + light: rgbToHSL(palette.bgLight), + dark: rgbToHSL(palette.bgDark) + }); + // Persist to localStorage if (typeof window !== "undefined") { localStorage.setItem( @@ -103,6 +115,7 @@ */ const resetToDefault = () => { calColors.set(DEFAULT_RCARD_COLORS); + bgColors.set(DEFAULT_BG_COLORS); darkTheme.set(false); lastSelectedTheme = null; @@ -112,6 +125,57 @@ } }; + /** + * Update background color + */ + const updateBgColor = (mode: "light" | "dark", rgbValue: string) => { + const hslValue = rgbToHSL(rgbValue); + bgColors.set({ ...$bgColors, [mode]: hslValue }); + }; + + /** + * Reset background colors to default + */ + const resetBgColors = () => { + bgColors.set(DEFAULT_BG_COLORS); + }; + + /** + * Reset effects to default + */ + const resetEffects = () => { + bgEffects.set(DEFAULT_BG_EFFECTS); + }; + + /** + * Update noise settings + */ + const updateNoise = (key: keyof BackgroundEffects["noise"], value: any) => { + bgEffects.set({ + ...$bgEffects, + noise: { ...$bgEffects.noise, [key]: value } + }); + }; + + /** + * Update glow settings + */ + const updateGlow = (key: keyof BackgroundEffects["glows"], value: any) => { + bgEffects.set({ + ...$bgEffects, + glows: { ...$bgEffects.glows, [key]: value } + }); + }; + + /** + * Update glow color (convert from RGB to HSL) + */ + const updateGlowColor = (colorNum: 1 | 2, rgbValue: string) => { + const hslValue = rgbToHSL(rgbValue); + const key = colorNum === 1 ? "color1" : "color2"; + updateGlow(key, hslValue); + }; + /** * Get the display label for a color key */ @@ -122,8 +186,8 @@ }; // Sort palette colors for display: E first, then 0-6, then -1 last - const sortPaletteColors = (colors: CalColors): string[] => { - return Object.entries(colors) + const sortPaletteColors = (palette: Palette): string[] => { + return Object.entries(palette.colors) .sort(([a], [b]) => { if (a === "E") return -1; if (b === "E") return 1; @@ -133,6 +197,9 @@ }) .map(([_, value]) => value); }; + + // State for collapsible sections + let effectsExpanded = false;
- {#each Object.entries(colorPalettes) as [name, colors]} + {#each Object.entries(colorPalettes) as [name, palette]} +
+

+ +
+ + + {#if effectsExpanded} +
+ +
+
+ + Noise Texture + + +
+ + {#if $bgEffects.noise.enabled} +
+
+ + + updateNoise( + "opacity", + parseFloat( + e.currentTarget.value + ) + )} + class="slider" /> +
+
+ + + updateNoise( + "baseFrequency", + parseFloat( + e.currentTarget.value + ) + )} + class="slider" /> +
+
+ {/if} +
+ + +
+
+ + Gradient Glows + + +
+ + {#if $bgEffects.glows.enabled} +
+
+
+ + +
+ {/if} +
+
{#if lastSelectedTheme} @@ -323,4 +672,19 @@ transform: rotate(360deg); } } + + .slider { + @apply w-full h-1.5 rounded-full appearance-none cursor-pointer + bg-zinc-300 dark:bg-zinc-600; + } + + .slider::-webkit-slider-thumb { + @apply appearance-none w-3.5 h-3.5 rounded-full + bg-blue-600 cursor-pointer; + } + + .slider::-moz-range-thumb { + @apply w-3.5 h-3.5 rounded-full border-0 + bg-blue-600 cursor-pointer; + } diff --git a/apps/web/src/lib/components/recal/Top.svelte b/apps/web/src/lib/components/recal/Top.svelte index d042db78..92db105e 100644 --- a/apps/web/src/lib/components/recal/Top.svelte +++ b/apps/web/src/lib/components/recal/Top.svelte @@ -270,7 +270,8 @@ class="card {$currentSchedule === schedule.id ? 'flex items-center gap-4' : 'termchoice'}" - class:selected={$currentSchedule === schedule.id} + class:selected={$currentSchedule === + schedule.id} on:click={() => $currentSchedule === schedule.id ? modalStore.push("editSchedule") diff --git a/apps/web/src/lib/components/ui/NoiseFilter.svelte b/apps/web/src/lib/components/ui/NoiseFilter.svelte new file mode 100644 index 00000000..86a3416e --- /dev/null +++ b/apps/web/src/lib/components/ui/NoiseFilter.svelte @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/apps/web/src/lib/components/ui/SidePanel.svelte b/apps/web/src/lib/components/ui/SidePanel.svelte index 845f6a1f..34f5ef58 100644 --- a/apps/web/src/lib/components/ui/SidePanel.svelte +++ b/apps/web/src/lib/components/ui/SidePanel.svelte @@ -34,7 +34,7 @@
{/if} + + diff --git a/apps/web/src/lib/scripts/ReCal+/palettes.ts b/apps/web/src/lib/scripts/ReCal+/palettes.ts index c912eb2c..11276b2a 100644 --- a/apps/web/src/lib/scripts/ReCal+/palettes.ts +++ b/apps/web/src/lib/scripts/ReCal+/palettes.ts @@ -1,183 +1,253 @@ // Credits: https://coolors.co/palettes/popular/7%20colors import type { CalColors } from "$lib/stores/styles"; -// Title -> CalColors -export const colorPalettes: Record = { +export type Palette = { + colors: CalColors; + bgLight: string; // hex color for light mode background + bgDark: string; // hex color for dark mode background +}; + +// Title -> Palette +export const colorPalettes: Record = { Bright: { - "-1": "#a8a8a8", - "0": "#9ee09e", - "1": "#feb44d", - "2": "#9fc2d0", - "3": "#fcfc7d", - "4": "#ff6461", - "5": "#ff99cc", - "6": "#cc99c8", - "E": "#e0e8f0" + colors: { + "-1": "#a8a8a8", + "0": "#9ee09e", + "1": "#feb44d", + "2": "#9fc2d0", + "3": "#fcfc7d", + "4": "#ff6461", + "5": "#ff99cc", + "6": "#cc99c8", + "E": "#e0e8f0" + }, + bgLight: "#ffffff", + bgDark: "#18181b" }, // From the original ReCal Legacy: { - "-1": "#e6e6e6", - "0": "#D0DECF", - "1": "#d5dcec", - "2": "#ebd2db", - "3": "#faf4cb", - "4": "#e7dcce", - "5": "#d1e7e4", - "6": "#dcd5e2", - "E": "#e6e8f0" + colors: { + "-1": "#e6e6e6", + "0": "#D0DECF", + "1": "#d5dcec", + "2": "#ebd2db", + "3": "#faf4cb", + "4": "#e7dcce", + "5": "#d1e7e4", + "6": "#dcd5e2", + "E": "#e6e8f0" + }, + bgLight: "#ffffff", + bgDark: "#18181b" }, Coastal: { - "-1": "#e6e6e6", - "0": "#b8e0d2", - "1": "#d6eadf", - "2": "#eac4d5", - "3": "#f9e2ae", - "4": "#95d5b2", - "5": "#a2d2ff", - "6": "#cdb4db", - "E": "#f0f9f4" + colors: { + "-1": "#e6e6e6", + "0": "#b8e0d2", + "1": "#d6eadf", + "2": "#eac4d5", + "3": "#f9e2ae", + "4": "#95d5b2", + "5": "#a2d2ff", + "6": "#cdb4db", + "E": "#f0f9f4" + }, + bgLight: "#ffffff", + bgDark: "#18181b" }, Sunset: { - "-1": "#e6e6e6", - "0": "#ffecd2", - "1": "#ffd6a5", - "2": "#fdcfe8", - "3": "#fff5ba", - "4": "#c8f7dc", - "5": "#d4e4f7", - "6": "#e8d4f7", - "E": "#fff8f0" + colors: { + "-1": "#e6e6e6", + "0": "#ffecd2", + "1": "#ffd6a5", + "2": "#fdcfe8", + "3": "#fff5ba", + "4": "#c8f7dc", + "5": "#d4e4f7", + "6": "#e8d4f7", + "E": "#fff8f0" + }, + bgLight: "#ffffff", + bgDark: "#18181b" }, Crayon: { - "-1": "#e6e6e6", - "0": "#ffadad", - "1": "#ffd6a5", - "2": "#fdffb6", - "3": "#caffbf", - "4": "#9bf6ff", - "5": "#a0c4ff", - "6": "#bdb2ff", - "E": "#fff0f0" + colors: { + "-1": "#e6e6e6", + "0": "#ffadad", + "1": "#ffd6a5", + "2": "#fdffb6", + "3": "#caffbf", + "4": "#9bf6ff", + "5": "#a0c4ff", + "6": "#bdb2ff", + "E": "#fff0f0" + }, + bgLight: "#ffffff", + bgDark: "#18181b" }, Minty: { - "-1": "#e6e6e6", - "0": "#84ffc9", - "1": "#8af2d2", - "2": "#91e5db", - "3": "#97d9e4", - "4": "#9dcced", - "5": "#a4bff6", - "6": "#aab2ff", - "E": "#e6fff0" + colors: { + "-1": "#e6e6e6", + "0": "#84ffc9", + "1": "#8af2d2", + "2": "#91e5db", + "3": "#97d9e4", + "4": "#9dcced", + "5": "#a4bff6", + "6": "#aab2ff", + "E": "#e6fff0" + }, + bgLight: "#ffffff", + bgDark: "#18181b" }, Sweet: { - "-1": "#e6e6e6", - "0": "#f5cbd9", - "1": "#eeccde", - "2": "#e7cce4", - "3": "#e0cde9", - "4": "#d8ceee", - "5": "#d1cef4", - "6": "#cacff9", - "E": "#fdf0ff" + colors: { + "-1": "#e6e6e6", + "0": "#f5cbd9", + "1": "#eeccde", + "2": "#e7cce4", + "3": "#e0cde9", + "4": "#d8ceee", + "5": "#d1cef4", + "6": "#cacff9", + "E": "#fdf0ff" + }, + bgLight: "#ffffff", + bgDark: "#18181b" }, Pearl: { - "-1": "#e6e6e6", - "0": "#e9b7ce", - "1": "#e5c1d4", - "2": "#e2cbda", - "3": "#ded5e0", - "4": "#dadfe5", - "5": "#d7e9eb", - "6": "#d3f3f1", - "E": "#fff5f0" + colors: { + "-1": "#e6e6e6", + "0": "#e9b7ce", + "1": "#e5c1d4", + "2": "#e2cbda", + "3": "#ded5e0", + "4": "#dadfe5", + "5": "#d7e9eb", + "6": "#d3f3f1", + "E": "#fff5f0" + }, + bgLight: "#ffffff", + bgDark: "#18181b" }, Forest: { - "-1": "#A8A8A8", - "0": "#A8B1A9", - "1": "#B1BAA9", - "2": "#C0BDA8", - "3": "#909D88", - "4": "#A7AFAC", - "5": "#E4DED3", - "6": "#D3D0C8", - "E": "#f0f5e6" + colors: { + "-1": "#A8A8A8", + "0": "#A8B1A9", + "1": "#B1BAA9", + "2": "#C0BDA8", + "3": "#909D88", + "4": "#A7AFAC", + "5": "#E4DED3", + "6": "#D3D0C8", + "E": "#f0f5e6" + }, + bgLight: "#ffffff", + bgDark: "#18181b" }, Slate: { - "-1": "#A8A8A8", - "0": "#b9c6cb", - "1": "#a7b7bd", - "2": "#eef2f3", - "3": "#dce3e6", - "4": "#cad4d8", - "5": "#95a8b0", - "6": "#8399a2", - "E": "#f0f5f7" + colors: { + "-1": "#A8A8A8", + "0": "#b9c6cb", + "1": "#a7b7bd", + "2": "#eef2f3", + "3": "#dce3e6", + "4": "#cad4d8", + "5": "#95a8b0", + "6": "#8399a2", + "E": "#f0f5f7" + }, + bgLight: "#ffffff", + bgDark: "#18181b" }, Aurora: { - "-1": "#A8A8A8", - "0": "#1a5c5c", - "1": "#6b4a0a", - "2": "#6b1a4a", - "3": "#1a5c2e", - "4": "#6b3a1a", - "5": "#1a3a6b", - "6": "#4a1a6b", - "E": "#1a2a3a" + colors: { + "-1": "#A8A8A8", + "0": "#1a5c5c", + "1": "#6b4a0a", + "2": "#6b1a4a", + "3": "#1a5c2e", + "4": "#6b3a1a", + "5": "#1a3a6b", + "6": "#4a1a6b", + "E": "#1a2a3a" + }, + bgLight: "#ffffff", + bgDark: "#0f1419" }, Crimson: { - "-1": "#A8A8A8", - "0": "#4a0d1c", - "1": "#5c1024", - "2": "#6e132c", - "3": "#801634", - "4": "#92193c", - "5": "#a41c44", - "6": "#b61f4c", - "E": "#2e0812" + colors: { + "-1": "#A8A8A8", + "0": "#4a0d1c", + "1": "#5c1024", + "2": "#6e132c", + "3": "#801634", + "4": "#92193c", + "5": "#a41c44", + "6": "#b61f4c", + "E": "#2e0812" + }, + bgLight: "#ffffff", + bgDark: "#1a0a0f" }, Pixel: { - "-1": "#A8A8A8", - "0": "#8a004d", - "1": "#005c99", - "2": "#007040", - "3": "#99005c", - "4": "#00668a", - "5": "#008050", - "6": "#a3005c", - "E": "#1a1a2e" + colors: { + "-1": "#A8A8A8", + "0": "#8a004d", + "1": "#005c99", + "2": "#007040", + "3": "#99005c", + "4": "#00668a", + "5": "#008050", + "6": "#a3005c", + "E": "#1a1a2e" + }, + bgLight: "#ffffff", + bgDark: "#0d0d1a" }, Midnight: { - "-1": "#A8A8A8", - "0": "#1a5568", - "1": "#1a4a5a", - "2": "#255060", - "3": "#2a4578", - "4": "#3a3a72", - "5": "#453580", - "6": "#503570", - "E": "#1a3246" + colors: { + "-1": "#A8A8A8", + "0": "#1a5568", + "1": "#1a4a5a", + "2": "#255060", + "3": "#2a4578", + "4": "#3a3a72", + "5": "#453580", + "6": "#503570", + "E": "#1a3246" + }, + bgLight: "#ffffff", + bgDark: "#0a1520" }, Cobalt: { - "-1": "#A8A8A8", - "0": "#3f004d", - "1": "#350455", - "2": "#2a085c", - "3": "#200c64", - "4": "#15106b", - "5": "#0b1473", - "6": "#00187a", - "E": "#2a0033" + colors: { + "-1": "#A8A8A8", + "0": "#3f004d", + "1": "#350455", + "2": "#2a085c", + "3": "#200c64", + "4": "#15106b", + "5": "#0b1473", + "6": "#00187a", + "E": "#2a0033" + }, + bgLight: "#ffffff", + bgDark: "#0d0515" }, Shadow: { - "-1": "#A8A8A8", - "0": "#2a454b", - "1": "#253e45", - "2": "#21373f", - "3": "#1c3139", - "4": "#172a32", - "5": "#13232c", - "6": "#0e1c26", - "E": "#1c2f34" + colors: { + "-1": "#A8A8A8", + "0": "#2a454b", + "1": "#253e45", + "2": "#21373f", + "3": "#1c3139", + "4": "#172a32", + "5": "#13232c", + "6": "#0e1c26", + "E": "#1c2f34" + }, + bgLight: "#ffffff", + bgDark: "#0a1214" } }; diff --git a/apps/web/src/lib/scripts/convert.ts b/apps/web/src/lib/scripts/convert.ts index 865c1a16..0b407fa4 100644 --- a/apps/web/src/lib/scripts/convert.ts +++ b/apps/web/src/lib/scripts/convert.ts @@ -325,6 +325,27 @@ const rgbToHSL = (hex: string) => { return `hsl(${hVal}, ${sVal}%, ${lVal}%)`; }; +/** + * Converts an HSL color to RGB components string (for CSS rgba usage) + * @param hsl + * @returns RGB components string like "255, 149, 0" + */ +const hslToRGBComponents = (hsl: string): string => { + // eslint-disable-next-line prefer-const + let [h, s, l] = hsl.split(",").map(x => parseInt(x.replace(/\D/g, ""))); + + l /= 100; + const a = (s * Math.min(l, 1 - l)) / 100; + + const f = (n: number) => { + const k = (n + h / 30) % 12; + const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1); + return Math.round(255 * color); + }; + + return `${f(0)}, ${f(8)}, ${f(4)}`; +}; + //---------------------------------------------------------------------- // TODO Refactor @@ -339,5 +360,6 @@ export { normalizeText, darkenHSL, hslToRGB, + hslToRGBComponents, rgbToHSL }; diff --git a/apps/web/src/lib/stores/styles.ts b/apps/web/src/lib/stores/styles.ts index b8929a48..dd2cfcd7 100644 --- a/apps/web/src/lib/stores/styles.ts +++ b/apps/web/src/lib/stores/styles.ts @@ -2,6 +2,150 @@ import { darkenHSL, rgbToHSL } from "$lib/scripts/convert"; import { colorPalettes } from "$lib/scripts/ReCal+/palettes"; import { get, writable } from "svelte/store"; +//---------------------------------------------------------------------- +// Background Colors +//---------------------------------------------------------------------- + +export type BgColors = { + light: string; // HSL string + dark: string; // HSL string +}; + +export const DEFAULT_BG_COLORS: BgColors = { + light: "hsl(0, 0%, 100%)", // white + dark: "hsl(240, 6%, 10%)" // zinc-900 +}; + +const initializeBgColors = (): BgColors => { + if (typeof window === "undefined") return DEFAULT_BG_COLORS; + + const stored = localStorage.getItem("bgColors"); + if (!stored) { + localStorage.setItem("bgColors", JSON.stringify(DEFAULT_BG_COLORS)); + return DEFAULT_BG_COLORS; + } + + try { + const parsed = JSON.parse(stored); + if (parsed.light && parsed.dark) { + return parsed; + } + } catch { + // Invalid JSON, reset to defaults + } + + localStorage.setItem("bgColors", JSON.stringify(DEFAULT_BG_COLORS)); + return DEFAULT_BG_COLORS; +}; + +const { + subscribe: bgSubscribe, + update: bgUpdate, + set: bgSet +} = writable(initializeBgColors()); + +export const bgColors = { + subscribe: bgSubscribe, + update: bgUpdate, + set: (value: BgColors) => { + bgSet(value); + localStorage.setItem("bgColors", JSON.stringify(value)); + } +}; + +//---------------------------------------------------------------------- +// Background Effects (Noise + Glows) +//---------------------------------------------------------------------- + +export type BackgroundEffects = { + noise: { + enabled: boolean; + opacity: number; // 0-1 + baseFrequency: number; // default 1.5 + }; + glows: { + enabled: boolean; + color1: string; // HSL + color2: string; // HSL + opacity: number; // 0-1 + }; +}; + +export const DEFAULT_BG_EFFECTS: BackgroundEffects = { + noise: { + enabled: false, + opacity: 0.5, + baseFrequency: 1.5 + }, + glows: { + enabled: false, + color1: "hsl(36, 100%, 50%)", // orange + color2: "hsl(211, 100%, 50%)", // blue + opacity: 0.15 + } +}; + +const initializeBgEffects = (): BackgroundEffects => { + if (typeof window === "undefined") return DEFAULT_BG_EFFECTS; + + const stored = localStorage.getItem("bgEffects"); + if (!stored) { + localStorage.setItem("bgEffects", JSON.stringify(DEFAULT_BG_EFFECTS)); + return DEFAULT_BG_EFFECTS; + } + + try { + const parsed = JSON.parse(stored); + // Validate structure + if (parsed.noise && parsed.glows) { + return { + noise: { + enabled: + parsed.noise.enabled ?? + DEFAULT_BG_EFFECTS.noise.enabled, + opacity: + parsed.noise.opacity ?? + DEFAULT_BG_EFFECTS.noise.opacity, + baseFrequency: + parsed.noise.baseFrequency ?? + DEFAULT_BG_EFFECTS.noise.baseFrequency + }, + glows: { + enabled: + parsed.glows.enabled ?? + DEFAULT_BG_EFFECTS.glows.enabled, + color1: + parsed.glows.color1 ?? DEFAULT_BG_EFFECTS.glows.color1, + color2: + parsed.glows.color2 ?? DEFAULT_BG_EFFECTS.glows.color2, + opacity: + parsed.glows.opacity ?? DEFAULT_BG_EFFECTS.glows.opacity + } + }; + } + } catch { + // Invalid JSON, reset to defaults + } + + localStorage.setItem("bgEffects", JSON.stringify(DEFAULT_BG_EFFECTS)); + return DEFAULT_BG_EFFECTS; +}; + +const { + subscribe: effectsSubscribe, + update: effectsUpdate, + set: effectsSet +} = writable(initializeBgEffects()); + +export const bgEffects = { + subscribe: effectsSubscribe, + update: effectsUpdate, + set: (value: BackgroundEffects) => { + effectsSet(value); + localStorage.setItem("bgEffects", JSON.stringify(value)); + } +}; + //---------------------------------------------------------------------- // General //---------------------------------------------------------------------- @@ -130,7 +274,7 @@ const initializeCalColors = (): CalColors => { if (!("E" in parsedColors)) { for (const key in colorPalettes) { const palette = colorPalettes[key]; - const hslPalette = Object.entries(palette) + const hslPalette = Object.entries(palette.colors) .map(([key, value]) => [key, rgbToHSL(value)]) .reduce( (acc, [key, value]) => ({ ...acc, [key]: value }), diff --git a/apps/web/src/routes/(apps)/+layout.svelte b/apps/web/src/routes/(apps)/+layout.svelte index b1bb5124..e8dfc362 100644 --- a/apps/web/src/routes/(apps)/+layout.svelte +++ b/apps/web/src/routes/(apps)/+layout.svelte @@ -10,7 +10,16 @@ -
+
+ + diff --git a/apps/web/src/routes/(apps)/recalplus/+page.svelte b/apps/web/src/routes/(apps)/recalplus/+page.svelte index 2495c818..a49ef140 100644 --- a/apps/web/src/routes/(apps)/recalplus/+page.svelte +++ b/apps/web/src/routes/(apps)/recalplus/+page.svelte @@ -142,8 +142,7 @@
+ class="flex flex-col flex-1 w-full max-w-[1500px] mx-auto max-h-screen overflow-clip">
diff --git a/apps/web/src/routes/(apps)/reqtree/+page.svelte b/apps/web/src/routes/(apps)/reqtree/+page.svelte index a8633831..661a1364 100644 --- a/apps/web/src/routes/(apps)/reqtree/+page.svelte +++ b/apps/web/src/routes/(apps)/reqtree/+page.svelte @@ -9,7 +9,7 @@
+ class="flex-1 w-full max-w-[1500px] mx-auto dark:text-white max-h-screen overflow-clip">

ReqTree

diff --git a/apps/web/src/routes/+layout.svelte b/apps/web/src/routes/+layout.svelte index 90033539..9b3e35aa 100644 --- a/apps/web/src/routes/+layout.svelte +++ b/apps/web/src/routes/+layout.svelte @@ -3,8 +3,15 @@ import { invalidate } from "$app/navigation"; import { onMount } from "svelte"; import { browser } from "$app/environment"; - import { darkTheme, isMobile } from "$lib/stores/styles"; + import { + darkTheme, + isMobile, + bgColors, + bgEffects + } from "$lib/stores/styles"; + import { hslToRGBComponents } from "$lib/scripts/convert"; import ToastLib from "$lib/components/general/ToastLib.svelte"; + import NoiseFilter from "$lib/components/ui/NoiseFilter.svelte"; export let data; @@ -20,6 +27,48 @@ } } + // Apply background color CSS variables + $: if (browser) { + document.documentElement.style.setProperty( + "--bg-light", + $bgColors.light + ); + document.documentElement.style.setProperty("--bg-dark", $bgColors.dark); + } + + // Apply background effects CSS variables + $: if (browser) { + const noiseOpacity = $bgEffects.noise.enabled + ? $darkTheme + ? Math.min($bgEffects.noise.opacity * 0.16, 0.15) // Much subtler in dark mode + : $bgEffects.noise.opacity + : 0; + const glowOpacity = $bgEffects.glows.enabled + ? $bgEffects.glows.opacity + : 0; + + document.documentElement.style.setProperty( + "--noise-opacity", + String(noiseOpacity) + ); + document.documentElement.style.setProperty( + "--noise-frequency", + String($bgEffects.noise.baseFrequency) + ); + document.documentElement.style.setProperty( + "--glow-opacity", + String(glowOpacity) + ); + document.documentElement.style.setProperty( + "--glow-color-1", + hslToRGBComponents($bgEffects.glows.color1) + ); + document.documentElement.style.setProperty( + "--glow-color-2", + hslToRGBComponents($bgEffects.glows.color2) + ); + } + onMount(() => { $isMobile = window.innerWidth < 600; @@ -35,6 +84,7 @@ }); +
From 1c00ae898491eb0af80581f9bc84dafb5abe6ffc Mon Sep 17 00:00:00 2001 From: Joshua Lau Date: Mon, 1 Dec 2025 15:58:07 -0500 Subject: [PATCH 02/11] Improve backgrounds --- .../general/style/ThemePanel.svelte | 86 +++++++++++++++---- apps/web/src/lib/scripts/ReCal+/palettes.ts | 44 +++++----- 2 files changed, 92 insertions(+), 38 deletions(-) diff --git a/apps/web/src/lib/components/general/style/ThemePanel.svelte b/apps/web/src/lib/components/general/style/ThemePanel.svelte index 9d9148a0..5f48495e 100644 --- a/apps/web/src/lib/components/general/style/ThemePanel.svelte +++ b/apps/web/src/lib/components/general/style/ThemePanel.svelte @@ -43,14 +43,38 @@ // Track the last selected theme for "Reset to Theme" functionality // Load from localStorage on init - let lastSelectedTheme: { name: string; colors: CalColors } | null = null; + let lastSelectedTheme: { + name: string; + colors: CalColors; + bgColors: BgColors; + } | null = null; // Load last selected theme from localStorage on mount if (typeof window !== "undefined") { const stored = localStorage.getItem(LAST_THEME_KEY); if (stored) { try { - lastSelectedTheme = JSON.parse(stored); + const parsed = JSON.parse(stored); + // Handle migration from old format without bgColors + if (parsed.bgColors) { + lastSelectedTheme = parsed; + } else if (parsed.name && colorPalettes[parsed.name]) { + // Reconstruct bgColors from palette + const palette = colorPalettes[parsed.name]; + lastSelectedTheme = { + ...parsed, + bgColors: { + light: rgbToHSL(palette.bgLight), + dark: rgbToHSL(palette.bgDark) + } + }; + localStorage.setItem( + LAST_THEME_KEY, + JSON.stringify(lastSelectedTheme) + ); + } else { + lastSelectedTheme = parsed; + } } catch { localStorage.removeItem(LAST_THEME_KEY); } @@ -69,13 +93,20 @@ ) as CalColors; calColors.set(hslColors); - lastSelectedTheme = { name, colors: hslColors }; // Apply background colors from palette - bgColors.set({ + const themeBgColors: BgColors = { light: rgbToHSL(palette.bgLight), dark: rgbToHSL(palette.bgDark) - }); + }; + bgColors.set(themeBgColors); + + // Store theme with both colors and bgColors + lastSelectedTheme = { + name, + colors: hslColors, + bgColors: themeBgColors + }; // Persist to localStorage if (typeof window !== "undefined") { @@ -102,11 +133,23 @@ }; /** - * Reset to last selected theme + * Reset to last selected theme (both colors and background) */ const resetToTheme = () => { if (lastSelectedTheme) { calColors.set(lastSelectedTheme.colors); + if (lastSelectedTheme.bgColors) { + bgColors.set(lastSelectedTheme.bgColors); + } + } + }; + + /** + * Reset background colors to last selected theme + */ + const resetBgToTheme = () => { + if (lastSelectedTheme?.bgColors) { + bgColors.set(lastSelectedTheme.bgColors); } }; @@ -198,8 +241,8 @@ .map(([_, value]) => value); }; - // State for collapsible sections - let effectsExpanded = false; + // State for collapsible sections - auto-expand if any effects are enabled + let effectsExpanded = $bgEffects.noise.enabled || $bgEffects.glows.enabled;
- +
+ {#if lastSelectedTheme?.bgColors} + + {/if} + +
@@ -391,7 +445,7 @@ class="flex items-center justify-between w-full text-left">

- Background Effects + Advanced Background Effects

= { "6": "#dcd5e2", "E": "#e6e8f0" }, - bgLight: "#ffffff", + bgLight: "#fafaf8", bgDark: "#18181b" }, Coastal: { @@ -52,7 +52,7 @@ export const colorPalettes: Record = { "6": "#cdb4db", "E": "#f0f9f4" }, - bgLight: "#ffffff", + bgLight: "#f8fcfa", bgDark: "#18181b" }, Sunset: { @@ -67,7 +67,7 @@ export const colorPalettes: Record = { "6": "#e8d4f7", "E": "#fff8f0" }, - bgLight: "#ffffff", + bgLight: "#fffcf8", bgDark: "#18181b" }, Crayon: { @@ -82,7 +82,7 @@ export const colorPalettes: Record = { "6": "#bdb2ff", "E": "#fff0f0" }, - bgLight: "#ffffff", + bgLight: "#fffefa", bgDark: "#18181b" }, Minty: { @@ -97,7 +97,7 @@ export const colorPalettes: Record = { "6": "#aab2ff", "E": "#e6fff0" }, - bgLight: "#ffffff", + bgLight: "#f6fffe", bgDark: "#18181b" }, Sweet: { @@ -112,7 +112,7 @@ export const colorPalettes: Record = { "6": "#cacff9", "E": "#fdf0ff" }, - bgLight: "#ffffff", + bgLight: "#f5e6fa", bgDark: "#18181b" }, Pearl: { @@ -127,23 +127,23 @@ export const colorPalettes: Record = { "6": "#d3f3f1", "E": "#fff5f0" }, - bgLight: "#ffffff", + bgLight: "#fffaf8", bgDark: "#18181b" }, - Forest: { - colors: { - "-1": "#A8A8A8", - "0": "#A8B1A9", - "1": "#B1BAA9", - "2": "#C0BDA8", - "3": "#909D88", - "4": "#A7AFAC", - "5": "#E4DED3", - "6": "#D3D0C8", - "E": "#f0f5e6" - }, - bgLight: "#ffffff", - bgDark: "#18181b" + Book: { + colors: { + "-1": "#c4b8a8", + "0": "#d4c4a8", + "1": "#c9b896", + "2": "#e8dcc8", + "3": "#d9cdb5", + "4": "#c4b090", + "5": "#bfad8f", + "6": "#d0c4ac", + "E": "#e5dbc8" + }, + bgLight: "#f5f0e6", + bgDark: "#2a2520" }, Slate: { colors: { @@ -157,7 +157,7 @@ export const colorPalettes: Record = { "6": "#8399a2", "E": "#f0f5f7" }, - bgLight: "#ffffff", + bgLight: "#f8fafb", bgDark: "#18181b" }, Aurora: { From 36403b41022e7e2b2f7b11a94a6387a628071253 Mon Sep 17 00:00:00 2001 From: Joshua Lau Date: Mon, 1 Dec 2025 16:05:13 -0500 Subject: [PATCH 03/11] Yay better bg colors --- apps/web/src/app.pcss | 18 ++++++++++++++++++ apps/web/src/lib/components/recal/Top.svelte | 4 ++-- .../recal/modals/AdvancedSearch.svelte | 18 ++++++++++++------ .../recal/modals/events/EditEvent.svelte | 2 +- .../recal/modals/events/ManageEvents.svelte | 2 +- apps/web/src/lib/scripts/ReCal+/palettes.ts | 12 ++++++------ 6 files changed, 40 insertions(+), 16 deletions(-) diff --git a/apps/web/src/app.pcss b/apps/web/src/app.pcss index 924236eb..8fcd52b1 100644 --- a/apps/web/src/app.pcss +++ b/apps/web/src/app.pcss @@ -26,6 +26,24 @@ background-color: var(--bg-dark); } +@layer components { + /* Themed panel/control backgrounds - slightly offset from main bg */ + .themed-panel { + background-color: color-mix(in srgb, var(--bg-light) 90%, #000); + } + .dark .themed-panel { + background-color: color-mix(in srgb, var(--bg-dark) 85%, #fff); + } + + /* Lighter variant for nested panels */ + .themed-panel-light { + background-color: color-mix(in srgb, var(--bg-light) 95%, #000); + } + .dark .themed-panel-light { + background-color: color-mix(in srgb, var(--bg-dark) 90%, #fff); + } +} + /* Background effects class for noise and glows */ .bg-effects { position: relative; diff --git a/apps/web/src/lib/components/recal/Top.svelte b/apps/web/src/lib/components/recal/Top.svelte index 92db105e..01ceaa69 100644 --- a/apps/web/src/lib/components/recal/Top.svelte +++ b/apps/web/src/lib/components/recal/Top.svelte @@ -164,7 +164,7 @@ dark:text-zinc-100 text-sm">
{#each Object.keys(ACTIVE_TERMS).map( x => parseInt(x) ) as activeTerm} +
+ + +
+ Color + +
+ + +
+ Position X + +
+ + +
+ Position Y + +
+ + +
+ Size + +
+ + +
+ Opacity + +
+ + +
+ Blur + +
+ + +
+ Shape +
+ + +
+
+
+ + diff --git a/apps/web/src/lib/components/general/style/GradientList.svelte b/apps/web/src/lib/components/general/style/GradientList.svelte new file mode 100644 index 00000000..97b5084f --- /dev/null +++ b/apps/web/src/lib/components/general/style/GradientList.svelte @@ -0,0 +1,119 @@ + + +
+
+ Gradients + {gradients.length}/{MAX_GRADIENTS} +
+ +
+ {#each gradients as gradient, index (gradient.id)} + + {/each} + + {#if canAdd} + + {/if} +
+
+ + diff --git a/apps/web/src/lib/components/general/style/ThemePanel.svelte b/apps/web/src/lib/components/general/style/ThemePanel.svelte index 8058548e..30ff093b 100644 --- a/apps/web/src/lib/components/general/style/ThemePanel.svelte +++ b/apps/web/src/lib/components/general/style/ThemePanel.svelte @@ -12,12 +12,18 @@ appFont, FONT_OPTIONS, DEFAULT_FONT, + createGradient, + MAX_GRADIENTS, type CalColors, type BgColors, - type BackgroundEffects + type BackgroundEffects, + type GradientConfig } from "$lib/stores/styles"; import { colorPalettes, type Palette } from "$lib/scripts/ReCal+/palettes"; import { rgbToHSL, hslToRGB } from "$lib/scripts/convert"; + import GradientCanvas from "./GradientCanvas.svelte"; + import GradientEditor from "./GradientEditor.svelte"; + import GradientList from "./GradientList.svelte"; $: open = $panelStore === "theme"; @@ -216,13 +222,66 @@ }); }; + // Selected gradient for editing + let selectedGradientId: string | null = null; + + $: selectedGradient = $bgEffects.glows.gradients.find( + g => g.id === selectedGradientId + ); + /** - * Update glow color (convert from RGB to HSL) + * Handle gradient selection */ - const updateGlowColor = (colorNum: 1 | 2, rgbValue: string) => { - const hslValue = rgbToHSL(rgbValue); - const key = colorNum === 1 ? "color1" : "color2"; - updateGlow(key, hslValue); + const handleGradientSelect = (e: CustomEvent<{ id: string }>) => { + selectedGradientId = e.detail.id || null; + }; + + /** + * Handle gradient position change (from canvas drag) + */ + const handleGradientMove = ( + e: CustomEvent<{ id: string; x: number; y: number }> + ) => { + const { id, x, y } = e.detail; + const updatedGradients = $bgEffects.glows.gradients.map(g => + g.id === id ? { ...g, x, y } : g + ); + updateGlow("gradients", updatedGradients); + }; + + /** + * Handle gradient update (from editor) + */ + const handleGradientUpdate = (e: CustomEvent) => { + const updated = e.detail; + const updatedGradients = $bgEffects.glows.gradients.map(g => + g.id === updated.id ? updated : g + ); + updateGlow("gradients", updatedGradients); + }; + + /** + * Handle gradient delete + */ + const handleGradientDelete = (e: CustomEvent<{ id: string }>) => { + const { id } = e.detail; + const updatedGradients = $bgEffects.glows.gradients.filter( + g => g.id !== id + ); + updateGlow("gradients", updatedGradients); + if (selectedGradientId === id) { + selectedGradientId = null; + } + }; + + /** + * Add a new gradient + */ + const handleAddGradient = () => { + if ($bgEffects.glows.gradients.length >= MAX_GRADIENTS) return; + const newGradient = createGradient(); + updateGlow("gradients", [...$bgEffects.glows.gradients, newGradient]); + selectedGradientId = newGradient.id; }; /** @@ -324,12 +383,12 @@ ? `--ring-color: ${$calColors["0"]}; border-color: ${$calColors["0"]}` : ""}> Aa + class="text-[12px] text-zinc-500 dark:text-zinc-400 truncate w-full text-center"> {font.name} @@ -611,83 +670,50 @@
{#if $bgEffects.glows.enabled} -
-
-
{:catch}