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
+144
View File
@@ -89,6 +89,78 @@
--reading-text-align: start;
}
/* ── Light palette — "Tulasī Garden" ──
A fresh, distinctly GREEN palette — noticeably green background
(not just a green-tinted white), with deep forest text and primary. */
:root:not(.dark)[data-light-palette="tulasi-garden"] {
--background: oklch(0.92 0.07 152);
--foreground: oklch(0.22 0.09 155);
--card: oklch(0.95 0.055 150);
--card-foreground: oklch(0.22 0.09 155);
--popover: oklch(0.95 0.055 150);
--popover-foreground: oklch(0.22 0.09 155);
--primary: oklch(0.45 0.19 150);
--primary-foreground: oklch(0.97 0.04 150);
--secondary: oklch(0.85 0.1 152);
--secondary-foreground: oklch(0.22 0.09 155);
--muted: oklch(0.87 0.09 152);
--muted-foreground: oklch(0.42 0.11 150);
--accent: oklch(0.87 0.09 152);
--accent-foreground: oklch(0.22 0.09 155);
--border: oklch(0.76 0.12 150);
--input: oklch(0.76 0.12 150);
--ring: oklch(0.56 0.17 150);
--chart-1: oklch(0.45 0.19 150);
--chart-2: oklch(0.42 0.11 150);
--chart-3: oklch(0.56 0.17 150);
--chart-4: oklch(0.34 0.12 145);
--chart-5: oklch(0.22 0.09 155);
--sidebar: oklch(0.95 0.055 150);
--sidebar-foreground: oklch(0.22 0.09 155);
--sidebar-primary: oklch(0.45 0.19 150);
--sidebar-primary-foreground: oklch(0.97 0.04 150);
--sidebar-accent: oklch(0.87 0.09 152);
--sidebar-accent-foreground: oklch(0.22 0.09 155);
--sidebar-border: oklch(0.76 0.12 150);
--sidebar-ring: oklch(0.56 0.17 150);
}
/* ── Light palette — "Yamunā Mist" ──
A distinctly BLUE palette inspired by the sacred Yamunā river —
noticeably blue background (not just a blue-tinted white). */
:root:not(.dark)[data-light-palette="yamuna-mist"] {
--background: oklch(0.92 0.05 230);
--foreground: oklch(0.24 0.09 240);
--card: oklch(0.95 0.04 230);
--card-foreground: oklch(0.24 0.09 240);
--popover: oklch(0.95 0.04 230);
--popover-foreground: oklch(0.24 0.09 240);
--primary: oklch(0.52 0.2 240);
--primary-foreground: oklch(0.97 0.03 230);
--secondary: oklch(0.85 0.08 232);
--secondary-foreground: oklch(0.24 0.09 240);
--muted: oklch(0.87 0.07 232);
--muted-foreground: oklch(0.44 0.11 235);
--accent: oklch(0.87 0.07 232);
--accent-foreground: oklch(0.24 0.09 240);
--border: oklch(0.76 0.1 232);
--input: oklch(0.76 0.1 232);
--ring: oklch(0.62 0.15 235);
--chart-1: oklch(0.52 0.2 240);
--chart-2: oklch(0.44 0.11 235);
--chart-3: oklch(0.62 0.15 235);
--chart-4: oklch(0.38 0.13 240);
--chart-5: oklch(0.24 0.09 240);
--sidebar: oklch(0.95 0.04 230);
--sidebar-foreground: oklch(0.24 0.09 240);
--sidebar-primary: oklch(0.52 0.2 240);
--sidebar-primary-foreground: oklch(0.97 0.03 230);
--sidebar-accent: oklch(0.87 0.07 232);
--sidebar-accent-foreground: oklch(0.24 0.09 240);
--sidebar-border: oklch(0.76 0.1 232);
--sidebar-ring: oklch(0.62 0.15 235);
}
/* ── Dark mode — "Ancient Manuscript" ── */
.dark {
--background: oklch(0.197 0.013 55.5);
@@ -124,6 +196,78 @@
--sidebar-ring: oklch(0.636 0.08 86.9);
}
/* ── Dark palette — "Nīlamaṇi" ──
"Blue jewel" — Kṛṣṇa's sapphire complexion. Deep navy with
bright sky-blue accents — cool and distinctly BLUE. */
.dark[data-dark-palette="nilamani"] {
--background: oklch(0.16 0.05 255);
--foreground: oklch(0.92 0.04 220);
--card: oklch(0.21 0.06 252);
--card-foreground: oklch(0.92 0.04 220);
--popover: oklch(0.21 0.06 252);
--popover-foreground: oklch(0.92 0.04 220);
--primary: oklch(0.72 0.18 235);
--primary-foreground: oklch(0.16 0.05 255);
--secondary: oklch(0.28 0.06 248);
--secondary-foreground: oklch(0.92 0.04 220);
--muted: oklch(0.28 0.06 248);
--muted-foreground: oklch(0.7 0.07 230);
--accent: oklch(0.28 0.06 248);
--accent-foreground: oklch(0.92 0.04 220);
--border: oklch(0.7 0.12 235 / 22%);
--input: oklch(0.7 0.12 235 / 26%);
--ring: oklch(0.66 0.14 235);
--chart-1: oklch(0.72 0.18 235);
--chart-2: oklch(0.7 0.07 230);
--chart-3: oklch(0.66 0.14 235);
--chart-4: oklch(0.46 0.1 240);
--chart-5: oklch(0.28 0.06 248);
--sidebar: oklch(0.21 0.06 252);
--sidebar-foreground: oklch(0.92 0.04 220);
--sidebar-primary: oklch(0.72 0.18 235);
--sidebar-primary-foreground: oklch(0.16 0.05 255);
--sidebar-accent: oklch(0.28 0.06 248);
--sidebar-accent-foreground: oklch(0.92 0.04 220);
--sidebar-border: oklch(0.7 0.12 235 / 22%);
--sidebar-ring: oklch(0.66 0.14 235);
}
/* ── Dark palette — "Vraja Twilight" ──
The crimson dusk over Vraja — deep wine background with warm
rose-red accents. Distinctly RED/WARM, no overlap with the others. */
.dark[data-dark-palette="vraja-twilight"] {
--background: oklch(0.18 0.05 15);
--foreground: oklch(0.92 0.03 25);
--card: oklch(0.22 0.06 12);
--card-foreground: oklch(0.92 0.03 25);
--popover: oklch(0.22 0.06 12);
--popover-foreground: oklch(0.92 0.03 25);
--primary: oklch(0.72 0.18 25);
--primary-foreground: oklch(0.18 0.05 15);
--secondary: oklch(0.28 0.06 15);
--secondary-foreground: oklch(0.92 0.03 25);
--muted: oklch(0.28 0.06 15);
--muted-foreground: oklch(0.7 0.07 22);
--accent: oklch(0.28 0.06 15);
--accent-foreground: oklch(0.92 0.03 25);
--border: oklch(0.7 0.12 25 / 22%);
--input: oklch(0.7 0.12 25 / 26%);
--ring: oklch(0.66 0.14 25);
--chart-1: oklch(0.72 0.18 25);
--chart-2: oklch(0.7 0.07 22);
--chart-3: oklch(0.66 0.14 25);
--chart-4: oklch(0.46 0.1 20);
--chart-5: oklch(0.28 0.06 15);
--sidebar: oklch(0.22 0.06 12);
--sidebar-foreground: oklch(0.92 0.03 25);
--sidebar-primary: oklch(0.72 0.18 25);
--sidebar-primary-foreground: oklch(0.18 0.05 15);
--sidebar-accent: oklch(0.28 0.06 15);
--sidebar-accent-foreground: oklch(0.92 0.03 25);
--sidebar-border: oklch(0.7 0.12 25 / 22%);
--sidebar-ring: oklch(0.66 0.14 25);
}
@layer base {
* {
@apply border-border outline-ring/50;
+5 -5
View File
@@ -117,16 +117,16 @@ export default async function TranscriptionPage({
<TranscriptionNav prev={prev} next={next} />
{/* Audio + Participants row */}
<div className="w-full flex flex-col md:flex-row items-start md:items-end justify-between gap-4">
<div className="w-full flex flex-col md:flex-row items-start md:items-start justify-between gap-4">
{/* Participants */}
{t.participants.length > 0 && (
<>
<div className="flex flex-col gap-2">
<div className="flex pl-2 items-center gap-2 flex-wrap text-sm text-muted-foreground">
<div className="">
<div className="flex flex-row pl-2 mb-2 items-center gap-2 text-sm text-muted-foreground">
<Users className="size-3.5" />
<span>Participants:</span>
</div>
<div className="flex items-center gap-2 flex-wrap text-sm text-muted-foreground">
<div className="max-w-full flex items-center gap-2 flex-wrap text-sm text-muted-foreground">
{t.participants.map((p, i) => (
<Badge
key={i}
@@ -143,7 +143,7 @@ export default async function TranscriptionPage({
{/* Play Audio button */}
{t.audioUrl && (
<div className="w-full md:w-auto flex justify-end">
<div className="w-full md:w-[25%] flex justify-end">
<PlayAudioButton
audioUrl={t.audioUrl}
title={t.name}
+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