From e1675a2bb7bc3be16dbe44ca19ad3d36ff69d00f Mon Sep 17 00:00:00 2001 From: Harrison Lloyd Date: Wed, 10 Dec 2025 12:18:16 -0800 Subject: [PATCH 01/18] saving current changes so that the theme page opens first --- src/App.tsx | 6 + src/themeOpenPage.css | 399 ++++++++++++++++++++++++++++++++++++++++++ src/themeOpenPage.tsx | 135 ++++++++++++++ 3 files changed, 540 insertions(+) create mode 100644 src/themeOpenPage.css create mode 100644 src/themeOpenPage.tsx diff --git a/src/App.tsx b/src/App.tsx index 05fade1..e31d123 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import { Grid } from "./Grid"; import { nanoid } from "nanoid"; import { toPng } from "html-to-image"; import styles from "./App.css"; +import OpeningTheme from "./themeOpenPage"; export interface Template { name: string; @@ -67,6 +68,7 @@ const App = () => { const [widgets, setWidgets] = useState[]>([]); const [templates, setTemplates] = useState([]); const [activeTemplate, setActiveTemplate] = useState(0); + const [openingTheme, setOpeningTheme] = useState(true); const gridRef = useRef(null); @@ -153,6 +155,10 @@ const App = () => { readTemplates(); }, []); + if (openingTheme) { + return setOpeningTheme(false)} />; + } + return (
* { position: relative; z-index: 1; } + +.themeOptions { + width: 220px; + height: 165px; + display: flex; + align-items: center; + justify-content: center; + + border: 2px solid rgba(0, 0, 0, 0.1); + border-radius: 16px; + background: rgba(255, 255, 255, 0.7); + backdrop-filter: blur(10px); + cursor: pointer; + font-size: 18px; + font-weight: 500; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + box-sizing: border-box; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.themeOptions:hover { + transform: translateY(-4px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); + border-color: rgba(0, 0, 0, 0.2); +} + +.colorBg { + background-image: url(https://images.unsplash.com/photo-1507166763745-bfe008fbb831?q=80&w=960&auto=format); + color: white; +} + +.side { + opacity: 0.5; + transform: scale(0.95); +} + +.navButton { /* These are the Xs that allow you to move left and right on the themes*/ + position: absolute; + top: 50%; + transform: translateY(-50%); + padding: 10px 14px; + border: 2px solid rgba(0, 0, 0, 0.15); + border-radius: 12px; + background: rgba(255, 255, 255, 0.8); + backdrop-filter: blur(10px); + cursor: pointer; + font-size: 18px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + z-index: 1; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + transition: all 0.2s ease; +} + +.navButton:hover { + background: rgba(255, 255, 255, 0.95); + transform: translateY(-50%) scale(1.05); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); + border-color: rgba(0, 0, 0, 0.25); +} + +.navPrev { left: -55px; } +.navNext { right: -55px; } + +.selected { + border: 2px solid #5b7ec6; + opacity: 1; + transform: scale(1); + box-shadow: 0 0 0 4px rgba(91, 126, 198, 0.2), 0 8px 24px rgba(0, 0, 0, 0.12); +} + +.selected:hover { + border: 2px solid #5b7ec6; + box-shadow: 0 0 0 4px rgba(107, 137, 196, 0.3), 0 8px 24px rgba(0, 0, 0, 0.15); + transform: translateY(-4px); +} + +.optionsRow { + position: absolute; + top: 250px; + left: 50%; + transform: translateX(-50%); + display:flex; + gap: 24px; + align-items: center; + justify-content: center; + width: 720px; + +} + +.bottomRow { + display: flex; + position: absolute; + gap: 16px; + top: 470px; + left: 50%; + transform: translateX(-50%); + justify-content: center; +} + +.actionButton { + padding: 12px 32px; + border: 2px solid rgba(0, 0, 0, 0.15); + border-radius: 12px; + background: rgba(255, 255, 255, 0.8); + backdrop-filter: blur(10px); + cursor: pointer; + font-size: 16px; + font-weight: 500; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + transition: all 0.2s ease; +} + +.actionButton:hover { + background: rgba(255, 255, 255, 0.95); + transform: translateY(-2px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); + border-color: rgba(0, 0, 0, 0.25); +} + +.actionButton:first-child { + background: linear-gradient(135deg, #4a90e2 0%, #357abd 100%); + color: white; + border-color: transparent; +} + +.actionButton:first-child:hover { + background: linear-gradient(135deg, #357abd 0%, #2868a8 100%); + box-shadow: 0 4px 16px rgba(74, 144, 226, 0.4); +} + + +.welcome{ + color: #1a1a2e; + font-size: 56px; + font-weight: 700; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + letter-spacing: -1.5px; + vertical-align: top; + position: absolute; + top: 44px; + left: 50%; + transform: translateX(-50%); + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); +} + +.chooseTheme { + color: #4a4a68; + font-size: 36px; + font-weight: 400; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + letter-spacing: -0.5px; + vertical-align: top; + position: absolute; + top: 120px; + left: 50%; + transform: translateX(-50%); + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.03); +} + +.progressDots { + position: absolute; + top: 425px; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 8px; + align-items: center; +} + +.dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: rgba(0, 0, 0, 0.2); + transition: all 0.3s ease; +} + +.dotActive { + width: 10px; + height: 10px; + background: #5b7ec6; + box-shadow: 0 0 0 3px rgba(91, 126, 198, 0.2); +} + +.errorMessage { + position: absolute; + top: 520px; + left: 50%; + transform: translateX(-50%); + padding: 10px 20px; + background: rgba(255, 255, 255, 0.85); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + color: #8b5a5a; + border: 1px solid rgba(139, 90, 90, 0.2); + border-radius: 8px; + font-size: 13px; + font-weight: 500; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + animation: slideIn 0.3s ease; +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateX(-50%) translateY(-10px); + } + to { + opacity: 1; + transform: translateX(-50%) translateY(0); + } +} + +.lightDarkSplit { + position: relative; + overflow: hidden; + padding: 0; + border-radius: 16px; +} + +.lightDarkSelected { + box-shadow: 0 0 0 4px rgba(91, 126, 198, 0.2), 0 8px 24px rgba(0, 0, 0, 0.12); + transform: scale(1); + opacity: 1; +} + +.lightDarkSplit .splitContainer { + width: 100%; + height: 100%; + display: flex; + position: relative; +} + +.lightDarkSplit .lightSide, +.lightDarkSplit .darkSide { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + gap: 8px; + position: absolute; + cursor: pointer; + transition: all 0.3s ease; + padding: 20px; + box-sizing: border-box; +} + +.lightDarkSplit .lightSide { + background: linear-gradient(135deg, #ffffff 0%, #f0f4f8 100%); + clip-path: polygon(0 0, 0 100%, 100% 100%); + color: #333; + left: 0; + top: 0; + align-items: flex-start; + justify-content: flex-end; +} + +.lightDarkSplit .darkSide { + background: linear-gradient(135deg, #1c1c31 0%, #2c2c3f 100%); + clip-path: polygon(0 0, 100% 0, 100% 100%); + color: #fff; + right: 0; + top: 0; + align-items: flex-end; + justify-content: flex-start; +} + +.lightDarkSplit .iconWrapper { + font-size: 32px; + display: flex; + align-items: center; + justify-content: center; + position: relative; + z-index: 2; + transition: transform 0.2s ease; +} + +.lightDarkSplit .lightSide .iconWrapper { + color: #fbbf24; +} + +.lightDarkSplit .darkSide .iconWrapper { + color: #ddd; +} + +.lightDarkSplit .modeLabel { + font-size: 14px; + font-weight: 500; + z-index: 2; + transition: transform 0.2s ease; + position: relative; +} + +.lightDarkSplit .lightSide:hover .iconWrapper, +.lightDarkSplit .lightSide:hover .modeLabel, +.lightDarkSplit .darkSide:hover .iconWrapper, +.lightDarkSplit .darkSide:hover .modeLabel { + transform: scale(1.1); +} + +.lightDarkSplit .lightSide.selectedSide, +.lightDarkSplit .darkSide.selectedSide { + z-index: 3; +} + +.lightDarkSplit .lightSide.selectedSide, +.lightDarkSplit .darkSide.selectedSide { + /* Box-shadow is now on parent .lightDarkSelected class */ +} + +.lightDarkSplit .lightSide.selectedSide::before { + content: ""; + position: absolute; + left: -1px; + bottom: -1px; + width: calc(100% + 1px); + height: calc(100% + 1px); + border-left: 4.5px solid #5b7ec6; + border-bottom: 4.5px solid #5b7ec6; + border-bottom-left-radius: 16px; + pointer-events: none; + z-index: 1; + clip-path: polygon(0 0, 0 100%, 100% 100%); +} + +.lightDarkSplit .darkSide.selectedSide::before { + content: ""; + position: absolute; + top: -1px; + right: -1px; + width: calc(100% + 1px); + height: calc(100% + 1px); + border-top: 4px solid #5b7ec6; + border-right: 4px solid #5b7ec6; + border-top-right-radius: 16px; + pointer-events: none; + z-index: 1; + clip-path: polygon(0 0, 100% 0, 100% 100%); +} + +.lightDarkSplit .lightSide.selectedSide::after, +.lightDarkSplit .darkSide.selectedSide::after { + content: ""; + position: absolute; + width: 141.42%; + height: 6.5px; + background: #5b7ec6; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) rotate(36.5deg); + pointer-events: none; + z-index: 10; +} + +.lightDarkSplit .lightSide.selectedSide .iconWrapper, +.lightDarkSplit .lightSide.selectedSide .modeLabel { + transform: scale(1.1); +} + +.lightDarkSplit .darkSide.selectedSide .iconWrapper, +.lightDarkSplit .darkSide.selectedSide .modeLabel { + transform: scale(1.1); +} \ No newline at end of file diff --git a/src/themeOpenPage.tsx b/src/themeOpenPage.tsx new file mode 100644 index 0000000..f06594c --- /dev/null +++ b/src/themeOpenPage.tsx @@ -0,0 +1,135 @@ +import React, { useState } from "react"; +import styles from "./themeOpenPage.css"; +import { CaretLeftIcon, CaretRightIcon, Sun, Moon } from "@phosphor-icons/react"; + +type Props = { + onContinue:()=>void +}; + +function OpeningTheme({ onContinue }: Props) { + const themes = ["Blur","Color background","Premade Layout","Light/Dark mode"]; + const [index, setIndex] = React.useState(0); + const n = themes.length; + const left = (index - 1 + n) % n; + const right = (index + 1) % n; + const [selected, setSelected] = useState>(new Set()); + const [lightDarkMode, setLightDarkMode] = useState>(new Map()); + const [showError, setShowError] = useState(false); + const isSelected = (i: number) => selected.has(i); + + const toggle = (i: number) => setSelected(prev => { + const next = new Set(prev); + next.has(i) ? next.delete(i) : next.add(i); + return next; + }); + + const toggleLightDark = (i: number, mode: 'light' | 'dark') => { + setLightDarkMode(prev => { + const next = new Map(prev); + const current = next.get(i); + if (current === mode) { + next.delete(i); + setSelected(prev => { + const nextSelected = new Set(prev); + nextSelected.delete(i); + return nextSelected; + }); + } else { + next.set(i, mode); + setSelected(prev => new Set(prev).add(i)); + } + return next; + }); + }; + + const handleContinue = () => { + if (selected.size === 0) { + setShowError(true); + setTimeout(() => setShowError(false), 3000); // Hide after 3 seconds + } else { + onContinue(); + } + }; + + // helper to build the class string for each tile + const tileClass = (i: number, extra = "") =>[styles.themeOptions,extra, + themes[i] === "Color background" ? styles.colorBg : "", + themes[i] === "Blur" ? styles.blurTile : "", + themes[i] === "Light/Dark mode" ? styles.lightDarkSplit : "", + isSelected(i) && themes[i] !== "Light/Dark mode" ? styles.selected : "", + themes[i] === "Light/Dark mode" && lightDarkMode.has(i) ? styles.lightDarkSelected : "",].join(" ").trim(); + + // Render the Light/Dark mode split theme + const renderLightDarkTheme = (i: number) => { + const selectedMode = lightDarkMode.get(i); + return ( +
+
{ + e.stopPropagation(); + toggleLightDark(i, 'light'); + }} + > +
+ +
+
Light mode
+
+
{ + e.stopPropagation(); + toggleLightDark(i, 'dark'); + }} + > +
+ +
+
Dark mode
+
+
+ ); + }; + + // Helper to render theme content + const renderThemeContent = (i: number) => { + if (themes[i] === "Light/Dark mode") { + return renderLightDarkTheme(i); + } + return themes[i]; + }; + + return ( +
+

Welcome

+

Choose a theme

+ +
+ + + + + +
+ +
+ {themes.map((_, i) => ( +
+ ))} +
+ + {showError && ( +
+ Please select at least one theme +
+ )} + +
+ + +
+
+ ); +} +export default OpeningTheme; \ No newline at end of file From 8350f5bfd81beed413f5b8a2f0ab665981178672 Mon Sep 17 00:00:00 2001 From: Harrison Lloyd Date: Wed, 10 Dec 2025 12:32:00 -0800 Subject: [PATCH 02/18] added feature so that the user can select a font --- src/themeOpenPage.css | 65 +++++++++++++++++++++++++++++++++++++++++++ src/themeOpenPage.tsx | 60 +++++++++++++++++++++++++++++++++++---- 2 files changed, 119 insertions(+), 6 deletions(-) diff --git a/src/themeOpenPage.css b/src/themeOpenPage.css index 6ed48e0..1b1c09f 100644 --- a/src/themeOpenPage.css +++ b/src/themeOpenPage.css @@ -396,4 +396,69 @@ .lightDarkSplit .darkSide.selectedSide .iconWrapper, .lightDarkSplit .darkSide.selectedSide .modeLabel { transform: scale(1.1); +} + +/* Fonts theme styling */ +.fontsTile { + position: relative; + overflow: hidden; + padding: 20px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.fontsContainer { + width: 100%; + display: flex; + flex-direction: column; + gap: 12px; + align-items: center; + justify-content: center; +} + +.fontsLabel { + font-size: 16px; + font-weight: 600; + color: #1a1a2e; + text-align: center; +} + +.fontDropdown { + width: 100%; + max-width: 180px; + padding: 10px 12px; + border: 2px solid rgba(0, 0, 0, 0.15); + border-radius: 8px; + background: rgba(255, 255, 255, 0.9); + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + text-align: center; +} + +.fontDropdown:hover { + border-color: rgba(0, 0, 0, 0.25); + background: rgba(255, 255, 255, 1); +} + +.fontDropdown:focus { + outline: none; + border-color: #5b7ec6; + box-shadow: 0 0 0 3px rgba(91, 126, 198, 0.2); +} + +.fontSelected { + border: 2px solid #5b7ec6; + opacity: 1; + transform: scale(1); + box-shadow: 0 0 0 4px rgba(91, 126, 198, 0.2), 0 8px 24px rgba(0, 0, 0, 0.12); +} + +.fontSelected:hover { + border: 2px solid #5b7ec6; + box-shadow: 0 0 0 4px rgba(107, 137, 196, 0.3), 0 8px 24px rgba(0, 0, 0, 0.15); + transform: translateY(-4px); } \ No newline at end of file diff --git a/src/themeOpenPage.tsx b/src/themeOpenPage.tsx index f06594c..166c149 100644 --- a/src/themeOpenPage.tsx +++ b/src/themeOpenPage.tsx @@ -7,13 +7,15 @@ type Props = { }; function OpeningTheme({ onContinue }: Props) { - const themes = ["Blur","Color background","Premade Layout","Light/Dark mode"]; + const themes = ["Blur","Color background","Fonts","Light/Dark mode"]; + const fontOptions = ["Arial", "Times New Roman", "Georgia", "Courier New", "Verdana"]; const [index, setIndex] = React.useState(0); const n = themes.length; const left = (index - 1 + n) % n; const right = (index + 1) % n; const [selected, setSelected] = useState>(new Set()); const [lightDarkMode, setLightDarkMode] = useState>(new Map()); + const [selectedFont, setSelectedFont] = useState(null); const [showError, setShowError] = useState(false); const isSelected = (i: number) => selected.has(i); @@ -42,6 +44,20 @@ function OpeningTheme({ onContinue }: Props) { }); }; + const toggleFont = (i: number, font: string) => { + if (selectedFont === font) { + setSelectedFont(null); + setSelected(prev => { + const nextSelected = new Set(prev); + nextSelected.delete(i); + return nextSelected; + }); + } else { + setSelectedFont(font); + setSelected(prev => new Set(prev).add(i)); + } + }; + const handleContinue = () => { if (selected.size === 0) { setShowError(true); @@ -55,9 +71,38 @@ function OpeningTheme({ onContinue }: Props) { const tileClass = (i: number, extra = "") =>[styles.themeOptions,extra, themes[i] === "Color background" ? styles.colorBg : "", themes[i] === "Blur" ? styles.blurTile : "", + themes[i] === "Fonts" ? styles.fontsTile : "", themes[i] === "Light/Dark mode" ? styles.lightDarkSplit : "", - isSelected(i) && themes[i] !== "Light/Dark mode" ? styles.selected : "", - themes[i] === "Light/Dark mode" && lightDarkMode.has(i) ? styles.lightDarkSelected : "",].join(" ").trim(); + isSelected(i) && themes[i] !== "Light/Dark mode" && themes[i] !== "Fonts" ? styles.selected : "", + themes[i] === "Light/Dark mode" && lightDarkMode.has(i) ? styles.lightDarkSelected : "", + themes[i] === "Fonts" && selectedFont ? styles.fontSelected : "",].join(" ").trim(); + + // Render the Fonts theme + const renderFontsTheme = (i: number) => { + return ( +
+
Select Font:
+ +
+ ); + }; // Render the Light/Dark mode split theme const renderLightDarkTheme = (i: number) => { @@ -97,6 +142,9 @@ function OpeningTheme({ onContinue }: Props) { if (themes[i] === "Light/Dark mode") { return renderLightDarkTheme(i); } + if (themes[i] === "Fonts") { + return renderFontsTheme(i); + } return themes[i]; }; @@ -107,9 +155,9 @@ function OpeningTheme({ onContinue }: Props) {
- - - + + +
From f25e845466c4df3b7d26e057adfa21c51c83c343 Mon Sep 17 00:00:00 2001 From: Harrison Lloyd Date: Wed, 10 Dec 2025 12:56:38 -0800 Subject: [PATCH 03/18] Added all the theme options under the theme tab in the settings --- src/Menu.tsx | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/src/Menu.tsx b/src/Menu.tsx index 659d7eb..da41e15 100644 --- a/src/Menu.tsx +++ b/src/Menu.tsx @@ -124,6 +124,124 @@ function WidgetSettings() { ); } +function ThemeSettings() { + const [blurAmount, setBlurAmount] = useState(() => { + return Number(localStorage.getItem("theme_blurAmount")) || 0; + }); + const [backgroundColor, setBackgroundColor] = useState(() => { + return localStorage.getItem("theme_backgroundColor") || "#ffffff"; + }); + const [selectedFont, setSelectedFont] = useState(() => { + return localStorage.getItem("theme_font") || ""; + }); + const [lightMode, setLightMode] = useState(() => { + return localStorage.getItem("theme_lightMode") === "true"; + }); + const [darkMode, setDarkMode] = useState(() => { + return localStorage.getItem("theme_darkMode") === "true"; + }); + + const fontOptions = ["Arial", "Times New Roman", "Georgia", "Courier New", "Verdana"]; + + const handleBlurChange = (value: number) => { + setBlurAmount(value); + localStorage.setItem("theme_blurAmount", value.toString()); + }; + + const handleBackgroundColorChange = (value: string) => { + setBackgroundColor(value); + localStorage.setItem("theme_backgroundColor", value); + }; + + const handleFontChange = (value: string) => { + setSelectedFont(value); + localStorage.setItem("theme_font", value); + }; + + const handleLightModeChange = (value: boolean) => { + if (value) { + setLightMode(true); + setDarkMode(false); + localStorage.setItem("theme_lightMode", "true"); + localStorage.setItem("theme_darkMode", "false"); + } else { + setLightMode(false); + localStorage.setItem("theme_lightMode", "false"); + } + }; + + const handleDarkModeChange = (value: boolean) => { + if (value) { + setDarkMode(true); + setLightMode(false); + localStorage.setItem("theme_darkMode", "true"); + localStorage.setItem("theme_lightMode", "false"); + } else { + setDarkMode(false); + localStorage.setItem("theme_darkMode", "false"); + } + }; + + return ( + <> + Themes + +
+ Blur ({blurAmount}%) +
+ handleBlurChange(Number(e.target.value))} + /> +
+
+ +
+ Background Color +
+ handleBackgroundColorChange(e.target.value)} + /> +
+
+ +
+ Fonts +
+ +
+
+ + + + + ); +} + function TemplateList() { const { templates, @@ -264,8 +382,9 @@ export default function Menu({ active }: { active: boolean }) {
{activeTab === MenuTab.Widget && } {activeTab === MenuTab.Add && active && } + {activeTab === MenuTab.Theme && } {activeTab === MenuTab.Template && } - {![MenuTab.Widget, MenuTab.Add, MenuTab.Template].includes(activeTab) && ( + {![MenuTab.Widget, MenuTab.Add, MenuTab.Theme, MenuTab.Template].includes(activeTab) && ( No Settings Available )}
From 468fea2580e9069dd11294c5944f0896f483f203 Mon Sep 17 00:00:00 2001 From: Harrison Lloyd Date: Wed, 10 Dec 2025 13:24:59 -0800 Subject: [PATCH 04/18] fixed some bugs and made is so that whats chosen on the theme pages applies to whats selected on the theme tab --- src/themeOpenPage.css | 93 +++++++++++++++++++++++++++++++++- src/themeOpenPage.tsx | 115 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 195 insertions(+), 13 deletions(-) diff --git a/src/themeOpenPage.css b/src/themeOpenPage.css index 1b1c09f..b4c75de 100644 --- a/src/themeOpenPage.css +++ b/src/themeOpenPage.css @@ -59,8 +59,8 @@ } .colorBg { - background-image: url(https://images.unsplash.com/photo-1507166763745-bfe008fbb831?q=80&w=960&auto=format); - color: white; + background: rgba(255, 255, 255, 0.9); + color: #333; } .side { @@ -461,4 +461,93 @@ border: 2px solid #5b7ec6; box-shadow: 0 0 0 4px rgba(107, 137, 196, 0.3), 0 8px 24px rgba(0, 0, 0, 0.15); transform: translateY(-4px); +} + +/* Blur theme components */ +.blurTileBg { + position: relative; + overflow: hidden; +} + +.blurContainer { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + gap: 12px; + align-items: center; + justify-content: center; + padding: 20px; +} + +.blurLabel { + font-size: 14px; + font-weight: 600; + color: #1a1a2e; + text-align: center; +} + +.blurSlider { + width: 100%; + max-width: 160px; + cursor: pointer; +} + +.selectButton { + padding: 8px 20px; + border: 2px solid rgba(0, 0, 0, 0.15); + border-radius: 8px; + background: rgba(255, 255, 255, 0.9); + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + color: #1a1a2e; +} + +.selectButton:hover { + background: rgba(255, 255, 255, 1); + border-color: #5b7ec6; + transform: scale(1.05); +} + +/* Color picker theme components */ +.colorPickerContainer { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + gap: 12px; + align-items: center; + justify-content: center; + padding: 20px; +} + +.colorLabel { + font-size: 14px; + font-weight: 600; + color: #1a1a2e; + text-align: center; +} + +.colorPicker { + width: 80px; + height: 80px; + border: 2px solid rgba(0, 0, 0, 0.15); + border-radius: 12px; + cursor: pointer; + transition: all 0.2s ease; +} + +.colorPicker:hover { + border-color: #5b7ec6; + transform: scale(1.05); +} + +.colorPreview { + width: 100%; + max-width: 140px; + height: 20px; + border: 2px solid rgba(0, 0, 0, 0.15); + border-radius: 6px; } \ No newline at end of file diff --git a/src/themeOpenPage.tsx b/src/themeOpenPage.tsx index 166c149..8af1bff 100644 --- a/src/themeOpenPage.tsx +++ b/src/themeOpenPage.tsx @@ -16,14 +16,29 @@ function OpeningTheme({ onContinue }: Props) { const [selected, setSelected] = useState>(new Set()); const [lightDarkMode, setLightDarkMode] = useState>(new Map()); const [selectedFont, setSelectedFont] = useState(null); + const [blurAmount, setBlurAmount] = useState(50); + const [backgroundColor, setBackgroundColor] = useState("#ffffff"); const [showError, setShowError] = useState(false); const isSelected = (i: number) => selected.has(i); - const toggle = (i: number) => setSelected(prev => { - const next = new Set(prev); - next.has(i) ? next.delete(i) : next.add(i); - return next; + const toggle = (i: number) => { + setSelected(prev => { + const next = new Set(prev); + const wasSelected = next.has(i); + next.has(i) ? next.delete(i) : next.add(i); + + // Save blur setting when toggled + if (themes[i] === "Blur") { + if (!wasSelected) { + localStorage.setItem("theme_blurAmount", blurAmount.toString()); + } else { + localStorage.setItem("theme_blurAmount", "0"); + } + } + + return next; }); + }; const toggleLightDark = (i: number, mode: 'light' | 'dark') => { setLightDarkMode(prev => { @@ -36,9 +51,20 @@ function OpeningTheme({ onContinue }: Props) { nextSelected.delete(i); return nextSelected; }); + // Clear both mode settings + localStorage.setItem("theme_lightMode", "false"); + localStorage.setItem("theme_darkMode", "false"); } else { next.set(i, mode); setSelected(prev => new Set(prev).add(i)); + // Save the selected mode + if (mode === 'light') { + localStorage.setItem("theme_lightMode", "true"); + localStorage.setItem("theme_darkMode", "false"); + } else { + localStorage.setItem("theme_darkMode", "true"); + localStorage.setItem("theme_lightMode", "false"); + } } return next; }); @@ -52,9 +78,11 @@ function OpeningTheme({ onContinue }: Props) { nextSelected.delete(i); return nextSelected; }); + localStorage.setItem("theme_font", ""); } else { setSelectedFont(font); setSelected(prev => new Set(prev).add(i)); + localStorage.setItem("theme_font", font); } }; @@ -70,13 +98,72 @@ function OpeningTheme({ onContinue }: Props) { // helper to build the class string for each tile const tileClass = (i: number, extra = "") =>[styles.themeOptions,extra, themes[i] === "Color background" ? styles.colorBg : "", - themes[i] === "Blur" ? styles.blurTile : "", + themes[i] === "Blur" ? styles.blurTileBg : "", themes[i] === "Fonts" ? styles.fontsTile : "", themes[i] === "Light/Dark mode" ? styles.lightDarkSplit : "", - isSelected(i) && themes[i] !== "Light/Dark mode" && themes[i] !== "Fonts" ? styles.selected : "", + isSelected(i) && themes[i] === "Blur" ? styles.selected : "", + isSelected(i) && themes[i] === "Color background" ? styles.selected : "", themes[i] === "Light/Dark mode" && lightDarkMode.has(i) ? styles.lightDarkSelected : "", themes[i] === "Fonts" && selectedFont ? styles.fontSelected : "",].join(" ").trim(); + // Render the Blur theme with slider + const renderBlurTheme = (i: number) => { + return ( +
+
Blur Amount: {blurAmount}%
+ { + e.stopPropagation(); + setBlurAmount(Number(e.target.value)); + }} + onClick={(e) => e.stopPropagation()} + /> + +
+ ); + }; + + // Render the Color Background theme with color picker + const renderColorBackgroundTheme = (i: number) => { + return ( +
+
Choose Color:
+ { + e.stopPropagation(); + setBackgroundColor(e.target.value); + localStorage.setItem("theme_backgroundColor", e.target.value); + if (!isSelected(i)) { + setSelected(prev => new Set(prev).add(i)); + } + }} + onClick={(e) => e.stopPropagation()} + /> +
e.stopPropagation()} + >
+
+ ); + }; + // Render the Fonts theme const renderFontsTheme = (i: number) => { return ( @@ -139,12 +226,18 @@ function OpeningTheme({ onContinue }: Props) { // Helper to render theme content const renderThemeContent = (i: number) => { - if (themes[i] === "Light/Dark mode") { - return renderLightDarkTheme(i); + if (themes[i] === "Blur") { + return renderBlurTheme(i); + } + if (themes[i] === "Color background") { + return renderColorBackgroundTheme(i); } if (themes[i] === "Fonts") { return renderFontsTheme(i); } + if (themes[i] === "Light/Dark mode") { + return renderLightDarkTheme(i); + } return themes[i]; }; @@ -155,9 +248,9 @@ function OpeningTheme({ onContinue }: Props) {
- - - + + +
From ce73397f3e099e2cb02d76b4ce455a03dce3cd01 Mon Sep 17 00:00:00 2001 From: Harrison Lloyd Date: Wed, 10 Dec 2025 13:29:22 -0800 Subject: [PATCH 05/18] fixed bug with light mode/dark mode not being checked --- src/Menu.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Menu.tsx b/src/Menu.tsx index da41e15..bbae7a5 100644 --- a/src/Menu.tsx +++ b/src/Menu.tsx @@ -135,10 +135,20 @@ function ThemeSettings() { return localStorage.getItem("theme_font") || ""; }); const [lightMode, setLightMode] = useState(() => { - return localStorage.getItem("theme_lightMode") === "true"; + const lightModeStored = localStorage.getItem("theme_lightMode"); + const darkModeStored = localStorage.getItem("theme_darkMode"); + + // If neither mode has been set, default to light mode + if (lightModeStored === null && darkModeStored === null) { + localStorage.setItem("theme_lightMode", "true"); + return true; + } + return lightModeStored === "true"; }); const [darkMode, setDarkMode] = useState(() => { - return localStorage.getItem("theme_darkMode") === "true"; + const darkModeStored = localStorage.getItem("theme_darkMode"); + // Only return true if explicitly set to true + return darkModeStored === "true"; }); const fontOptions = ["Arial", "Times New Roman", "Georgia", "Courier New", "Verdana"]; From 647656bd7c6777ff14d342c4a7f05b5d3e34fac7 Mon Sep 17 00:00:00 2001 From: Harrison Lloyd Date: Wed, 10 Dec 2025 13:40:03 -0800 Subject: [PATCH 06/18] Got rid of the background color idea --- src/Menu.tsx | 19 ------------------- src/themeOpenPage.tsx | 38 ++------------------------------------ 2 files changed, 2 insertions(+), 55 deletions(-) diff --git a/src/Menu.tsx b/src/Menu.tsx index bbae7a5..c0460d7 100644 --- a/src/Menu.tsx +++ b/src/Menu.tsx @@ -128,9 +128,6 @@ function ThemeSettings() { const [blurAmount, setBlurAmount] = useState(() => { return Number(localStorage.getItem("theme_blurAmount")) || 0; }); - const [backgroundColor, setBackgroundColor] = useState(() => { - return localStorage.getItem("theme_backgroundColor") || "#ffffff"; - }); const [selectedFont, setSelectedFont] = useState(() => { return localStorage.getItem("theme_font") || ""; }); @@ -158,11 +155,6 @@ function ThemeSettings() { localStorage.setItem("theme_blurAmount", value.toString()); }; - const handleBackgroundColorChange = (value: string) => { - setBackgroundColor(value); - localStorage.setItem("theme_backgroundColor", value); - }; - const handleFontChange = (value: string) => { setSelectedFont(value); localStorage.setItem("theme_font", value); @@ -209,17 +201,6 @@ function ThemeSettings() { -
- Background Color -
- handleBackgroundColorChange(e.target.value)} - /> -
-
-
Fonts
diff --git a/src/themeOpenPage.tsx b/src/themeOpenPage.tsx index 8af1bff..f77b16a 100644 --- a/src/themeOpenPage.tsx +++ b/src/themeOpenPage.tsx @@ -7,7 +7,7 @@ type Props = { }; function OpeningTheme({ onContinue }: Props) { - const themes = ["Blur","Color background","Fonts","Light/Dark mode"]; + const themes = ["Blur","Fonts","Light/Dark mode"]; const fontOptions = ["Arial", "Times New Roman", "Georgia", "Courier New", "Verdana"]; const [index, setIndex] = React.useState(0); const n = themes.length; @@ -17,7 +17,6 @@ function OpeningTheme({ onContinue }: Props) { const [lightDarkMode, setLightDarkMode] = useState>(new Map()); const [selectedFont, setSelectedFont] = useState(null); const [blurAmount, setBlurAmount] = useState(50); - const [backgroundColor, setBackgroundColor] = useState("#ffffff"); const [showError, setShowError] = useState(false); const isSelected = (i: number) => selected.has(i); @@ -97,12 +96,10 @@ function OpeningTheme({ onContinue }: Props) { // helper to build the class string for each tile const tileClass = (i: number, extra = "") =>[styles.themeOptions,extra, - themes[i] === "Color background" ? styles.colorBg : "", themes[i] === "Blur" ? styles.blurTileBg : "", themes[i] === "Fonts" ? styles.fontsTile : "", themes[i] === "Light/Dark mode" ? styles.lightDarkSplit : "", isSelected(i) && themes[i] === "Blur" ? styles.selected : "", - isSelected(i) && themes[i] === "Color background" ? styles.selected : "", themes[i] === "Light/Dark mode" && lightDarkMode.has(i) ? styles.lightDarkSelected : "", themes[i] === "Fonts" && selectedFont ? styles.fontSelected : "",].join(" ").trim(); @@ -136,34 +133,6 @@ function OpeningTheme({ onContinue }: Props) { ); }; - // Render the Color Background theme with color picker - const renderColorBackgroundTheme = (i: number) => { - return ( -
-
Choose Color:
- { - e.stopPropagation(); - setBackgroundColor(e.target.value); - localStorage.setItem("theme_backgroundColor", e.target.value); - if (!isSelected(i)) { - setSelected(prev => new Set(prev).add(i)); - } - }} - onClick={(e) => e.stopPropagation()} - /> -
e.stopPropagation()} - >
-
- ); - }; - // Render the Fonts theme const renderFontsTheme = (i: number) => { return ( @@ -229,9 +198,6 @@ function OpeningTheme({ onContinue }: Props) { if (themes[i] === "Blur") { return renderBlurTheme(i); } - if (themes[i] === "Color background") { - return renderColorBackgroundTheme(i); - } if (themes[i] === "Fonts") { return renderFontsTheme(i); } @@ -244,7 +210,7 @@ function OpeningTheme({ onContinue }: Props) { return (

Welcome

-

Choose a theme

+

Choose your themes!

From 5595fe341efde21ad6083195f14ecbd658ef0714 Mon Sep 17 00:00:00 2001 From: Harrison Lloyd Date: Wed, 10 Dec 2025 13:47:28 -0800 Subject: [PATCH 07/18] now the light mode/dark mode bug is actually gone --- src/Menu.tsx | 59 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/src/Menu.tsx b/src/Menu.tsx index c0460d7..f40c2ac 100644 --- a/src/Menu.tsx +++ b/src/Menu.tsx @@ -161,27 +161,39 @@ function ThemeSettings() { }; const handleLightModeChange = (value: boolean) => { - if (value) { + // Always keep one mode selected - if trying to uncheck, keep it checked + if (!value && lightMode) { + // User is trying to uncheck light mode, switch to dark mode instead + setLightMode(false); + setDarkMode(true); + localStorage.setItem("theme_lightMode", "false"); + localStorage.setItem("theme_darkMode", "true"); + } else if (value && !lightMode) { + // User is checking light mode, uncheck dark mode setLightMode(true); setDarkMode(false); localStorage.setItem("theme_lightMode", "true"); localStorage.setItem("theme_darkMode", "false"); - } else { - setLightMode(false); - localStorage.setItem("theme_lightMode", "false"); } + // If trying to check when already checked, do nothing }; const handleDarkModeChange = (value: boolean) => { - if (value) { + // Always keep one mode selected - if trying to uncheck, keep it checked + if (!value && darkMode) { + // User is trying to uncheck dark mode, switch to light mode instead + setDarkMode(false); + setLightMode(true); + localStorage.setItem("theme_darkMode", "false"); + localStorage.setItem("theme_lightMode", "true"); + } else if (value && !darkMode) { + // User is checking dark mode, uncheck light mode setDarkMode(true); setLightMode(false); localStorage.setItem("theme_darkMode", "true"); localStorage.setItem("theme_lightMode", "false"); - } else { - setDarkMode(false); - localStorage.setItem("theme_darkMode", "false"); } + // If trying to check when already checked, do nothing }; return ( @@ -219,16 +231,27 @@ function ThemeSettings() {
- - +
+ Light Mode +
+ handleLightModeChange(e.target.checked)} + /> +
+
+ +
+ Dark Mode +
+ handleDarkModeChange(e.target.checked)} + /> +
+
); } From fcb11840d78bd7d10508cb246358584a628ed856 Mon Sep 17 00:00:00 2001 From: Harrison Lloyd Date: Wed, 10 Dec 2025 16:32:31 -0800 Subject: [PATCH 08/18] Made it so that the blur effect works on the opening page as well as the theme tab in the menu on the widgets --- src/App.css | 6 +++++- src/App.tsx | 6 ++++++ src/Menu.tsx | 7 +++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/App.css b/src/App.css index 6e70b6f..d15e8bc 100644 --- a/src/App.css +++ b/src/App.css @@ -47,6 +47,10 @@ body { opacity: 0.5; } +:root { + --blur-amount: 40px; +} + .container { width: 100%; height: 100%; @@ -54,7 +58,7 @@ body { border-radius: 8px; border: 1px solid #fff2; - backdrop-filter: blur(40px); + backdrop-filter: blur(var(--blur-amount)); transform: translateZ(0); backface-visibility: hidden; } diff --git a/src/App.tsx b/src/App.tsx index e31d123..4d4ea70 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -153,6 +153,12 @@ const App = () => { useEffect(() => { readTemplates(); + + // Apply blur from localStorage on initial load + const blurAmount = localStorage.getItem("theme_blurAmount"); + if (blurAmount !== null) { + document.documentElement.style.setProperty('--blur-amount', `${blurAmount}px`); + } }, []); if (openingTheme) { diff --git a/src/Menu.tsx b/src/Menu.tsx index f40c2ac..827b9cc 100644 --- a/src/Menu.tsx +++ b/src/Menu.tsx @@ -150,9 +150,16 @@ function ThemeSettings() { const fontOptions = ["Arial", "Times New Roman", "Georgia", "Courier New", "Verdana"]; + // Apply blur on initial load + useEffect(() => { + document.documentElement.style.setProperty('--blur-amount', `${blurAmount}px`); + }, []); + const handleBlurChange = (value: number) => { setBlurAmount(value); localStorage.setItem("theme_blurAmount", value.toString()); + // Apply the blur to widgets using CSS variable + document.documentElement.style.setProperty('--blur-amount', `${value}px`); }; const handleFontChange = (value: string) => { From ed1d1a7f931921172eab2806a3e6b01b054c8d16 Mon Sep 17 00:00:00 2001 From: Harrison Lloyd Date: Wed, 10 Dec 2025 16:56:25 -0800 Subject: [PATCH 09/18] Selecting the font style now changes all other text to that font --- src/App.css | 3 ++- src/App.tsx | 6 ++++++ src/Menu.tsx | 15 +++++++++++++-- src/themeOpenPage.tsx | 2 +- src/widgets/Clock.css | 2 +- src/widgets/Notepad.css | 4 ++-- 6 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/App.css b/src/App.css index d15e8bc..1830c8d 100644 --- a/src/App.css +++ b/src/App.css @@ -11,7 +11,7 @@ body { height: 100svh; margin: 0; color: #fff; - font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-family: var(--app-font); background-image: url(https://images.unsplash.com/photo-1507166763745-bfe008fbb831?q=80&w=960&auto=format); background-size: cover; overflow: hidden; @@ -49,6 +49,7 @@ body { :root { --blur-amount: 40px; + --app-font: Inter, Avenir, Helvetica, Arial, sans-serif; } .container { diff --git a/src/App.tsx b/src/App.tsx index 4d4ea70..4cf0759 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -159,6 +159,12 @@ const App = () => { if (blurAmount !== null) { document.documentElement.style.setProperty('--blur-amount', `${blurAmount}px`); } + + // Apply font from localStorage on initial load + const selectedFont = localStorage.getItem("theme_font"); + if (selectedFont && selectedFont !== "") { + document.documentElement.style.setProperty('--app-font', selectedFont); + } }, []); if (openingTheme) { diff --git a/src/Menu.tsx b/src/Menu.tsx index 827b9cc..f83d429 100644 --- a/src/Menu.tsx +++ b/src/Menu.tsx @@ -150,9 +150,13 @@ function ThemeSettings() { const fontOptions = ["Arial", "Times New Roman", "Georgia", "Courier New", "Verdana"]; - // Apply blur on initial load + // Apply blur and font on initial load useEffect(() => { document.documentElement.style.setProperty('--blur-amount', `${blurAmount}px`); + + if (selectedFont && selectedFont !== "") { + document.documentElement.style.setProperty('--app-font', selectedFont); + } }, []); const handleBlurChange = (value: number) => { @@ -165,6 +169,13 @@ function ThemeSettings() { const handleFontChange = (value: string) => { setSelectedFont(value); localStorage.setItem("theme_font", value); + // Apply the font using CSS variable + if (value && value !== "") { + document.documentElement.style.setProperty('--app-font', value); + } else { + // Reset to default font if "None" is selected + document.documentElement.style.setProperty('--app-font', 'Inter, Avenir, Helvetica, Arial, sans-serif'); + } }; const handleLightModeChange = (value: boolean) => { @@ -228,7 +239,7 @@ function ThemeSettings() { onChange={(e) => handleFontChange(e.target.value)} style={{ fontFamily: selectedFont || "inherit" }} > - + {fontOptions.map((font) => ( + {fontOptions.map((font) => (