theme update

This commit is contained in:
gosangam
2026-05-11 01:58:02 +05:30
parent db8deb232b
commit 9a3056fbd8
3 changed files with 287 additions and 6 deletions
+138 -1
View File
@@ -17,18 +17,88 @@ function useMounted() {
);
}
type LightPalette = "aged-parchment" | "tulasi-garden" | "yamuna-mist";
type DarkPalette = "ancient-manuscript" | "nilamani" | "vraja-twilight";
type ReadingPrefs = {
serifFont: boolean;
fontSize: string;
justified: boolean;
lightPalette: LightPalette;
darkPalette: DarkPalette;
};
const DEFAULT_PREFS: ReadingPrefs = {
serifFont: false,
fontSize: "base",
justified: false,
lightPalette: "aged-parchment",
darkPalette: "ancient-manuscript",
};
// Preview swatches use approximations of each palette's background
// and primary colours — the real colours come from the CSS variables.
const LIGHT_PALETTES: {
id: LightPalette;
name: string;
bg: string;
primary: string;
}[] = [
// Warm cream + saffron — the original
{
id: "aged-parchment",
name: "Aged Parchment",
bg: "#f5ecd7",
primary: "#b88940",
},
// Sage green background + deep emerald — distinctly GREEN
{
id: "tulasi-garden",
name: "Tulasī Garden",
bg: "#c4dcc1",
primary: "#207537",
},
// Soft blue background + cerulean — distinctly BLUE
{
id: "yamuna-mist",
name: "Yamunā Mist",
bg: "#c2d7ec",
primary: "#1f6fd6",
},
];
const DARK_PALETTES: {
id: DarkPalette;
name: string;
bg: string;
primary: string;
}[] = [
// Warm brown + saffron — the original
{
id: "ancient-manuscript",
name: "Ancient Manuscript",
bg: "#2a2519",
primary: "#d0a04e",
},
// Deep navy + sky-blue — distinctly BLUE
{
id: "nilamani",
name: "Nīlamaṇi",
bg: "#101a36",
primary: "#4a9fff",
},
// Deep wine + rose-red — distinctly RED/WARM
{
id: "vraja-twilight",
name: "Vraja Twilight",
bg: "#2b1217",
primary: "#e07252",
},
];
const LIGHT_PALETTE_IDS = LIGHT_PALETTES.map((p) => p.id);
const DARK_PALETTE_IDS = DARK_PALETTES.map((p) => p.id);
const FONT_SIZES = [
{ value: "xs", label: "XS" },
{ value: "sm", label: "S" },
@@ -58,6 +128,12 @@ function loadPrefs(): ReadingPrefs {
typeof parsed.justified === "boolean"
? parsed.justified
: DEFAULT_PREFS.justified,
lightPalette: LIGHT_PALETTE_IDS.includes(parsed.lightPalette)
? parsed.lightPalette
: DEFAULT_PREFS.lightPalette,
darkPalette: DARK_PALETTE_IDS.includes(parsed.darkPalette)
? parsed.darkPalette
: DEFAULT_PREFS.darkPalette,
};
} catch {
return DEFAULT_PREFS;
@@ -86,6 +162,11 @@ function applyClasses(prefs: ReadingPrefs) {
// Justified
root.classList.toggle("reading-justified", prefs.justified);
// Palettes — always set both attributes so the appropriate one takes
// effect when the user toggles light ↔ dark.
root.setAttribute("data-light-palette", prefs.lightPalette);
root.setAttribute("data-dark-palette", prefs.darkPalette);
}
function Toggle({
@@ -128,7 +209,7 @@ export function ReadingSettings() {
const [prefs, setPrefs] = useState<ReadingPrefs>(() => loadPrefs());
const panelRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
const { theme, setTheme } = useTheme();
const { theme, setTheme, resolvedTheme } = useTheme();
const mounted = useMounted();
// Apply classes on mount (prefs already loaded via lazy initializer)
@@ -178,6 +259,14 @@ export function ReadingSettings() {
const fontSizeIndex = FONT_SIZES.findIndex((f) => f.value === prefs.fontSize);
const currentFontSizeLabel = FONT_SIZES[fontSizeIndex]?.label ?? "M";
// Show palettes matching the currently visible mode. Falls back to light if
// resolvedTheme hasn't been determined yet (e.g. first render).
const isDark = resolvedTheme === "dark";
const visiblePalettes = isDark ? DARK_PALETTES : LIGHT_PALETTES;
const activePaletteId = isDark ? prefs.darkPalette : prefs.lightPalette;
const activePaletteName =
visiblePalettes.find((p) => p.id === activePaletteId)?.name ?? "";
if (!mounted) {
return (
<Button variant="ghost" size="icon-sm" aria-label="Reading settings">
@@ -239,6 +328,54 @@ export function ReadingSettings() {
</div>
</div>
{/* Palette selector — shows light or dark palettes depending on
the currently resolved theme. */}
<div className="mb-4">
<div className="flex items-center justify-between">
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
Palette
</span>
<span className="text-xs text-muted-foreground">
{activePaletteName}
</span>
</div>
<div className="grid grid-cols-3 gap-2 mt-2">
{visiblePalettes.map((p) => {
const isActive = activePaletteId === p.id;
return (
<button
key={p.id}
type="button"
onClick={() =>
updatePrefs(
isDark
? { darkPalette: p.id as DarkPalette }
: { lightPalette: p.id as LightPalette },
)
}
aria-label={`Use ${p.name} palette`}
aria-pressed={isActive}
title={p.name}
className={cn(
"group relative aspect-square rounded-md transition-all cursor-pointer overflow-hidden",
"border-2 outline-none focus-visible:ring-2 focus-visible:ring-ring",
isActive
? "border-primary ring-2 ring-primary/30"
: "border-border hover:border-foreground/40",
)}
style={{ backgroundColor: p.bg }}
>
{/* Primary-color dot in the corner */}
<span
className="absolute bottom-1.5 right-1.5 size-3 rounded-full ring-1 ring-black/10"
style={{ backgroundColor: p.primary }}
/>
</button>
);
})}
</div>
</div>
<div className="border-t border-border pt-3 space-y-1">
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
Reading