Something went wrong. Try again.
grain.social is a photo sharing platform built on atproto. grain.social
appview atproto photography
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697import { writable, get } from "svelte/store";import { browser } from "$app/environment";import { callXrpc } from "$hatk/client";
export type ThemePreference = "system" | "light" | "dark";type ResolvedTheme = "light" | "dark";
// Also read by the inline script in app.html, which applies the theme before// first paint. Keep the two in sync.const STORAGE_KEY = "grain:theme";
const THEME_COLOR: Record<ResolvedTheme, string> = { dark: "#080b12", light: "#ffffff",};
/** What the user picked — "system" follows the OS. */export const themePreference = writable<ThemePreference>(readStored());
function isThemePreference(value: unknown): value is ThemePreference { return value === "system" || value === "light" || value === "dark";}
function systemTheme(): ResolvedTheme { if (!browser) return "dark"; return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";}
/** * Everything theme-dependent keys off `data-theme` on <html>, in CSS — that way * server-rendered markup is already correct and nothing has to swap on hydration. */function apply(preference: ThemePreference): void { if (!browser) return; const resolved = preference === "system" ? systemTheme() : preference; document.documentElement.dataset.theme = resolved; document .querySelector('meta[name="theme-color"]') ?.setAttribute("content", THEME_COLOR[resolved]);}
function readStored(): ThemePreference { if (!browser) return "system"; try { const stored = localStorage.getItem(STORAGE_KEY); if (isThemePreference(stored)) return stored; } catch {} return "system";}
function store(preference: ThemePreference): void { if (!browser) return; try { localStorage.setItem(STORAGE_KEY, preference); } catch {}}
/** * Sync the store with what the inline app.html script already applied, and keep * following the OS while the preference is "system". Call once from the root layout. */export function initTheme(): () => void { if (!browser) return () => {}; const preference = readStored(); themePreference.set(preference); apply(preference);
const media = window.matchMedia("(prefers-color-scheme: light)"); const onChange = () => { if (get(themePreference) === "system") apply("system"); }; media.addEventListener("change", onChange); return () => media.removeEventListener("change", onChange);}
/** User picked a theme: apply it, remember it locally, and sync it to the account. */export async function setTheme(preference: ThemePreference): Promise<void> { if (get(themePreference) === preference) return; themePreference.set(preference); store(preference); apply(preference); try { await callXrpc("dev.hatk.putPreference", { key: "theme", value: preference }); } catch { // Logged-out or offline — localStorage still holds the choice. }}
/** Apply a theme that came from the account's saved preferences (no write-back). */export function applyStoredTheme(value: unknown): void { if (!isThemePreference(value) || !browser) return; if (get(themePreference) === value) return; themePreference.set(value); store(value); apply(value);}