diff --git a/client/src/components/layout/NavHeader.tsx b/client/src/components/layout/NavHeader.tsx index b782a80..3cd1efd 100644 --- a/client/src/components/layout/NavHeader.tsx +++ b/client/src/components/layout/NavHeader.tsx @@ -9,6 +9,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { SeasonSelector } from "@/components/ui/season-selector"; export function NavHeader() { const { theme, setTheme } = useTheme(); @@ -76,6 +77,8 @@ export function NavHeader() { + +
diff --git a/client/src/components/ui/season-selector.tsx b/client/src/components/ui/season-selector.tsx new file mode 100644 index 0000000..2572ece --- /dev/null +++ b/client/src/components/ui/season-selector.tsx @@ -0,0 +1,65 @@ +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { useSeason } from "@/hooks/use-season"; +import { Leaf, Sun, Umbrella, Snowflake } from "lucide-react"; + +interface SeasonSelectorProps { + className?: string; +} + +export function SeasonSelector({ className }: SeasonSelectorProps) { + const { season, setSeason } = useSeason(); + + const getIcon = () => { + switch (season) { + case "spring": + return ; + case "summer": + return ; + case "autumn": + return ; + case "winter": + return ; + default: + return ; + } + }; + + return ( + + + + + + setSeason("spring")}> + + Spring + + setSeason("summer")}> + + Summer + + setSeason("autumn")}> + + Autumn + + setSeason("winter")}> + + Winter + + + + ); +} diff --git a/client/src/hooks/use-season.tsx b/client/src/hooks/use-season.tsx new file mode 100644 index 0000000..5fb80b2 --- /dev/null +++ b/client/src/hooks/use-season.tsx @@ -0,0 +1,44 @@ +import { createContext, useContext, useEffect, useState } from "react"; + +type Season = "spring" | "summer" | "autumn" | "winter"; + +interface SeasonContextProps { + season: Season; + setSeason: (season: Season) => void; +} + +const defaultSeason: Season = "spring"; + +const SeasonContext = createContext({ + season: defaultSeason, + setSeason: () => {}, +}); + +export function SeasonProvider({ children }: { children: React.ReactNode }) { + const [season, setSeason] = useState(() => { + // Try to get the saved season from localStorage + if (typeof window !== 'undefined') { + const savedSeason = localStorage.getItem("season") as Season; + return savedSeason || defaultSeason; + } + return defaultSeason; + }); + + useEffect(() => { + // Apply the season class to the document element + const html = document.documentElement; + html.classList.remove("spring", "summer", "autumn", "winter"); + html.classList.add(season); + + // Save the current season to localStorage + localStorage.setItem("season", season); + }, [season]); + + return ( + + {children} + + ); +} + +export const useSeason = () => useContext(SeasonContext); diff --git a/client/src/index.css b/client/src/index.css index f13b04d..abca8dd 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -4,84 +4,328 @@ @tailwind components; @tailwind utilities; +/** + * Base Theme Definition + * These variables serve as shared values across all seasonal themes + */ :root { - --background: 180 7% 97%; - --foreground: 0 0% 20%; - - --muted: 180 7% 94%; - --muted-foreground: 0 0% 45%; - - --popover: 180 7% 97%; - --popover-foreground: 0 0% 20%; - - --card: 180 7% 97%; - --card-foreground: 0 0% 20%; - - --border: 174 10% 80%; - --input: 174 10% 80%; - - --primary: 174 40% 24%; - --primary-foreground: 0 0% 100%; - - --secondary: 180 7% 94%; - --secondary-foreground: 0 0% 20%; - - --accent: 174 30% 40%; - --accent-foreground: 0 0% 100%; + --radius: 0.5rem; + /* Default destructive/error colors that aren't seasonal */ --destructive: 0 84.2% 60.2%; --destructive-foreground: 0 0% 100%; - --ring: 174 40% 24%; - - --radius: 0.5rem; - - /* Custom theme colors */ - --cream: 180 7% 97%; - --navy: 174 40% 24%; - --russet: 0 60% 45%; - --sage: 174 30% 35%; - --ink: 0 0% 20%; - --paper: 180 7% 94%; + /* Content width variables */ + --content-width: min(90%, 1200px); + --reading-width: min(90%, 700px); } -.dark { - --background: 174 20% 12%; - --foreground: 0 0% 90%; +/** + * Spring Theme 🌱 + * Fresh, vibrant green palette inspired by new growth + */ +.spring { + /* Main colors */ + --background: 120 30% 96%; + --foreground: 120 15% 15%; - --muted: 174 20% 18%; - --muted-foreground: 0 0% 70%; + --muted: 120 20% 92%; + --muted-foreground: 120 10% 40%; - --popover: 174 20% 14%; - --popover-foreground: 0 0% 90%; + --popover: 120 30% 96%; + --popover-foreground: 120 15% 15%; - --card: 174 20% 14%; - --card-foreground: 0 0% 90%; + --card: 120 30% 97%; + --card-foreground: 120 15% 15%; - --border: 174 15% 25%; - --input: 174 15% 25%; + --border: 120 30% 85%; + --input: 120 30% 85%; - --primary: 174 35% 45%; - --primary-foreground: 0 0% 100%; + --primary: 142 50% 30%; + --primary-foreground: 120 54% 95%; - --secondary: 174 20% 20%; - --secondary-foreground: 0 0% 90%; + --secondary: 120 20% 92%; + --secondary-foreground: 120 15% 15%; - --accent: 174 40% 45%; - --accent-foreground: 0 0% 100%; + --accent: 142 76% 45%; + --accent-foreground: 120 54% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 100%; + --ring: 142 50% 30%; - --ring: 174 40% 45%; + /* Legacy semantic colors for backward compatibility */ + --cream: 120 30% 96%; + --navy: 142 50% 30%; + --russet: 22 80% 50%; + --sage: 142 40% 55%; + --ink: 120 15% 15%; + --paper: 120 20% 92%; +} + +.spring.dark { + --background: 143 30% 12%; + --foreground: 120 15% 90%; - /* Custom theme colors */ - --cream: 0 0% 90%; - --navy: 174 35% 45%; + --muted: 143 25% 18%; + --muted-foreground: 120 10% 70%; + + --popover: 143 30% 14%; + --popover-foreground: 120 15% 90%; + + --card: 143 30% 14%; + --card-foreground: 120 15% 90%; + + --border: 143 25% 25%; + --input: 143 25% 25%; + + --primary: 142 75% 45%; + --primary-foreground: 120 54% 98%; + + --secondary: 143 25% 20%; + --secondary-foreground: 120 15% 90%; + + --accent: 142 75% 45%; + --accent-foreground: 120 54% 98%; + + --ring: 142 75% 45%; + + /* Legacy semantic colors for backward compatibility */ + --cream: 120 15% 90%; + --navy: 142 75% 45%; + --russet: 22 70% 55%; + --sage: 142 60% 45%; + --ink: 120 15% 90%; + --paper: 143 25% 18%; +} + +/** + * Summer Theme ☀️ + * Warm, sunny palette with amber and gold tones + */ +.summer { + /* Main colors */ + --background: 48 30% 96%; + --foreground: 30 15% 15%; + + --muted: 48 20% 92%; + --muted-foreground: 30 10% 40%; + + --popover: 48 30% 96%; + --popover-foreground: 30 15% 15%; + + --card: 48 30% 97%; + --card-foreground: 30 15% 15%; + + --border: 48 30% 85%; + --input: 48 30% 85%; + + --primary: 36 80% 40%; + --primary-foreground: 48 54% 98%; + + --secondary: 48 20% 92%; + --secondary-foreground: 30 15% 15%; + + --accent: 36 80% 50%; + --accent-foreground: 48 54% 98%; + + --ring: 36 80% 40%; + + /* Legacy semantic colors */ + --cream: 48 30% 96%; + --navy: 36 80% 40%; + --russet: 16 80% 55%; + --sage: 54 60% 45%; + --ink: 30 15% 15%; + --paper: 48 20% 92%; +} + +.summer.dark { + --background: 36 30% 12%; + --foreground: 48 15% 90%; + + --muted: 36 25% 18%; + --muted-foreground: 30 10% 70%; + + --popover: 36 30% 14%; + --popover-foreground: 48 15% 90%; + + --card: 36 30% 14%; + --card-foreground: 48 15% 90%; + + --border: 36 25% 25%; + --input: 36 25% 25%; + + --primary: 36 70% 55%; + --primary-foreground: 48 54% 98%; + + --secondary: 36 25% 20%; + --secondary-foreground: 48 15% 90%; + + --accent: 36 70% 55%; + --accent-foreground: 48 54% 98%; + + --ring: 36 70% 55%; + + /* Legacy semantic colors */ + --cream: 48 15% 90%; + --navy: 36 70% 55%; + --russet: 16 70% 60%; + --sage: 54 50% 50%; + --ink: 48 15% 90%; + --paper: 36 25% 18%; +} + +/** + * Autumn Theme 🍂 + * Rich, earthy palette with warm reds and oranges + */ +.autumn { + /* Main colors */ + --background: 24 30% 96%; + --foreground: 24 15% 15%; + + --muted: 24 20% 92%; + --muted-foreground: 18 10% 40%; + + --popover: 24 30% 96%; + --popover-foreground: 24 15% 15%; + + --card: 24 30% 97%; + --card-foreground: 24 15% 15%; + + --border: 24 30% 85%; + --input: 24 30% 85%; + + --primary: 18 75% 35%; + --primary-foreground: 24 54% 98%; + + --secondary: 24 20% 92%; + --secondary-foreground: 24 15% 15%; + + --accent: 18 75% 45%; + --accent-foreground: 24 54% 98%; + + --ring: 18 75% 35%; + + /* Legacy semantic colors */ + --cream: 24 30% 96%; + --navy: 18 75% 35%; + --russet: 12 80% 45%; + --sage: 36 60% 45%; + --ink: 24 15% 15%; + --paper: 24 20% 92%; +} + +.autumn.dark { + --background: 18 30% 12%; + --foreground: 24 15% 90%; + + --muted: 18 25% 18%; + --muted-foreground: 18 10% 70%; + + --popover: 18 30% 14%; + --popover-foreground: 24 15% 90%; + + --card: 18 30% 14%; + --card-foreground: 24 15% 90%; + + --border: 18 25% 25%; + --input: 18 25% 25%; + + --primary: 18 65% 50%; + --primary-foreground: 24 54% 98%; + + --secondary: 18 25% 20%; + --secondary-foreground: 24 15% 90%; + + --accent: 18 65% 50%; + --accent-foreground: 24 54% 98%; + + --ring: 18 65% 50%; + + /* Legacy semantic colors */ + --cream: 24 15% 90%; + --navy: 18 65% 50%; + --russet: 12 70% 50%; + --sage: 36 50% 50%; + --ink: 24 15% 90%; + --paper: 18 25% 18%; +} + +/** + * Winter Theme ❄️ + * Cool, crisp palette with blues and silvers + */ +.winter { + /* Main colors */ + --background: 210 30% 97%; + --foreground: 210 15% 15%; + + --muted: 210 20% 93%; + --muted-foreground: 210 10% 40%; + + --popover: 210 30% 97%; + --popover-foreground: 210 15% 15%; + + --card: 210 30% 98%; + --card-foreground: 210 15% 15%; + + --border: 210 30% 85%; + --input: 210 30% 85%; + + --primary: 220 70% 35%; + --primary-foreground: 210 54% 98%; + + --secondary: 210 20% 93%; + --secondary-foreground: 210 15% 15%; + + --accent: 220 70% 45%; + --accent-foreground: 210 54% 98%; + + --ring: 220 70% 35%; + + /* Legacy semantic colors */ + --cream: 210 30% 97%; + --navy: 220 70% 35%; --russet: 0 60% 50%; - --sage: 174 30% 40%; - --ink: 0 0% 90%; - --paper: 174 20% 16%; + --sage: 200 60% 45%; + --ink: 210 15% 15%; + --paper: 210 20% 93%; +} + +.winter.dark { + --background: 220 30% 12%; + --foreground: 210 15% 90%; + + --muted: 220 25% 18%; + --muted-foreground: 210 10% 70%; + + --popover: 220 30% 14%; + --popover-foreground: 210 15% 90%; + + --card: 220 30% 14%; + --card-foreground: 210 15% 90%; + + --border: 220 25% 25%; + --input: 220 25% 25%; + + --primary: 220 60% 55%; + --primary-foreground: 210 54% 98%; + + --secondary: 220 25% 20%; + --secondary-foreground: 210 15% 90%; + + --accent: 220 60% 55%; + --accent-foreground: 210 54% 98%; + + --ring: 220 60% 55%; + + /* Legacy semantic colors */ + --cream: 210 15% 90%; + --navy: 220 60% 55%; + --russet: 0 55% 55%; + --sage: 200 45% 50%; + --ink: 210 15% 90%; + --paper: 220 25% 18%; } /* Content width variables */ @@ -97,23 +341,24 @@ html { scroll-behavior: smooth; + @apply spring; /* Default to spring theme */ } body { - @apply bg-cream text-ink font-serif antialiased; + @apply bg-background text-foreground font-serif antialiased; } h1, h2, h3, h4, h5, h6 { - @apply font-serif text-navy dark:text-cream; + @apply font-serif text-primary dark:text-primary-foreground; } p { - @apply text-ink dark:text-cream/90; + @apply text-foreground dark:text-foreground/90; } /* Line numbers for reading view */ .line-number { - @apply inline-block min-w-8 mr-2 text-right text-sage/70 dark:text-sage/60 select-none; + @apply inline-block min-w-8 mr-2 text-right text-muted-foreground/70 dark:text-muted-foreground/60 select-none; } /* Text lines in reading view */ @@ -122,7 +367,7 @@ } .text-line:hover { - @apply bg-navy/5 dark:bg-cream/5; + @apply bg-primary/5 dark:bg-primary-foreground/5; } /* Custom scrollbar */ @@ -131,15 +376,15 @@ } ::-webkit-scrollbar-track { - @apply bg-paper/50 dark:bg-[#1e1e1e]/50; + @apply bg-muted/50 dark:bg-muted/50; } ::-webkit-scrollbar-thumb { - @apply bg-sage rounded-full; + @apply bg-accent/80 rounded-full; } .dark ::-webkit-scrollbar-thumb { - @apply bg-sage/70; + @apply bg-accent/70; } /* Font size utilities for reading view */ diff --git a/client/src/main.tsx b/client/src/main.tsx index fb3bfaf..143f7da 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -2,9 +2,12 @@ import { createRoot } from "react-dom/client"; import App from "./App"; import "./index.css"; import { ThemeProvider } from "@/components/ui/theme-provider"; +import { SeasonProvider } from "@/hooks/use-season"; createRoot(document.getElementById("root")!).render( - + + + );