Something went wrong. Try again.
I will pick a better name later.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176export const THEME_COOKIE = "later_theme";export const COLOR_THEME_COOKIE = "later_color_theme";
export const DEFAULT_THEME_PREFERENCE = "auto";export const THEME_PREFERENCES = ["auto", "light", "dark"] as const;
export const DEFAULT_COLOR_THEME = "black-white";export const COLOR_THEMES = [ { id: "black-white", label: "Black & white", swatches: ["#ffffff", "#171717", "#737373"], }, { id: "catppuccin", label: "Catppuccin", swatches: ["#f5e0dc", "#cba6f7", "#89b4fa", "#a6e3a1"], }, { id: "gruvbox", label: "Gruvbox", swatches: ["#fbf1c7", "#d79921", "#98971a", "#282828"], }, { id: "ayu", label: "Ayu", swatches: ["#fafafa", "#ff6a00", "#36a3d9", "#0f1419"], }, { id: "everforest", label: "Everforest", swatches: ["#fdf6e3", "#8da101", "#3a94c5", "#2d353b"], }, { id: "nord", label: "Nord", swatches: ["#eceff4", "#88c0d0", "#5e81ac", "#2e3440"], }, { id: "rose-pine", label: "Rosé Pine", swatches: ["#faf4ed", "#b4637a", "#56949f", "#191724"], }, { id: "vesper", label: "Vesper", swatches: ["#101010", "#ffc799", "#99ffe4", "#ff8080"], },] as const;
export type ThemePreference = (typeof THEME_PREFERENCES)[number];export type EffectiveTheme = "light" | "dark";export type ColorTheme = (typeof COLOR_THEMES)[number]["id"];
const THEME_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;const COLOR_THEME_IDS = COLOR_THEMES.map((theme) => theme.id) as ColorTheme[];let removeSystemThemeListener: (() => void) | undefined;
export function isThemePreference(value: unknown): value is ThemePreference { return typeof value === "string" && THEME_PREFERENCES.includes(value as ThemePreference);}
export function parseThemePreference(value: unknown): ThemePreference { return isThemePreference(value) ? value : DEFAULT_THEME_PREFERENCE;}
export function isColorTheme(value: unknown): value is ColorTheme { return typeof value === "string" && COLOR_THEME_IDS.includes(value as ColorTheme);}
export function parseColorTheme(value: unknown): ColorTheme { return isColorTheme(value) ? value : DEFAULT_COLOR_THEME;}
export function effectiveTheme(preference: ThemePreference, systemTheme: EffectiveTheme): EffectiveTheme { return preference === "auto" ? systemTheme : preference;}
function parseCookieHeader(cookieHeader: string) { return cookieHeader.split(";").reduce<Record<string, string>>((cookies, cookie) => { const [name, ...valueParts] = cookie.trim().split("="); if (!name) return cookies; cookies[name] = decodeURIComponent(valueParts.join("=")); return cookies; }, {});}
export function themePreferenceFromCookieHeader(cookieHeader: string | null | undefined): ThemePreference { if (!cookieHeader) return DEFAULT_THEME_PREFERENCE; return parseThemePreference(parseCookieHeader(cookieHeader)[THEME_COOKIE]);}
export function colorThemeFromCookieHeader(cookieHeader: string | null | undefined): ColorTheme { if (!cookieHeader) return DEFAULT_COLOR_THEME; return parseColorTheme(parseCookieHeader(cookieHeader)[COLOR_THEME_COOKIE]);}
export function themePreferenceFromRequest(request: Request): ThemePreference { return themePreferenceFromCookieHeader(request.headers.get("Cookie"));}
export function colorThemeFromRequest(request: Request): ColorTheme { return colorThemeFromCookieHeader(request.headers.get("Cookie"));}
export function serializeThemePreferenceCookie(preference: ThemePreference) { return `${THEME_COOKIE}=${encodeURIComponent(preference)}; Path=/; SameSite=Lax; Max-Age=${THEME_COOKIE_MAX_AGE}`;}
export function serializeColorThemeCookie(colorTheme: ColorTheme) { return `${COLOR_THEME_COOKIE}=${encodeURIComponent(colorTheme)}; Path=/; SameSite=Lax; Max-Age=${THEME_COOKIE_MAX_AGE}`;}
export function persistThemePreference(preference: ThemePreference) { if (typeof document === "undefined") return; document.cookie = serializeThemePreferenceCookie(preference);}
export function persistColorTheme(colorTheme: ColorTheme) { if (typeof document === "undefined") return; document.cookie = serializeColorThemeCookie(colorTheme);}
function systemThemeFromWindow(win: Window): EffectiveTheme { return win.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";}
function applyEffectiveTheme(theme: EffectiveTheme, doc: Document) { doc.documentElement.classList.toggle("dark", theme === "dark"); doc.documentElement.style.colorScheme = theme;}
export function applyColorTheme(colorTheme: ColorTheme, doc: Document = document) { doc.documentElement.dataset.colorTheme = colorTheme;}
export function applyThemePreference(preference: ThemePreference, win: Window = window) { removeSystemThemeListener?.(); removeSystemThemeListener = undefined;
const mediaQuery = win.matchMedia("(prefers-color-scheme: dark)"); const apply = () => applyEffectiveTheme(effectiveTheme(preference, mediaQuery.matches ? "dark" : "light"), win.document);
apply();
if (preference === "auto") { mediaQuery.addEventListener("change", apply); removeSystemThemeListener = () => mediaQuery.removeEventListener("change", apply); }}
export function applyThemeSettings(settings: { themePreference: ThemePreference; colorTheme: ColorTheme }, win: Window = window) { applyColorTheme(settings.colorTheme, win.document); applyThemePreference(settings.themePreference, win);}
const colorThemeValidationExpression = COLOR_THEME_IDS.map((theme) => `rawColorTheme === '${theme}'`).join(" || ");
export const themeInitScript = `(() => { const cookieValue = (name) => { const prefix = name + '='; const cookie = document.cookie.split(';').map((part) => part.trim()).find((part) => part.startsWith(prefix)); return cookie ? decodeURIComponent(cookie.slice(prefix.length)) : undefined; }; const rawPreference = cookieValue('${THEME_COOKIE}') ?? '${DEFAULT_THEME_PREFERENCE}'; const preference = rawPreference === 'light' || rawPreference === 'dark' || rawPreference === 'auto' ? rawPreference : '${DEFAULT_THEME_PREFERENCE}'; const rawColorTheme = cookieValue('${COLOR_THEME_COOKIE}') ?? '${DEFAULT_COLOR_THEME}'; const colorTheme = ${colorThemeValidationExpression} ? rawColorTheme : '${DEFAULT_COLOR_THEME}'; const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; const theme = preference === 'auto' ? systemTheme : preference; document.documentElement.classList.toggle('dark', theme === 'dark'); document.documentElement.style.colorScheme = theme; document.documentElement.dataset.colorTheme = colorTheme;})();`;