mirror of
https://github.com/SamyRai/tercul-frontend.git
synced 2025-12-27 02:31:34 +00:00
Enable users to change the website's appearance based on the four seasons
Implements seasonal themes using CSS variables, a SeasonProvider, and a SeasonSelector component, storing user preferences in local storage. Replit-Commit-Author: Agent Replit-Commit-Session-Id: cbacfb18-842a-4116-a907-18c0105ad8ec Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/39b5c689-6e8a-4d5a-9792-69cc81a56534/5cf2d5fa-a53c-49bf-9d66-cc5f8c3a3659.jpg
This commit is contained in:
parent
427ac70c27
commit
0f2384b05b
@ -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() {
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<SeasonSelector />
|
||||
|
||||
<div className="h-6 w-px bg-accent/20 dark:bg-accent/20 mx-1"></div>
|
||||
|
||||
<Link href="/profile">
|
||||
|
||||
65
client/src/components/ui/season-selector.tsx
Normal file
65
client/src/components/ui/season-selector.tsx
Normal file
@ -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 <Leaf className="h-5 w-5" />;
|
||||
case "summer":
|
||||
return <Sun className="h-5 w-5" />;
|
||||
case "autumn":
|
||||
return <Umbrella className="h-5 w-5" />;
|
||||
case "winter":
|
||||
return <Snowflake className="h-5 w-5" />;
|
||||
default:
|
||||
return <Leaf className="h-5 w-5" />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={`p-2 rounded-full text-foreground/70 dark:text-foreground/70 hover:bg-accent/20 dark:hover:bg-accent/20 ${className}`}
|
||||
>
|
||||
{getIcon()}
|
||||
<span className="sr-only">Change season theme</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => setSeason("spring")}>
|
||||
<Leaf className="mr-2 h-4 w-4" />
|
||||
<span>Spring</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setSeason("summer")}>
|
||||
<Sun className="mr-2 h-4 w-4" />
|
||||
<span>Summer</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setSeason("autumn")}>
|
||||
<Umbrella className="mr-2 h-4 w-4" />
|
||||
<span>Autumn</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setSeason("winter")}>
|
||||
<Snowflake className="mr-2 h-4 w-4" />
|
||||
<span>Winter</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
44
client/src/hooks/use-season.tsx
Normal file
44
client/src/hooks/use-season.tsx
Normal file
@ -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<SeasonContextProps>({
|
||||
season: defaultSeason,
|
||||
setSeason: () => {},
|
||||
});
|
||||
|
||||
export function SeasonProvider({ children }: { children: React.ReactNode }) {
|
||||
const [season, setSeason] = useState<Season>(() => {
|
||||
// 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 (
|
||||
<SeasonContext.Provider value={{ season, setSeason }}>
|
||||
{children}
|
||||
</SeasonContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useSeason = () => useContext(SeasonContext);
|
||||
@ -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 */
|
||||
|
||||
@ -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(
|
||||
<ThemeProvider defaultTheme="system">
|
||||
<App />
|
||||
<SeasonProvider>
|
||||
<App />
|
||||
</SeasonProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user