theme update
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user