diff --git a/src/App.css b/src/App.css index 6e70b6f..6def3b8 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; @@ -36,15 +36,9 @@ body { z-index: -1; } -.content h1 { - font-size: 3.6rem; - font-weight: 700; -} - -.content p { - font-size: 1.5rem; - font-weight: 400; - opacity: 0.5; +:root { + --blur-amount: 40px; + --app-font: Arial, sans-serif; } .container { @@ -54,11 +48,30 @@ body { border-radius: 8px; border: 1px solid #fff2; - backdrop-filter: blur(40px); + backdrop-filter: blur(var(--blur-amount)); transform: translateZ(0); backface-visibility: hidden; } +/* Light mode styling */ +.lightMode .container { + background-color: rgba(255, 255, 255, 0.95); + border: 1px solid rgba(0, 0, 0, 0.1); +} + +.lightMode { + color: #000; +} + +.lightMode .button { + color: rgba(0, 0, 0, 0.8); +} + +.lightMode .button:hover { + border-color: rgba(0, 0, 0, 0.3); + color: #000; +} + .container::before { content: ""; position: absolute; diff --git a/src/App.tsx b/src/App.tsx index 5a16c36..b89168f 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; @@ -25,6 +26,7 @@ interface AppContextType { deleting: boolean; hidden: boolean; menuOpen: boolean; + theme: 'light' | 'dark'; setWidgets: React.Dispatch[]>>; setTemplates: React.Dispatch>; @@ -32,6 +34,7 @@ interface AppContextType { setDeleting: React.Dispatch>; setHidden: React.Dispatch>; setMenuOpen: React.Dispatch>; + setTheme: React.Dispatch>; saveTemplate: (name?: string) => void; loadTemplate: (index?: number) => void; @@ -67,34 +70,61 @@ const App = () => { let [widgets, setWidgets] = useState[]>([]); let [templates, setTemplates] = useState([]); let [activeTemplate, setActiveTemplate] = useState(0); + const [openingTheme, setOpeningTheme] = useState(() => { + const completed = localStorage.getItem("theme_setup_completed"); + return completed !== "true"; + }); + const [theme, setTheme] = useState<'light' | 'dark'>('dark'); const gridRef = useRef(null); async function saveTemplate(name?: string) { - const template = { - name: name ?? templates[activeTemplate]?.name ?? "Default", - image: await toPng(gridRef.current, { - canvasWidth: 240, - canvasHeight: 135, - }), - widgets: structuredClone(widgets), - pinned: false, - }; + try { + const templateWithoutImage = { + name: name ?? templates[activeTemplate]?.name ?? "Default", + image: null, + widgets: structuredClone(widgets), + pinned: false, + }; - if (name === null || name === undefined) { - const _templates = [...templates]; - _templates[activeTemplate] = template; + let _templates: Template[]; + let _activeTemplate: number; - templates = [..._templates]; - setTemplates(templates); - } else { - templates = [...templates, template]; - activeTemplate = templates.length; + if (name === null || name === undefined) { + _templates = [...templates]; + _templates[activeTemplate] = templateWithoutImage; + _activeTemplate = activeTemplate; + } else { + _templates = [...templates, templateWithoutImage]; + _activeTemplate = templates.length; + } + + localStorage.setItem("templates", JSON.stringify(_templates)); + localStorage.setItem("activeTemplate", JSON.stringify(_activeTemplate)); + + templates = _templates; + activeTemplate = _activeTemplate; setTemplates(templates); - setActiveTemplate(activeTemplate); - } + if (name !== null && name !== undefined) { + setActiveTemplate(activeTemplate); + } + + try { + const image = await toPng(gridRef.current, { + canvasWidth: 240, + canvasHeight: 135, + }); - writeTemplates(); + _templates[_activeTemplate].image = image; + templates = _templates; + localStorage.setItem("templates", JSON.stringify(templates)); + setTemplates([...templates]); + } catch (screenshotError) { + console.warn("Screenshot failed, but template was saved:", screenshotError); + } + } catch (error) { + console.error("Error in saveTemplate:", error); + } } function loadTemplate(index?: number) { @@ -161,11 +191,47 @@ const App = () => { useEffect(() => { readTemplates(); + + const blurAmount = localStorage.getItem("theme_blurAmount"); + if (blurAmount !== null) { + document.documentElement.style.setProperty('--blur-amount', `${blurAmount}px`); + } + + const selectedFont = localStorage.getItem("theme_font"); + if (selectedFont && selectedFont !== "") { + document.documentElement.style.setProperty('--app-font', selectedFont); + } + + const lightMode = localStorage.getItem("theme_lightMode"); + if (lightMode === "true") { + setTheme('light'); + } else { + setTheme('dark'); + } }, []); + if (openingTheme) { + return { + setOpeningTheme(false); + localStorage.setItem("theme_setup_completed", "true"); + + const blurAmount = localStorage.getItem("theme_blurAmount"); + if (blurAmount !== null) { + document.documentElement.style.setProperty('--blur-amount', `${blurAmount}px`); + } + + const lightMode = localStorage.getItem("theme_lightMode"); + if (lightMode === "true") { + setTheme('light'); + } else { + setTheme('dark'); + } + }} />; + } + return (
{ if (e.target === e.currentTarget) { setMenuOpen(false); @@ -182,6 +248,7 @@ const App = () => { deleting, hidden, menuOpen, + theme, setWidgets, setTemplates, @@ -189,6 +256,7 @@ const App = () => { setDeleting, setHidden, setMenuOpen, + setTheme, saveTemplate, loadTemplate, @@ -201,6 +269,7 @@ const App = () => { {widgets.map((state) => { const map = WidgetMap[state.type]; + if (!map) return null; const Component = map.component; return ( { + const saved = localStorage.getItem("theme_blurAmount"); + return saved !== null ? Number(saved) : 40; + }); + const [selectedFont, setSelectedFont] = useState(() => { + return localStorage.getItem("theme_font") || ""; + }); + const [lightMode, setLightMode] = useState(() => { + const lightModeStored = localStorage.getItem("theme_lightMode"); + // Only return true if explicitly set to true + return lightModeStored === "true"; + }); + const [darkMode, setDarkMode] = useState(() => { + const lightModeStored = localStorage.getItem("theme_lightMode"); + const darkModeStored = localStorage.getItem("theme_darkMode"); + + // If neither mode has been set, default to dark mode + if (lightModeStored !== "true" && darkModeStored !== "true") { + localStorage.setItem("theme_darkMode", "true"); + localStorage.setItem("theme_lightMode", "false"); + return true; + } + return darkModeStored === "true"; + }); + + const fontOptions = ["Times New Roman", "Georgia", "Courier New", "Verdana"]; + + useEffect(() => { + const savedBlur = localStorage.getItem("theme_blurAmount"); + if (savedBlur !== null) { + document.documentElement.style.setProperty('--blur-amount', `${blurAmount}px`); + } + + if (selectedFont && selectedFont !== "") { + document.documentElement.style.setProperty('--app-font', selectedFont); + } + }, []); + + 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) => { + 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 "Default" is selected + document.documentElement.style.setProperty('--app-font', 'Arial, sans-serif'); + } + }; + + const handleLightModeChange = (value: boolean) => { + // 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"); + setTheme('dark'); + } 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"); + setTheme('light'); + } + // If trying to check when already checked, do nothing + }; + + const handleDarkModeChange = (value: boolean) => { + // 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"); + setTheme('light'); + } 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"); + setTheme('dark'); + } + // If trying to check when already checked, do nothing + }; + + return ( + <> + Themes + +
+ Blur ({blurAmount}%) +
+ handleBlurChange(Number(e.target.value))} + /> +
+
+ +
+ Fonts +
+ +
+
+ +
+ Light Mode +
+ handleLightModeChange(e.target.checked)} + /> +
+
+ +
+ Dark Mode +
+ handleDarkModeChange(e.target.checked)} + /> +
+
+ + ); +} + function TemplateList() { const { templates, @@ -264,10 +424,11 @@ export default function Menu({ active }: { active: boolean }) {
{activeTab === MenuTab.Widget && } {activeTab === MenuTab.Add && active && } + {activeTab === MenuTab.Theme && } {activeTab === MenuTab.Templates && } - {![MenuTab.Widget, MenuTab.Add, MenuTab.Templates].includes( - activeTab, - ) && No Settings Available} + {![MenuTab.Widget, MenuTab.Add, MenuTab.Theme, MenuTab.Templates].includes(activeTab) && ( + No Settings Available + )} ); } diff --git a/src/themeOpenPage.css b/src/themeOpenPage.css new file mode 100644 index 0000000..83cd23e --- /dev/null +++ b/src/themeOpenPage.css @@ -0,0 +1,583 @@ +.blurTile { + position: relative; + overflow: hidden; + color: #111; +} + +.blurTile::before { + content: ""; + position: absolute; + inset: 0; + background: + radial-gradient(120px 80px at 30% 30%, #dfe6f1, transparent 60%), + radial-gradient(120px 80px at 70% 60%, #e6dff1, transparent 60%), + linear-gradient(135deg, #fafafa, #eaeaea 60%, #f2f2f2); + filter: blur(75px); /* the blur effect */ + transform: scale(1.1); /* avoid sharp edges after blur */ + z-index: 0; +} + +.blurTile > * { 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: rgba(255, 255, 255, 0.9); + color: #333; +} + +.side { + opacity: 0.5; + transform: scale(0.95); +} + +.navButton { /* These are the Xs that allow you to move left and right on the themes*/ + display: flex; + place-content: center; + 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: #fffe; + 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: #fffc; + font-size: 28px; + 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); +} + +/* Fonts theme styling */ +.fontsTile { + position: relative; + overflow: hidden; + padding: 20px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background: rgba(255, 255, 255, 0.85); +} + +.fontsContainer { + width: 100%; + display: flex; + flex-direction: column; + gap: 14px; + align-items: center; + justify-content: center; +} + +.fontsLabel { + font-size: 15px; + font-weight: 600; + color: #4a4a68; + text-align: center; +} + +.fontDropdown { + width: 100%; + max-width: 180px; + padding: 10px 14px; + border: 2px solid #e0e0e0; + border-radius: 8px; + background: #ffffff; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + text-align: center; + color: #4a4a68; +} + +.fontDropdown:hover { + border-color: #5b7ec6; +} + +.fontDropdown:focus { + outline: none; + border-color: #5b7ec6; + box-shadow: 0 0 0 3px rgba(91, 126, 198, 0.15); +} + +.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); +} + +/* Blur theme components */ +.blurTileBg { + position: relative; + overflow: hidden; + background: rgba(255, 255, 255, 0.85); +} + +.blurContainer { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + gap: 14px; + align-items: center; + justify-content: center; + padding: 20px; +} + +.blurLabel { + font-size: 15px; + font-weight: 600; + color: #4a4a68; + text-align: center; + margin-top: -10px; +} + +.blurSlider { + width: 100%; + max-width: 170px; + height: 5px; + cursor: pointer; + -webkit-appearance: none; + appearance: none; + background: #e0e0e0; + border-radius: 10px; + outline: none; + margin-top: 20px; +} + +.blurSlider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 18px; + height: 18px; + border-radius: 50%; + background: #5b7ec6; + cursor: pointer; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); + transition: all 0.2s ease; +} + +.blurSlider::-webkit-slider-thumb:hover { + transform: scale(1.1); +} + +.blurSlider::-moz-range-thumb { + width: 18px; + height: 18px; + border-radius: 50%; + background: #5b7ec6; + cursor: pointer; + border: none; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); + transition: all 0.2s ease; +} + +.blurSlider::-moz-range-thumb:hover { + transform: scale(1.1); +} + +.selectButton { + padding: 9px 24px; + border: 2px solid #5b7ec6; + border-radius: 8px; + background: #ffffff; + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + color: #5b7ec6; +} + +.selectButton:hover { + background: #5b7ec6; + color: #ffffff; +} + +/* 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 new file mode 100644 index 0000000..02733ba --- /dev/null +++ b/src/themeOpenPage.tsx @@ -0,0 +1,257 @@ +import React, { useState } from "react"; +import { CaretLeftIcon, CaretRightIcon, Sun, Moon } from "@phosphor-icons/react"; +import globalStyles from "./App.css" +import styles from "./themeOpenPage.css"; + +type Props = { + onContinue:()=>void +}; + +function OpeningTheme({ onContinue }: Props) { + const themes = ["Blur","Fonts","Light/Dark mode"]; + const fontOptions = ["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(""); + const [blurAmount, setBlurAmount] = useState(50); + const [showError, setShowError] = useState(false); + const isSelected = (i: number) => selected.has(i); + + 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 => { + 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; + }); + // 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; + }); + }; + + const toggleFont = (i: number, font: string) => { + if (selectedFont === font) { + setSelectedFont(""); + setSelected(prev => { + const nextSelected = new Set(prev); + nextSelected.delete(i); + return nextSelected; + }); + localStorage.setItem("theme_font", ""); + document.documentElement.style.setProperty('--app-font', 'Arial, sans-serif'); + } else { + setSelectedFont(font); + setSelected(prev => new Set(prev).add(i)); + const fontToSave = font === "Arial" ? "" : font; + localStorage.setItem("theme_font", fontToSave); + if (fontToSave) { + document.documentElement.style.setProperty('--app-font', font); + } else { + document.documentElement.style.setProperty('--app-font', 'Arial, sans-serif'); + } + } + }; + + const handleContinue = () => { + if (selected.size === 0) { + setShowError(true); + setTimeout(() => setShowError(false), 3000); // Hide after 3 seconds + } else { + const blurIndex = themes.indexOf("Blur"); + if (selected.has(blurIndex)) { + localStorage.setItem("theme_blurAmount", blurAmount.toString()); + } + onContinue(); + } + }; + + // helper to build the class string for each tile + const tileClass = (i: number, extra = "") =>[styles.themeOptions,extra, + themes[i] === "Blur" ? styles.blurTileBg : "", + themes[i] === "Fonts" ? styles.fontsTile : "", + themes[i] === "Light/Dark mode" ? styles.lightDarkSplit : "", + isSelected(i) && themes[i] === "Blur" ? 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(); + const newValue = Number(e.target.value); + setBlurAmount(newValue); + + if (newValue !== 50) { + setSelected(prev => new Set(prev).add(i)); + } else { + setSelected(prev => { + const next = new Set(prev); + next.delete(i); + return next; + }); + } + }} + onClick={(e) => e.stopPropagation()} + /> +
+ ); + }; + + // Render the Fonts theme + const renderFontsTheme = (i: number) => { + return ( +
+
Select Font:
+ +
+ ); + }; + + // 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] === "Blur") { + return renderBlurTheme(i); + } + if (themes[i] === "Fonts") { + return renderFontsTheme(i); + } + if (themes[i] === "Light/Dark mode") { + return renderLightDarkTheme(i); + } + return themes[i]; + }; + + return ( +
+

Welcome

+

Select your home theme

+ +
+ + + + + +
+ +
+ {themes.map((_, i) => ( +
+ ))} +
+ + {showError && ( +
+ Please select at least one theme +
+ )} + +
+ + +
+
+ ); +} +export default OpeningTheme; \ No newline at end of file diff --git a/src/widgets/BatteryWidget.css b/src/widgets/BatteryWidget.css index b4f58ad..907dae0 100644 --- a/src/widgets/BatteryWidget.css +++ b/src/widgets/BatteryWidget.css @@ -10,13 +10,20 @@ justify-content: center; gap: 4px; height: 100%; - color: #fffc; + color: rgba(255, 255, 255, 0.849); font-variant-numeric: tabular-nums; } +/* Light mode - white text and battery outline */ +:global(.lightMode) .row { + color: #fff; + --stroke: rgba(255, 255, 255, 0.8); + --level: rgba(255, 255, 255, 0.872); +} + @media (prefers-color-scheme: light) { .row { - --stroke: rgba(232, 225, 225, 0.35); + --stroke: rgba(246, 239, 239, 0.35); color: #a9a9b8; } } diff --git a/src/widgets/Calendar.css b/src/widgets/Calendar.css index 49e37a3..e223ec1 100644 --- a/src/widgets/Calendar.css +++ b/src/widgets/Calendar.css @@ -8,6 +8,14 @@ gap: 4px; } +/* Light mode - white background with blur */ +:global(.lightMode) .body { + background-color: rgba(255, 255, 255, 0.55) !important; + border: 1px solid rgba(0, 0, 0, 0.15) !important; + -webkit-backdrop-filter: blur(var(--blur-amount)) !important; + backdrop-filter: blur(var(--blur-amount)) !important; +} + .header { width: 100%; display: flex; @@ -25,6 +33,10 @@ gap: 8px; } +:global(.lightMode) .selector { + color: #000; +} + .selector-button { display: flex; align-items: center; @@ -44,6 +56,15 @@ border: 1px solid #fff2; } +:global(.lightMode) .selector-button { + color: #000; +} + +:global(.lightMode) .selector-button:hover { + background-color: rgba(0, 0, 0, 0.1); + border: 1px solid rgba(0, 0, 0, 0.2); +} + .day-names { display: grid; grid-template-columns: repeat(7, 1fr); @@ -52,6 +73,10 @@ color: #fff8; } +:global(.lightMode) .day-names { + color: #666; +} + .month { width: 100%; height: 100%; @@ -81,3 +106,21 @@ color: #fff8; padding: 2px 4px; } + +:global(.lightMode) .day { + background-color: rgba(0, 0, 0, 0.05); + border: 1px solid rgba(0, 0, 0, 0.1); +} + +:global(.lightMode) .day.active { + background-color: rgba(0, 0, 0, 0.15); +} + +:global(.lightMode) .day.today { + border: 1px solid rgba(0, 0, 0, 0.3); + background-color: rgba(0, 0, 0, 0.1); +} + +:global(.lightMode) .day span { + color: #333; +} diff --git a/src/widgets/Calendar.tsx b/src/widgets/Calendar.tsx index fc54fc6..620579e 100644 --- a/src/widgets/Calendar.tsx +++ b/src/widgets/Calendar.tsx @@ -7,9 +7,17 @@ import { CaretLeftIcon, CaretRightIcon } from "@phosphor-icons/react"; export interface CalendarSettings {} export function Calendar({ settings }: WidgetState) { - const [date, setDate] = useState(new Date()); + const [date, setDate] = useState(() => { + const saved = localStorage.getItem("calendar-date"); + return saved ? new Date(JSON.parse(saved)) : new Date(); + }); const offset = new Date(date.getFullYear(), date.getMonth(), 1).getDay(); + // Save selected date to localStorage + useEffect(() => { + localStorage.setItem("calendar-date", JSON.stringify(date.toISOString())); + }, [date]); + const days = []; for (let i = -offset; i < 42 - offset; i++) { days.push(new Date(date.getFullYear(), date.getMonth(), i + 1)); diff --git a/src/widgets/Clock.css b/src/widgets/Clock.css index a632bea..8c447d3 100644 --- a/src/widgets/Clock.css +++ b/src/widgets/Clock.css @@ -7,7 +7,7 @@ align-items: center; justify-content: center; - font-family: Inter, sans-serif; + font-family: var(--app-font); text-align: center; container-name: clock; diff --git a/src/widgets/Notepad.css b/src/widgets/Notepad.css index 4cf44eb..f300b3b 100644 --- a/src/widgets/Notepad.css +++ b/src/widgets/Notepad.css @@ -6,6 +6,14 @@ padding: 8px; } +/* Light mode - white background with blur */ +:global(.lightMode) .body { + background-color: rgba(255, 255, 255, 0.55) !important; + border: 1px solid rgba(0, 0, 0, 0.15) !important; + -webkit-backdrop-filter: blur(var(--blur-amount)) !important; + backdrop-filter: blur(var(--blur-amount)) !important; +} + .title { display: flex; justify-content: center; @@ -18,6 +26,10 @@ text-align: center; } +:global(.lightMode) .title { + color: #000; +} + /* The note area */ .textarea { flex: 1; @@ -25,7 +37,7 @@ resize: none; padding: 10px; font-size: 0.95rem; - font-family: "Inter", sans-serif; + font-family: var(--app-font); color: #fffc; background-color: #fff0; outline: none; @@ -43,6 +55,20 @@ background-color: #0002; } +:global(.lightMode) .textarea { + color: #000; + border: 1px solid transparent; +} + +:global(.lightMode) .textarea::placeholder { + color: #999; +} + +:global(.lightMode) .textarea:focus { + border-color: rgba(0, 0, 0, 0.2); + background-color: rgba(0, 0, 0, 0.05); +} + /* Tabs container */ .tabs { display: flex; @@ -51,6 +77,10 @@ flex-wrap: wrap; } +:global(.lightMode) .tabs { + border-bottom: 1px solid rgba(0, 0, 0, 0.15); +} + /* Individual tabs */ .tab { flex: 0 1 auto; @@ -82,6 +112,18 @@ flex: 1 0 auto; } +:global(.lightMode) .tab { + color: #333; +} + +:global(.lightMode) .tab:hover { + background-color: rgba(0, 0, 0, 0.05); +} + +:global(.lightMode) .activeTab { + background-color: rgba(0, 0, 0, 0.1); +} + .tab span { white-space: nowrap; overflow: hidden; @@ -103,6 +145,14 @@ color: #fff; } +:global(.lightMode) .closeBtn { + color: #666; +} + +:global(.lightMode) .closeBtn:hover { + color: #000; +} + /* Add button */ .addBtn { flex-shrink: 0; @@ -122,6 +172,15 @@ background-color: #fff2; } +:global(.lightMode) .addBtn { + background-color: rgba(0, 0, 0, 0.1); + color: #000; +} + +:global(.lightMode) .addBtn:hover { + background-color: rgba(0, 0, 0, 0.15); +} + /* Inline rename input */ .renameInput { background: transparent; @@ -129,7 +188,7 @@ outline: none; color: #fff; font-weight: 600; - font-family: "Inter", sans-serif; + font-family: var(--app-font); font-size: 0.95rem; width: 100px; font-size: 0.8rem; @@ -142,6 +201,15 @@ background-color: #0006; } +:global(.lightMode) .renameInput { + color: #000; + background-color: rgba(0, 0, 0, 0.1); +} + +:global(.lightMode) .renameInput:focus { + background-color: rgba(0, 0, 0, 0.15); +} + .pinBtn { border: none; background: none; diff --git a/src/widgets/Notepad.tsx b/src/widgets/Notepad.tsx index 09afd47..fa4a14b 100644 --- a/src/widgets/Notepad.tsx +++ b/src/widgets/Notepad.tsx @@ -11,24 +11,24 @@ interface NoteState { } export function Notepad() { - const [notes, setNotes] = useState([ - { id: "1", title: "Note 1", content: "", pinned: false }, - ]); - const [activeNoteId, setActiveNoteId] = useState("1"); - const [editingId, setEditingId] = useState(null); - const [tempTitle, setTempTitle] = useState(""); - - // Load from localStorage - useEffect(() => { + const [notes, setNotes] = useState(() => { const saved = localStorage.getItem("multi-notes"); if (saved) { const parsed = JSON.parse(saved); - setNotes(parsed); - if (parsed.length > 0) setActiveNoteId(parsed[0].id); - } else { - createNewNote(true); + return parsed; } - }, []); + return [{ id: "1", title: "Note 1", content: "", pinned: false }]; + }); + const [activeNoteId, setActiveNoteId] = useState(() => { + const saved = localStorage.getItem("multi-notes"); + if (saved) { + const parsed = JSON.parse(saved); + return parsed.length > 0 ? parsed[0].id : "1"; + } + return "1"; + }); + const [editingId, setEditingId] = useState(null); + const [tempTitle, setTempTitle] = useState(""); // Save to localStorage useEffect(() => { diff --git a/src/widgets/Search.css b/src/widgets/Search.css index 99520a6..4e4fad6 100644 --- a/src/widgets/Search.css +++ b/src/widgets/Search.css @@ -21,8 +21,21 @@ gap: 8px; } +/* Light mode - white background with blur on the bar only */ +:global(.lightMode) .bar { + background-color: rgba(255, 255, 255, 0.55) !important; + border: 1px solid rgba(0, 0, 0, 0.15) !important; + -webkit-backdrop-filter: blur(var(--blur-amount)) !important; + backdrop-filter: blur(var(--blur-amount)) !important; +} + .bar svg { flex-shrink: 0; + color: #fff; +} + +:global(.lightMode) .bar svg { + color: #000; } .search-input { @@ -43,6 +56,15 @@ user-select: none; } +:global(.lightMode) .search-input { + color: #000; + font-weight: 500; +} + +:global(.lightMode) .search-input::placeholder { + color: #666; +} + .search-button { display: flex; align-items: center; @@ -64,3 +86,14 @@ background-color: #fff4; transition: 0.15s; } + +:global(.lightMode) .search-button { + background-color: rgba(0, 0, 0, 0.2); + color: #000; +} + +:global(.lightMode) .search-button:hover { + background-color: rgba(0, 0, 0, 0.3); + border-color: rgba(0, 0, 0, 0.3); + color: #000; +} diff --git a/src/widgets/Search.tsx b/src/widgets/Search.tsx index b1b4d6e..8215e68 100644 --- a/src/widgets/Search.tsx +++ b/src/widgets/Search.tsx @@ -20,7 +20,6 @@ export function Search({ settings }: WidgetState) { {settings.showIcon && ( )} @@ -37,7 +36,6 @@ export function Search({ settings }: WidgetState) { > diff --git a/src/widgets/Shortcut.css b/src/widgets/Shortcut.css index 2ef0677..a0ffcf0 100644 --- a/src/widgets/Shortcut.css +++ b/src/widgets/Shortcut.css @@ -12,12 +12,25 @@ background-color 0.15s; } +/* Light mode - white background with blur */ +:global(.lightMode) .shortcut { + background-color: rgba(255, 255, 255, 0.55) !important; + border: 1px solid rgba(0, 0, 0, 0.15) !important; + -webkit-backdrop-filter: blur(var(--blur-amount)) !important; + backdrop-filter: blur(var(--blur-amount)) !important; +} + .shortcut:hover { border-color: #fff4; background-color: #fff2; transition: 0.15s; } +:global(.lightMode) .shortcut:hover { + border-color: rgba(0, 0, 0, 0.25); + background-color: rgba(255, 255, 255, 0.75); +} + .icon { width: 50%; height: 50%; @@ -25,3 +38,8 @@ filter: drop-shadow(0 0 4px #fff2); /*filter: saturate(0) sepia(0.5) hue-rotate(160deg);*/ } + +:global(.lightMode) .icon { + fill: #000; + filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.2)); +} diff --git a/src/widgets/ToDoList.css b/src/widgets/ToDoList.css index 1eeca3b..f8a3d3f 100644 --- a/src/widgets/ToDoList.css +++ b/src/widgets/ToDoList.css @@ -11,6 +11,14 @@ overflow: hidden; } +/* Light mode - light grey with blur */ +:global(.lightMode) .body { + background-color: rgba(255, 255, 255, 0.55) !important; + border: 1px solid rgba(0, 0, 0, 0.15) !important; + -webkit-backdrop-filter: blur(var(--blur-amount)) !important; + backdrop-filter: blur(var(--blur-amount)) !important; +} + /* Title at the top */ .title { font-size: 1rem; @@ -20,6 +28,10 @@ text-align: center; } +:global(.lightMode) .title { + color: #000; +} + /* Task list */ .todo-list { list-style: none; /* remove default bullets */ @@ -45,6 +57,14 @@ background-color: #fff2; /* subtle hover effect */ } +:global(.lightMode) .todo-list li { + border-bottom: 1px solid rgba(0, 0, 0, 0.1); +} + +:global(.lightMode) .todo-list li:hover { + background-color: rgba(0, 0, 0, 0.05); +} + /* Bullet point before task */ .todo-list li span::before { content: "•"; @@ -57,12 +77,20 @@ vertical-align: middle; } +:global(.lightMode) .todo-list li span::before { + color: #666; +} + /* Task text */ .todo-list li span { - color: #fffc; /* black text */ + color: #fffc; flex: 1; /* fill available width */ } +:global(.lightMode) .todo-list li span { + color: #000; +} + /* Completed task style */ .completed span { text-decoration: line-through; @@ -72,8 +100,8 @@ /* Checkmark delete button */ .delete-task { background: transparent; /* empty square */ - border: 2px solid #fff4; /* green border */ - color: #fff8; /* green checkmark */ + border: 2px solid #fff4; + color: #fff8; border-radius: 4px; width: 22px; height: 22px; @@ -91,7 +119,16 @@ } .delete-task:hover { - background: #fff4; /* light green background on hover */ + background: #fff4; +} + +:global(.lightMode) .delete-task { + border: 2px solid rgba(0, 0, 0, 0.2); + color: #666; +} + +:global(.lightMode) .delete-task:hover { + background: rgba(0, 0, 0, 0.1); } /* Input container */ @@ -123,6 +160,20 @@ color: #fff6; } +:global(.lightMode) .todo-input-container input { + color: #000; + border: 1px solid rgba(0, 0, 0, 0.2); +} + +:global(.lightMode) .todo-input-container input:focus { + border-color: rgba(0, 0, 0, 0.3); + background-color: rgba(0, 0, 0, 0.05); +} + +:global(.lightMode) .todo-input-container input::placeholder { + color: #999; +} + .todo-input-container button { padding: 6px 12px; border: 1px solid #fff2; @@ -138,3 +189,13 @@ background-color: #fff4; transition: background-color 0.2s; } + +:global(.lightMode) .todo-input-container button { + border: 1px solid rgba(0, 0, 0, 0.2); + background-color: rgba(0, 0, 0, 0.1); + color: #000; +} + +:global(.lightMode) .todo-input-container button:hover { + background-color: rgba(0, 0, 0, 0.2); +} diff --git a/src/widgets/ToDoList.tsx b/src/widgets/ToDoList.tsx index 7e0940b..8cadf12 100644 --- a/src/widgets/ToDoList.tsx +++ b/src/widgets/ToDoList.tsx @@ -10,14 +10,11 @@ interface Task { } export function ToDoList() { - const [tasks, setTasks] = useState([]); - const [input, setInput] = useState(""); - - // Load tasks from localStorage - useEffect(() => { + const [tasks, setTasks] = useState(() => { const saved = localStorage.getItem("todo-tasks"); - if (saved) setTasks(JSON.parse(saved)); - }, []); + return saved ? JSON.parse(saved) : []; + }); + const [input, setInput] = useState(""); // Save tasks to localStorage whenever tasks change useEffect(() => { diff --git a/src/widgets/Weather.css b/src/widgets/Weather.css index 2edee54..852bf62 100644 --- a/src/widgets/Weather.css +++ b/src/widgets/Weather.css @@ -28,6 +28,21 @@ padding: 6px; } +/* Light mode - white background with blur on individual days */ +:global(.lightMode) .day { + background-color: rgba(255, 255, 255, 0.55) !important; + border: 1px solid rgba(0, 0, 0, 0.1) !important; + -webkit-backdrop-filter: blur(var(--blur-amount)) !important; + backdrop-filter: blur(var(--blur-amount)) !important; + border-radius: 8px; + color: #000; +} + +/* Light mode - dark weather icons */ +:global(.lightMode) .day svg { + color: #333; +} + .temp { display: flex; flex-flow: row wrap; @@ -44,3 +59,13 @@ .temp-min { opacity: 0.75; } + +/* Light mode text styling */ +:global(.lightMode) .temp-min, +:global(.lightMode) .temp-max { + color: #000; +} + +:global(.lightMode) .temp-min { + opacity: 0.6; +} diff --git a/src/widgets/Weather.tsx b/src/widgets/Weather.tsx index 2c1764f..110eba3 100644 --- a/src/widgets/Weather.tsx +++ b/src/widgets/Weather.tsx @@ -84,7 +84,6 @@ export function Weather() { {getIcon(t.cloudCover, { size: 16, weight: "fill", - color: "#fff8", })}