@shiftbloom-studio/circadian-ui 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/schedule.ts","../src/core/storage.ts","../src/core/tokens.ts","../src/core/script.ts"],"names":["window"],"mappings":"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","file":"server.cjs","sourcesContent":["import { CircadianSchedule, Phase } from \"./types\";\n\nexport const defaultSchedule: CircadianSchedule = {\n dawn: { start: \"05:30\", end: \"08:30\" },\n day: { start: \"08:30\", end: \"17:30\" },\n dusk: { start: \"17:30\", end: \"21:30\" },\n night: { start: \"21:30\", end: \"05:30\" }\n};\n\nexport interface PhaseWindowMinutes {\n start: number;\n end: number;\n}\n\nexport type CircadianScheduleMinutes = Record<Phase, PhaseWindowMinutes>;\n\nconst minutesInDay = 24 * 60;\n\nexport const parseTimeToMinutes = (value: string): number => {\n const [hours, minutes] = value.split(\":\").map(Number);\n if (Number.isNaN(hours) || Number.isNaN(minutes)) {\n throw new Error(`Invalid time format: ${value}`);\n }\n return ((hours % 24) * 60 + minutes) % minutesInDay;\n};\n\nexport const normalizeSchedule = (\n schedule?: Partial<CircadianSchedule>\n): CircadianScheduleMinutes => {\n const merged: CircadianSchedule = {\n ...defaultSchedule,\n ...schedule,\n dawn: { ...defaultSchedule.dawn, ...schedule?.dawn },\n day: { ...defaultSchedule.day, ...schedule?.day },\n dusk: { ...defaultSchedule.dusk, ...schedule?.dusk },\n night: { ...defaultSchedule.night, ...schedule?.night }\n };\n\n return {\n dawn: {\n start: parseTimeToMinutes(merged.dawn.start),\n end: parseTimeToMinutes(merged.dawn.end)\n },\n day: {\n start: parseTimeToMinutes(merged.day.start),\n end: parseTimeToMinutes(merged.day.end)\n },\n dusk: {\n start: parseTimeToMinutes(merged.dusk.start),\n end: parseTimeToMinutes(merged.dusk.end)\n },\n night: {\n start: parseTimeToMinutes(merged.night.start),\n end: parseTimeToMinutes(merged.night.end)\n }\n };\n};\n\nexport const getMinutesFromDate = (date: Date): number => date.getHours() * 60 + date.getMinutes();\n\nconst isWithinRange = (minutes: number, start: number, end: number): boolean => {\n if (start === end) {\n return true;\n }\n if (start < end) {\n return minutes >= start && minutes < end;\n }\n return minutes >= start || minutes < end;\n};\n\nexport const getPhaseFromTime = (date: Date, schedule?: Partial<CircadianSchedule>): Phase => {\n const minutes = getMinutesFromDate(date);\n const normalized = normalizeSchedule(schedule);\n const phases: Phase[] = [\"dawn\", \"day\", \"dusk\", \"night\"];\n for (const phase of phases) {\n const window = normalized[phase];\n if (isWithinRange(minutes, window.start, window.end)) {\n return phase;\n }\n }\n return \"night\";\n};\n\nexport const computeNextTransition = (date: Date, schedule?: Partial<CircadianSchedule>): Date => {\n const normalized = normalizeSchedule(schedule);\n const currentPhase = getPhaseFromTime(date, schedule);\n const minutes = getMinutesFromDate(date);\n const endMinutes = normalized[currentPhase].end;\n let delta = endMinutes - minutes;\n if (delta <= 0) {\n delta += minutesInDay;\n }\n return new Date(date.getTime() + delta * 60 * 1000);\n};\n","import { PersistedState } from \"./types\";\n\nexport const defaultStorageKey = \"cui:preferences\";\n\nconst hasStorage = (): boolean =>\n typeof window !== \"undefined\" && typeof window.localStorage !== \"undefined\";\n\nexport const loadPersistedState = (key: string = defaultStorageKey): PersistedState | null => {\n if (!hasStorage()) {\n return null;\n }\n try {\n const raw = window.localStorage.getItem(key);\n if (!raw) {\n return null;\n }\n return JSON.parse(raw) as PersistedState;\n } catch {\n return null;\n }\n};\n\nexport const persistState = (state: PersistedState, key: string = defaultStorageKey): void => {\n if (!hasStorage()) {\n return;\n }\n try {\n window.localStorage.setItem(key, JSON.stringify(state));\n } catch {\n // Ignore write errors\n }\n};\n\nexport const clearPersistedState = (key: string = defaultStorageKey): void => {\n if (!hasStorage()) {\n return;\n }\n try {\n window.localStorage.removeItem(key);\n } catch {\n // Ignore cleanup errors\n }\n};\n","import { CircadianTokens, ColorSchemeBias, Phase } from \"./types\";\n\nexport const defaultTokens: Record<Phase, CircadianTokens> = {\n dawn: {\n bg: \"27 60% 96%\",\n fg: \"24 18% 18%\",\n muted: \"27 40% 90%\",\n mutedFg: \"24 14% 35%\",\n card: \"0 0% 100%\",\n cardFg: \"24 18% 18%\",\n border: \"24 22% 84%\",\n ring: \"20 65% 45%\",\n accent: \"20 80% 92%\",\n accentFg: \"20 40% 30%\",\n destructive: \"0 74% 55%\",\n destructiveFg: \"0 0% 100%\"\n },\n day: {\n bg: \"0 0% 100%\",\n fg: \"222 28% 14%\",\n muted: \"210 20% 96%\",\n mutedFg: \"215 16% 35%\",\n card: \"0 0% 100%\",\n cardFg: \"222 28% 14%\",\n border: \"214 20% 90%\",\n ring: \"220 65% 45%\",\n accent: \"220 90% 95%\",\n accentFg: \"220 45% 30%\",\n destructive: \"0 72% 55%\",\n destructiveFg: \"0 0% 100%\"\n },\n dusk: {\n bg: \"240 24% 14%\",\n fg: \"30 40% 95%\",\n muted: \"245 20% 22%\",\n mutedFg: \"30 20% 80%\",\n card: \"240 22% 16%\",\n cardFg: \"30 40% 95%\",\n border: \"245 16% 30%\",\n ring: \"32 70% 60%\",\n accent: \"32 55% 25%\",\n accentFg: \"32 70% 85%\",\n destructive: \"0 70% 55%\",\n destructiveFg: \"0 0% 100%\"\n },\n night: {\n bg: \"230 22% 10%\",\n fg: \"210 40% 96%\",\n muted: \"230 18% 16%\",\n mutedFg: \"210 20% 80%\",\n card: \"230 20% 12%\",\n cardFg: \"210 40% 96%\",\n border: \"230 16% 24%\",\n ring: \"210 80% 60%\",\n accent: \"210 35% 20%\",\n accentFg: \"210 50% 90%\",\n destructive: \"0 65% 55%\",\n destructiveFg: \"0 0% 100%\"\n }\n};\n\nexport const cssVarMap: Record<keyof CircadianTokens, string> = {\n bg: \"--cui-bg\",\n fg: \"--cui-fg\",\n muted: \"--cui-muted\",\n mutedFg: \"--cui-muted-fg\",\n card: \"--cui-card\",\n cardFg: \"--cui-card-fg\",\n border: \"--cui-border\",\n ring: \"--cui-ring\",\n accent: \"--cui-accent\",\n accentFg: \"--cui-accent-fg\",\n destructive: \"--cui-destructive\",\n destructiveFg: \"--cui-destructive-fg\"\n};\n\nexport const resolveTokens = (\n phase: Phase,\n overrides?: Partial<Record<Phase, Partial<CircadianTokens>>>\n): CircadianTokens => {\n return {\n ...defaultTokens[phase],\n ...overrides?.[phase]\n };\n};\n\nexport const tokensToCssVars = (tokens: CircadianTokens): Record<string, string> => {\n const vars: Record<string, string> = {};\n for (const [key, value] of Object.entries(tokens)) {\n const cssVar = cssVarMap[key as keyof CircadianTokens];\n vars[cssVar] = value;\n }\n return vars;\n};\n\nexport const applyTokensToElement = (element: HTMLElement, tokens: CircadianTokens) => {\n const vars = tokensToCssVars(tokens);\n for (const [key, value] of Object.entries(vars)) {\n element.style.setProperty(key, value);\n }\n};\n\nexport const applyColorSchemeBias = (\n tokens: CircadianTokens,\n prefers: \"dark\" | \"light\" | \"no-preference\",\n bias: ColorSchemeBias\n): CircadianTokens => {\n if (prefers === \"no-preference\") {\n return tokens;\n }\n const delta = prefers === \"dark\" ? bias.dark : bias.light;\n const adjust = (value: string): string => {\n const [h, s, l] = value.split(\" \");\n const lightness = Math.max(0, Math.min(100, Number(l.replace(\"%\", \"\")) + delta));\n return `${h} ${s} ${lightness}%`;\n };\n\n return {\n ...tokens,\n bg: adjust(tokens.bg),\n fg: adjust(tokens.fg),\n muted: adjust(tokens.muted),\n mutedFg: adjust(tokens.mutedFg),\n card: adjust(tokens.card),\n cardFg: adjust(tokens.cardFg),\n border: adjust(tokens.border),\n ring: adjust(tokens.ring),\n accent: adjust(tokens.accent),\n accentFg: adjust(tokens.accentFg),\n destructive: adjust(tokens.destructive),\n destructiveFg: adjust(tokens.destructiveFg)\n };\n};\n","import { CircadianConfig, CircadianSchedule, Phase, ScheduleMode } from \"./types\";\nimport { defaultSchedule } from \"./schedule\";\nimport { defaultStorageKey } from \"./storage\";\nimport { defaultTokens, tokensToCssVars } from \"./tokens\";\n\nconst serialize = (value: unknown): string => JSON.stringify(value);\n\nconst getMergedSchedule = (schedule?: Partial<CircadianSchedule>): CircadianSchedule => ({\n ...defaultSchedule,\n ...schedule,\n dawn: { ...defaultSchedule.dawn, ...schedule?.dawn },\n day: { ...defaultSchedule.day, ...schedule?.day },\n dusk: { ...defaultSchedule.dusk, ...schedule?.dusk },\n night: { ...defaultSchedule.night, ...schedule?.night }\n});\n\nconst getMode = (mode?: ScheduleMode): ScheduleMode => mode ?? \"time\";\n\nexport const createInlineScript = (config?: CircadianConfig): string => {\n const schedule = getMergedSchedule(config?.schedule);\n const storageKey = config?.storageKey ?? defaultStorageKey;\n const persist = config?.persist !== false;\n const tokens = {\n dawn: tokensToCssVars({\n ...defaultTokens.dawn,\n ...config?.tokens?.dawn\n }),\n day: tokensToCssVars({\n ...defaultTokens.day,\n ...config?.tokens?.day\n }),\n dusk: tokensToCssVars({\n ...defaultTokens.dusk,\n ...config?.tokens?.dusk\n }),\n night: tokensToCssVars({\n ...defaultTokens.night,\n ...config?.tokens?.night\n })\n };\n\n return `(() => {\n try {\n const schedule = ${serialize(schedule)};\n const tokens = ${serialize(tokens)};\n const storageKey = ${serialize(storageKey)};\n const persist = ${serialize(persist)};\n const fallbackMode = ${serialize(getMode(config?.mode))};\n const now = new Date();\n const minutes = now.getHours() * 60 + now.getMinutes();\n\n const isWithin = (value, start, end) => {\n if (start === end) return true;\n if (start < end) return value >= start && value < end;\n return value >= start || value < end;\n };\n\n const parse = (time) => {\n const [h, m] = time.split(\":\").map(Number);\n return ((h % 24) * 60 + m) % 1440;\n };\n\n const normalized = {\n dawn: { start: parse(schedule.dawn.start), end: parse(schedule.dawn.end) },\n day: { start: parse(schedule.day.start), end: parse(schedule.day.end) },\n dusk: { start: parse(schedule.dusk.start), end: parse(schedule.dusk.end) },\n night: { start: parse(schedule.night.start), end: parse(schedule.night.end) }\n };\n\n const order = [\"dawn\", \"day\", \"dusk\", \"night\"];\n let phase = \"night\";\n for (const key of order) {\n const window = normalized[key];\n if (isWithin(minutes, window.start, window.end)) {\n phase = key;\n break;\n }\n }\n\n let mode = fallbackMode;\n const persisted = persist && window.localStorage ? window.localStorage.getItem(storageKey) : null;\n if (persisted) {\n try {\n const parsed = JSON.parse(persisted);\n if (parsed.mode) mode = parsed.mode;\n if (parsed.phase) phase = parsed.phase;\n } catch {\n // ignore\n }\n }\n\n const root = document.documentElement;\n root.setAttribute(\"data-cui-phase\", phase);\n const vars = tokens[phase] || tokens.night;\n for (const key in vars) {\n root.style.setProperty(key, vars[key]);\n }\n } catch {\n // ignore\n }\n})();`;\n};\n\nexport const resolveInitialPhase = (date: Date, schedule?: Partial<CircadianSchedule>): Phase => {\n const merged = getMergedSchedule(schedule);\n const minutes = date.getHours() * 60 + date.getMinutes();\n const isWithin = (value: number, start: number, end: number) => {\n if (start === end) return true;\n if (start < end) return value >= start && value < end;\n return value >= start || value < end;\n };\n const parse = (time: string) => {\n const [h, m] = time.split(\":\").map(Number);\n return ((h % 24) * 60 + m) % 1440;\n };\n const order: Phase[] = [\"dawn\", \"day\", \"dusk\", \"night\"];\n for (const phase of order) {\n const window = merged[phase];\n if (isWithin(minutes, parse(window.start), parse(window.end))) {\n return phase;\n }\n }\n return \"night\";\n};\n"]}
1
+ {"version":3,"sources":["../src/core/schedule.ts","../src/core/storage.ts","../src/core/tokens.ts","../src/core/script.ts"],"names":["window"],"mappings":"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","file":"server.cjs","sourcesContent":["import { CircadianSchedule, Phase } from \"./types\";\n\nexport const defaultSchedule: CircadianSchedule = {\n dawn: { start: \"05:30\", end: \"08:30\" },\n day: { start: \"08:30\", end: \"17:30\" },\n dusk: { start: \"17:30\", end: \"21:30\" },\n night: { start: \"21:30\", end: \"05:30\" }\n};\n\nexport interface PhaseWindowMinutes {\n start: number;\n end: number;\n}\n\nexport type CircadianScheduleMinutes = Record<Phase, PhaseWindowMinutes>;\n\nconst minutesInDay = 24 * 60;\n\nexport const parseTimeToMinutes = (value: string): number => {\n const [hours, minutes] = value.split(\":\").map(Number);\n if (Number.isNaN(hours) || Number.isNaN(minutes)) {\n throw new Error(`Invalid time format: ${value}`);\n }\n return ((hours % 24) * 60 + minutes) % minutesInDay;\n};\n\nexport const normalizeSchedule = (\n schedule?: Partial<CircadianSchedule>\n): CircadianScheduleMinutes => {\n const merged: CircadianSchedule = {\n ...defaultSchedule,\n ...schedule,\n dawn: { ...defaultSchedule.dawn, ...schedule?.dawn },\n day: { ...defaultSchedule.day, ...schedule?.day },\n dusk: { ...defaultSchedule.dusk, ...schedule?.dusk },\n night: { ...defaultSchedule.night, ...schedule?.night }\n };\n\n return {\n dawn: {\n start: parseTimeToMinutes(merged.dawn.start),\n end: parseTimeToMinutes(merged.dawn.end)\n },\n day: {\n start: parseTimeToMinutes(merged.day.start),\n end: parseTimeToMinutes(merged.day.end)\n },\n dusk: {\n start: parseTimeToMinutes(merged.dusk.start),\n end: parseTimeToMinutes(merged.dusk.end)\n },\n night: {\n start: parseTimeToMinutes(merged.night.start),\n end: parseTimeToMinutes(merged.night.end)\n }\n };\n};\n\nexport const getMinutesFromDate = (date: Date): number => date.getHours() * 60 + date.getMinutes();\n\nconst isWithinRange = (minutes: number, start: number, end: number): boolean => {\n if (start === end) {\n return true;\n }\n if (start < end) {\n return minutes >= start && minutes < end;\n }\n return minutes >= start || minutes < end;\n};\n\nexport const getPhaseFromMinutes = (\n minutes: number,\n schedule: CircadianScheduleMinutes\n): Phase => {\n const phases: Phase[] = [\"dawn\", \"day\", \"dusk\", \"night\"];\n for (const phase of phases) {\n const window = schedule[phase];\n if (isWithinRange(minutes, window.start, window.end)) {\n return phase;\n }\n }\n return \"night\";\n};\n\nexport const getPhaseFromTime = (date: Date, schedule?: Partial<CircadianSchedule>): Phase => {\n const minutes = getMinutesFromDate(date);\n const normalized = normalizeSchedule(schedule);\n return getPhaseFromMinutes(minutes, normalized);\n};\n\nexport const computeNextTransition = (date: Date, schedule?: Partial<CircadianSchedule>): Date => {\n const normalized = normalizeSchedule(schedule);\n const currentPhase = getPhaseFromMinutes(getMinutesFromDate(date), normalized);\n const minutes = getMinutesFromDate(date);\n const endMinutes = normalized[currentPhase].end;\n let delta = endMinutes - minutes;\n if (delta <= 0) {\n delta += minutesInDay;\n }\n return new Date(date.getTime() + delta * 60 * 1000);\n};\n\nexport const computeNextTransitionFromMinutes = (\n date: Date,\n schedule: CircadianScheduleMinutes\n): Date => {\n const currentPhase = getPhaseFromMinutes(getMinutesFromDate(date), schedule);\n const minutes = getMinutesFromDate(date);\n const endMinutes = schedule[currentPhase].end;\n let delta = endMinutes - minutes;\n if (delta <= 0) {\n delta += minutesInDay;\n }\n return new Date(date.getTime() + delta * 60 * 1000);\n};\n","import { PersistedState } from \"./types\";\n\nexport const defaultStorageKey = \"cui:preferences\";\n\nconst hasStorage = (): boolean =>\n typeof window !== \"undefined\" && typeof window.localStorage !== \"undefined\";\n\nexport const loadPersistedState = (key: string = defaultStorageKey): PersistedState | null => {\n if (!hasStorage()) {\n return null;\n }\n try {\n const raw = window.localStorage.getItem(key);\n if (!raw) {\n return null;\n }\n return JSON.parse(raw) as PersistedState;\n } catch {\n return null;\n }\n};\n\nexport const persistState = (state: PersistedState, key: string = defaultStorageKey): void => {\n if (!hasStorage()) {\n return;\n }\n try {\n window.localStorage.setItem(key, JSON.stringify(state));\n } catch {\n // Ignore write errors\n }\n};\n\nexport const clearPersistedState = (key: string = defaultStorageKey): void => {\n if (!hasStorage()) {\n return;\n }\n try {\n window.localStorage.removeItem(key);\n } catch {\n // Ignore cleanup errors\n }\n};\n","import { CircadianTokens, ColorSchemeBias, Phase } from \"./types\";\n\nexport const defaultTokens: Record<Phase, CircadianTokens> = {\n dawn: {\n bg: \"27 60% 96%\",\n fg: \"24 18% 18%\",\n muted: \"27 40% 90%\",\n mutedFg: \"24 14% 35%\",\n card: \"0 0% 100%\",\n cardFg: \"24 18% 18%\",\n border: \"24 22% 84%\",\n ring: \"20 65% 45%\",\n accent: \"20 80% 92%\",\n accentFg: \"20 40% 30%\",\n destructive: \"0 74% 55%\",\n destructiveFg: \"0 0% 100%\"\n },\n day: {\n bg: \"0 0% 100%\",\n fg: \"222 28% 14%\",\n muted: \"210 20% 96%\",\n mutedFg: \"215 16% 35%\",\n card: \"0 0% 100%\",\n cardFg: \"222 28% 14%\",\n border: \"214 20% 90%\",\n ring: \"220 65% 45%\",\n accent: \"220 90% 95%\",\n accentFg: \"220 45% 30%\",\n destructive: \"0 72% 55%\",\n destructiveFg: \"0 0% 100%\"\n },\n dusk: {\n bg: \"240 24% 14%\",\n fg: \"30 40% 95%\",\n muted: \"245 20% 22%\",\n mutedFg: \"30 20% 80%\",\n card: \"240 22% 16%\",\n cardFg: \"30 40% 95%\",\n border: \"245 16% 30%\",\n ring: \"32 70% 60%\",\n accent: \"32 55% 25%\",\n accentFg: \"32 70% 85%\",\n destructive: \"0 70% 55%\",\n destructiveFg: \"0 0% 100%\"\n },\n night: {\n bg: \"230 22% 10%\",\n fg: \"210 40% 96%\",\n muted: \"230 18% 16%\",\n mutedFg: \"210 20% 80%\",\n card: \"230 20% 12%\",\n cardFg: \"210 40% 96%\",\n border: \"230 16% 24%\",\n ring: \"210 80% 60%\",\n accent: \"210 35% 20%\",\n accentFg: \"210 50% 90%\",\n destructive: \"0 65% 55%\",\n destructiveFg: \"0 0% 100%\"\n }\n};\n\nexport const cssVarMap: Record<keyof CircadianTokens, string> = {\n bg: \"--cui-bg\",\n fg: \"--cui-fg\",\n muted: \"--cui-muted\",\n mutedFg: \"--cui-muted-fg\",\n card: \"--cui-card\",\n cardFg: \"--cui-card-fg\",\n border: \"--cui-border\",\n ring: \"--cui-ring\",\n accent: \"--cui-accent\",\n accentFg: \"--cui-accent-fg\",\n destructive: \"--cui-destructive\",\n destructiveFg: \"--cui-destructive-fg\"\n};\n\nexport const resolveTokens = (\n phase: Phase,\n overrides?: Partial<Record<Phase, Partial<CircadianTokens>>>\n): CircadianTokens => {\n return {\n ...defaultTokens[phase],\n ...overrides?.[phase]\n };\n};\n\nexport const tokensToCssVars = (tokens: CircadianTokens): Record<string, string> => {\n const vars: Record<string, string> = {};\n for (const [key, value] of Object.entries(tokens)) {\n const cssVar = cssVarMap[key as keyof CircadianTokens];\n vars[cssVar] = value;\n }\n return vars;\n};\n\nexport const applyTokensToElement = (element: HTMLElement, tokens: CircadianTokens) => {\n const vars = tokensToCssVars(tokens);\n for (const [key, value] of Object.entries(vars)) {\n element.style.setProperty(key, value);\n }\n};\n\nexport const applyColorSchemeBias = (\n tokens: CircadianTokens,\n prefers: \"dark\" | \"light\" | \"no-preference\",\n bias: ColorSchemeBias\n): CircadianTokens => {\n if (prefers === \"no-preference\") {\n return tokens;\n }\n const delta = prefers === \"dark\" ? bias.dark : bias.light;\n const adjust = (value: string): string => {\n const [h, s, l] = value.split(\" \");\n const lightness = Math.max(0, Math.min(100, Number(l.replace(\"%\", \"\")) + delta));\n return `${h} ${s} ${lightness}%`;\n };\n\n return {\n ...tokens,\n bg: adjust(tokens.bg),\n fg: adjust(tokens.fg),\n muted: adjust(tokens.muted),\n mutedFg: adjust(tokens.mutedFg),\n card: adjust(tokens.card),\n cardFg: adjust(tokens.cardFg),\n border: adjust(tokens.border),\n ring: adjust(tokens.ring),\n accent: adjust(tokens.accent),\n accentFg: adjust(tokens.accentFg),\n destructive: adjust(tokens.destructive),\n destructiveFg: adjust(tokens.destructiveFg)\n };\n};\n","import { CircadianConfig, CircadianSchedule, Phase, ScheduleMode } from \"./types\";\nimport { defaultSchedule } from \"./schedule\";\nimport { defaultStorageKey } from \"./storage\";\nimport { defaultTokens, tokensToCssVars } from \"./tokens\";\n\nconst serialize = (value: unknown): string => JSON.stringify(value);\n\nconst getMergedSchedule = (schedule?: Partial<CircadianSchedule>): CircadianSchedule => ({\n ...defaultSchedule,\n ...schedule,\n dawn: { ...defaultSchedule.dawn, ...schedule?.dawn },\n day: { ...defaultSchedule.day, ...schedule?.day },\n dusk: { ...defaultSchedule.dusk, ...schedule?.dusk },\n night: { ...defaultSchedule.night, ...schedule?.night }\n});\n\nconst getMode = (mode?: ScheduleMode): ScheduleMode => mode ?? \"auto\";\n\nexport const createInlineScript = (config?: CircadianConfig): string => {\n const schedule = getMergedSchedule(config?.schedule);\n const storageKey = config?.storageKey ?? defaultStorageKey;\n const persist = config?.persist !== false;\n const initialPhase = config?.initialPhase ?? null;\n const setAttributeOn = config?.setAttributeOn ?? \"html\";\n const tokens = {\n dawn: tokensToCssVars({\n ...defaultTokens.dawn,\n ...config?.tokens?.dawn\n }),\n day: tokensToCssVars({\n ...defaultTokens.day,\n ...config?.tokens?.day\n }),\n dusk: tokensToCssVars({\n ...defaultTokens.dusk,\n ...config?.tokens?.dusk\n }),\n night: tokensToCssVars({\n ...defaultTokens.night,\n ...config?.tokens?.night\n })\n };\n\n return `(() => {\n try {\n const schedule = ${serialize(schedule)};\n const tokens = ${serialize(tokens)};\n const storageKey = ${serialize(storageKey)};\n const persist = ${serialize(persist)};\n const fallbackMode = ${serialize(getMode(config?.mode))};\n const initialPhase = ${serialize(initialPhase)};\n const setAttributeOn = ${serialize(setAttributeOn)};\n const now = new Date();\n const minutes = now.getHours() * 60 + now.getMinutes();\n\n const isWithin = (value, start, end) => {\n if (start === end) return true;\n if (start < end) return value >= start && value < end;\n return value >= start || value < end;\n };\n\n const parse = (time) => {\n const [h, m] = time.split(\":\").map(Number);\n return ((h % 24) * 60 + m) % 1440;\n };\n\n const normalized = {\n dawn: { start: parse(schedule.dawn.start), end: parse(schedule.dawn.end) },\n day: { start: parse(schedule.day.start), end: parse(schedule.day.end) },\n dusk: { start: parse(schedule.dusk.start), end: parse(schedule.dusk.end) },\n night: { start: parse(schedule.night.start), end: parse(schedule.night.end) }\n };\n\n const order = [\"dawn\", \"day\", \"dusk\", \"night\"];\n let phase = initialPhase || \"night\";\n if (!initialPhase) {\n for (const key of order) {\n const window = normalized[key];\n if (isWithin(minutes, window.start, window.end)) {\n phase = key;\n break;\n }\n }\n }\n\n let mode = fallbackMode;\n const persisted = persist && window.localStorage ? window.localStorage.getItem(storageKey) : null;\n if (persisted) {\n try {\n const parsed = JSON.parse(persisted);\n if (parsed.mode) mode = parsed.mode;\n if (parsed.phase) phase = parsed.phase;\n } catch {\n // ignore\n }\n }\n\n const root =\n setAttributeOn === \"body\" && document.body ? document.body : document.documentElement;\n root.setAttribute(\"data-cui-phase\", phase);\n const vars = tokens[phase] || tokens.night;\n for (const key in vars) {\n root.style.setProperty(key, vars[key]);\n }\n } catch {\n // ignore\n }\n})();`;\n};\n\nexport const resolveInitialPhase = (date: Date, schedule?: Partial<CircadianSchedule>): Phase => {\n const merged = getMergedSchedule(schedule);\n const minutes = date.getHours() * 60 + date.getMinutes();\n const isWithin = (value: number, start: number, end: number) => {\n if (start === end) return true;\n if (start < end) return value >= start && value < end;\n return value >= start || value < end;\n };\n const parse = (time: string) => {\n const [h, m] = time.split(\":\").map(Number);\n return ((h % 24) * 60 + m) % 1440;\n };\n const order: Phase[] = [\"dawn\", \"day\", \"dusk\", \"night\"];\n for (const phase of order) {\n const window = merged[phase];\n if (isWithin(minutes, parse(window.start), parse(window.end))) {\n return phase;\n }\n }\n return \"night\";\n};\n"]}
package/dist/server.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  type Phase = "dawn" | "day" | "dusk" | "night";
2
- type ScheduleMode = "time" | "sun" | "manual";
2
+ type ScheduleMode = "time" | "sun" | "manual" | "auto";
3
3
  interface PhaseWindow {
4
4
  start: string;
5
5
  end: string;
@@ -56,6 +56,7 @@ interface CircadianConfig {
56
56
  mode?: ScheduleMode;
57
57
  sunTimesProvider?: SunTimesProvider;
58
58
  sunSchedule?: Partial<SunScheduleOptions>;
59
+ initialPhase?: Phase;
59
60
  persist?: boolean;
60
61
  storageKey?: string;
61
62
  accessibility?: Partial<AccessibilityOptions>;
package/dist/server.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  type Phase = "dawn" | "day" | "dusk" | "night";
2
- type ScheduleMode = "time" | "sun" | "manual";
2
+ type ScheduleMode = "time" | "sun" | "manual" | "auto";
3
3
  interface PhaseWindow {
4
4
  start: string;
5
5
  end: string;
@@ -56,6 +56,7 @@ interface CircadianConfig {
56
56
  mode?: ScheduleMode;
57
57
  sunTimesProvider?: SunTimesProvider;
58
58
  sunSchedule?: Partial<SunScheduleOptions>;
59
+ initialPhase?: Phase;
59
60
  persist?: boolean;
60
61
  storageKey?: string;
61
62
  accessibility?: Partial<AccessibilityOptions>;
package/dist/server.js CHANGED
@@ -101,11 +101,13 @@ var getMergedSchedule = (schedule) => ({
101
101
  dusk: { ...defaultSchedule.dusk, ...schedule?.dusk },
102
102
  night: { ...defaultSchedule.night, ...schedule?.night }
103
103
  });
104
- var getMode = (mode) => mode ?? "time";
104
+ var getMode = (mode) => mode ?? "auto";
105
105
  var createInlineScript = (config) => {
106
106
  const schedule = getMergedSchedule(config?.schedule);
107
107
  const storageKey = config?.storageKey ?? defaultStorageKey;
108
108
  const persist = config?.persist !== false;
109
+ const initialPhase = config?.initialPhase ?? null;
110
+ const setAttributeOn = config?.setAttributeOn ?? "html";
109
111
  const tokens = {
110
112
  dawn: tokensToCssVars({
111
113
  ...defaultTokens.dawn,
@@ -131,6 +133,8 @@ var createInlineScript = (config) => {
131
133
  const storageKey = ${serialize(storageKey)};
132
134
  const persist = ${serialize(persist)};
133
135
  const fallbackMode = ${serialize(getMode(config?.mode))};
136
+ const initialPhase = ${serialize(initialPhase)};
137
+ const setAttributeOn = ${serialize(setAttributeOn)};
134
138
  const now = new Date();
135
139
  const minutes = now.getHours() * 60 + now.getMinutes();
136
140
 
@@ -153,12 +157,14 @@ var createInlineScript = (config) => {
153
157
  };
154
158
 
155
159
  const order = ["dawn", "day", "dusk", "night"];
156
- let phase = "night";
157
- for (const key of order) {
158
- const window = normalized[key];
159
- if (isWithin(minutes, window.start, window.end)) {
160
- phase = key;
161
- break;
160
+ let phase = initialPhase || "night";
161
+ if (!initialPhase) {
162
+ for (const key of order) {
163
+ const window = normalized[key];
164
+ if (isWithin(minutes, window.start, window.end)) {
165
+ phase = key;
166
+ break;
167
+ }
162
168
  }
163
169
  }
164
170
 
@@ -174,7 +180,8 @@ var createInlineScript = (config) => {
174
180
  }
175
181
  }
176
182
 
177
- const root = document.documentElement;
183
+ const root =
184
+ setAttributeOn === "body" && document.body ? document.body : document.documentElement;
178
185
  root.setAttribute("data-cui-phase", phase);
179
186
  const vars = tokens[phase] || tokens.night;
180
187
  for (const key in vars) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/schedule.ts","../src/core/storage.ts","../src/core/tokens.ts","../src/core/script.ts"],"names":["window"],"mappings":"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","file":"server.js","sourcesContent":["import { CircadianSchedule, Phase } from \"./types\";\n\nexport const defaultSchedule: CircadianSchedule = {\n dawn: { start: \"05:30\", end: \"08:30\" },\n day: { start: \"08:30\", end: \"17:30\" },\n dusk: { start: \"17:30\", end: \"21:30\" },\n night: { start: \"21:30\", end: \"05:30\" }\n};\n\nexport interface PhaseWindowMinutes {\n start: number;\n end: number;\n}\n\nexport type CircadianScheduleMinutes = Record<Phase, PhaseWindowMinutes>;\n\nconst minutesInDay = 24 * 60;\n\nexport const parseTimeToMinutes = (value: string): number => {\n const [hours, minutes] = value.split(\":\").map(Number);\n if (Number.isNaN(hours) || Number.isNaN(minutes)) {\n throw new Error(`Invalid time format: ${value}`);\n }\n return ((hours % 24) * 60 + minutes) % minutesInDay;\n};\n\nexport const normalizeSchedule = (\n schedule?: Partial<CircadianSchedule>\n): CircadianScheduleMinutes => {\n const merged: CircadianSchedule = {\n ...defaultSchedule,\n ...schedule,\n dawn: { ...defaultSchedule.dawn, ...schedule?.dawn },\n day: { ...defaultSchedule.day, ...schedule?.day },\n dusk: { ...defaultSchedule.dusk, ...schedule?.dusk },\n night: { ...defaultSchedule.night, ...schedule?.night }\n };\n\n return {\n dawn: {\n start: parseTimeToMinutes(merged.dawn.start),\n end: parseTimeToMinutes(merged.dawn.end)\n },\n day: {\n start: parseTimeToMinutes(merged.day.start),\n end: parseTimeToMinutes(merged.day.end)\n },\n dusk: {\n start: parseTimeToMinutes(merged.dusk.start),\n end: parseTimeToMinutes(merged.dusk.end)\n },\n night: {\n start: parseTimeToMinutes(merged.night.start),\n end: parseTimeToMinutes(merged.night.end)\n }\n };\n};\n\nexport const getMinutesFromDate = (date: Date): number => date.getHours() * 60 + date.getMinutes();\n\nconst isWithinRange = (minutes: number, start: number, end: number): boolean => {\n if (start === end) {\n return true;\n }\n if (start < end) {\n return minutes >= start && minutes < end;\n }\n return minutes >= start || minutes < end;\n};\n\nexport const getPhaseFromTime = (date: Date, schedule?: Partial<CircadianSchedule>): Phase => {\n const minutes = getMinutesFromDate(date);\n const normalized = normalizeSchedule(schedule);\n const phases: Phase[] = [\"dawn\", \"day\", \"dusk\", \"night\"];\n for (const phase of phases) {\n const window = normalized[phase];\n if (isWithinRange(minutes, window.start, window.end)) {\n return phase;\n }\n }\n return \"night\";\n};\n\nexport const computeNextTransition = (date: Date, schedule?: Partial<CircadianSchedule>): Date => {\n const normalized = normalizeSchedule(schedule);\n const currentPhase = getPhaseFromTime(date, schedule);\n const minutes = getMinutesFromDate(date);\n const endMinutes = normalized[currentPhase].end;\n let delta = endMinutes - minutes;\n if (delta <= 0) {\n delta += minutesInDay;\n }\n return new Date(date.getTime() + delta * 60 * 1000);\n};\n","import { PersistedState } from \"./types\";\n\nexport const defaultStorageKey = \"cui:preferences\";\n\nconst hasStorage = (): boolean =>\n typeof window !== \"undefined\" && typeof window.localStorage !== \"undefined\";\n\nexport const loadPersistedState = (key: string = defaultStorageKey): PersistedState | null => {\n if (!hasStorage()) {\n return null;\n }\n try {\n const raw = window.localStorage.getItem(key);\n if (!raw) {\n return null;\n }\n return JSON.parse(raw) as PersistedState;\n } catch {\n return null;\n }\n};\n\nexport const persistState = (state: PersistedState, key: string = defaultStorageKey): void => {\n if (!hasStorage()) {\n return;\n }\n try {\n window.localStorage.setItem(key, JSON.stringify(state));\n } catch {\n // Ignore write errors\n }\n};\n\nexport const clearPersistedState = (key: string = defaultStorageKey): void => {\n if (!hasStorage()) {\n return;\n }\n try {\n window.localStorage.removeItem(key);\n } catch {\n // Ignore cleanup errors\n }\n};\n","import { CircadianTokens, ColorSchemeBias, Phase } from \"./types\";\n\nexport const defaultTokens: Record<Phase, CircadianTokens> = {\n dawn: {\n bg: \"27 60% 96%\",\n fg: \"24 18% 18%\",\n muted: \"27 40% 90%\",\n mutedFg: \"24 14% 35%\",\n card: \"0 0% 100%\",\n cardFg: \"24 18% 18%\",\n border: \"24 22% 84%\",\n ring: \"20 65% 45%\",\n accent: \"20 80% 92%\",\n accentFg: \"20 40% 30%\",\n destructive: \"0 74% 55%\",\n destructiveFg: \"0 0% 100%\"\n },\n day: {\n bg: \"0 0% 100%\",\n fg: \"222 28% 14%\",\n muted: \"210 20% 96%\",\n mutedFg: \"215 16% 35%\",\n card: \"0 0% 100%\",\n cardFg: \"222 28% 14%\",\n border: \"214 20% 90%\",\n ring: \"220 65% 45%\",\n accent: \"220 90% 95%\",\n accentFg: \"220 45% 30%\",\n destructive: \"0 72% 55%\",\n destructiveFg: \"0 0% 100%\"\n },\n dusk: {\n bg: \"240 24% 14%\",\n fg: \"30 40% 95%\",\n muted: \"245 20% 22%\",\n mutedFg: \"30 20% 80%\",\n card: \"240 22% 16%\",\n cardFg: \"30 40% 95%\",\n border: \"245 16% 30%\",\n ring: \"32 70% 60%\",\n accent: \"32 55% 25%\",\n accentFg: \"32 70% 85%\",\n destructive: \"0 70% 55%\",\n destructiveFg: \"0 0% 100%\"\n },\n night: {\n bg: \"230 22% 10%\",\n fg: \"210 40% 96%\",\n muted: \"230 18% 16%\",\n mutedFg: \"210 20% 80%\",\n card: \"230 20% 12%\",\n cardFg: \"210 40% 96%\",\n border: \"230 16% 24%\",\n ring: \"210 80% 60%\",\n accent: \"210 35% 20%\",\n accentFg: \"210 50% 90%\",\n destructive: \"0 65% 55%\",\n destructiveFg: \"0 0% 100%\"\n }\n};\n\nexport const cssVarMap: Record<keyof CircadianTokens, string> = {\n bg: \"--cui-bg\",\n fg: \"--cui-fg\",\n muted: \"--cui-muted\",\n mutedFg: \"--cui-muted-fg\",\n card: \"--cui-card\",\n cardFg: \"--cui-card-fg\",\n border: \"--cui-border\",\n ring: \"--cui-ring\",\n accent: \"--cui-accent\",\n accentFg: \"--cui-accent-fg\",\n destructive: \"--cui-destructive\",\n destructiveFg: \"--cui-destructive-fg\"\n};\n\nexport const resolveTokens = (\n phase: Phase,\n overrides?: Partial<Record<Phase, Partial<CircadianTokens>>>\n): CircadianTokens => {\n return {\n ...defaultTokens[phase],\n ...overrides?.[phase]\n };\n};\n\nexport const tokensToCssVars = (tokens: CircadianTokens): Record<string, string> => {\n const vars: Record<string, string> = {};\n for (const [key, value] of Object.entries(tokens)) {\n const cssVar = cssVarMap[key as keyof CircadianTokens];\n vars[cssVar] = value;\n }\n return vars;\n};\n\nexport const applyTokensToElement = (element: HTMLElement, tokens: CircadianTokens) => {\n const vars = tokensToCssVars(tokens);\n for (const [key, value] of Object.entries(vars)) {\n element.style.setProperty(key, value);\n }\n};\n\nexport const applyColorSchemeBias = (\n tokens: CircadianTokens,\n prefers: \"dark\" | \"light\" | \"no-preference\",\n bias: ColorSchemeBias\n): CircadianTokens => {\n if (prefers === \"no-preference\") {\n return tokens;\n }\n const delta = prefers === \"dark\" ? bias.dark : bias.light;\n const adjust = (value: string): string => {\n const [h, s, l] = value.split(\" \");\n const lightness = Math.max(0, Math.min(100, Number(l.replace(\"%\", \"\")) + delta));\n return `${h} ${s} ${lightness}%`;\n };\n\n return {\n ...tokens,\n bg: adjust(tokens.bg),\n fg: adjust(tokens.fg),\n muted: adjust(tokens.muted),\n mutedFg: adjust(tokens.mutedFg),\n card: adjust(tokens.card),\n cardFg: adjust(tokens.cardFg),\n border: adjust(tokens.border),\n ring: adjust(tokens.ring),\n accent: adjust(tokens.accent),\n accentFg: adjust(tokens.accentFg),\n destructive: adjust(tokens.destructive),\n destructiveFg: adjust(tokens.destructiveFg)\n };\n};\n","import { CircadianConfig, CircadianSchedule, Phase, ScheduleMode } from \"./types\";\nimport { defaultSchedule } from \"./schedule\";\nimport { defaultStorageKey } from \"./storage\";\nimport { defaultTokens, tokensToCssVars } from \"./tokens\";\n\nconst serialize = (value: unknown): string => JSON.stringify(value);\n\nconst getMergedSchedule = (schedule?: Partial<CircadianSchedule>): CircadianSchedule => ({\n ...defaultSchedule,\n ...schedule,\n dawn: { ...defaultSchedule.dawn, ...schedule?.dawn },\n day: { ...defaultSchedule.day, ...schedule?.day },\n dusk: { ...defaultSchedule.dusk, ...schedule?.dusk },\n night: { ...defaultSchedule.night, ...schedule?.night }\n});\n\nconst getMode = (mode?: ScheduleMode): ScheduleMode => mode ?? \"time\";\n\nexport const createInlineScript = (config?: CircadianConfig): string => {\n const schedule = getMergedSchedule(config?.schedule);\n const storageKey = config?.storageKey ?? defaultStorageKey;\n const persist = config?.persist !== false;\n const tokens = {\n dawn: tokensToCssVars({\n ...defaultTokens.dawn,\n ...config?.tokens?.dawn\n }),\n day: tokensToCssVars({\n ...defaultTokens.day,\n ...config?.tokens?.day\n }),\n dusk: tokensToCssVars({\n ...defaultTokens.dusk,\n ...config?.tokens?.dusk\n }),\n night: tokensToCssVars({\n ...defaultTokens.night,\n ...config?.tokens?.night\n })\n };\n\n return `(() => {\n try {\n const schedule = ${serialize(schedule)};\n const tokens = ${serialize(tokens)};\n const storageKey = ${serialize(storageKey)};\n const persist = ${serialize(persist)};\n const fallbackMode = ${serialize(getMode(config?.mode))};\n const now = new Date();\n const minutes = now.getHours() * 60 + now.getMinutes();\n\n const isWithin = (value, start, end) => {\n if (start === end) return true;\n if (start < end) return value >= start && value < end;\n return value >= start || value < end;\n };\n\n const parse = (time) => {\n const [h, m] = time.split(\":\").map(Number);\n return ((h % 24) * 60 + m) % 1440;\n };\n\n const normalized = {\n dawn: { start: parse(schedule.dawn.start), end: parse(schedule.dawn.end) },\n day: { start: parse(schedule.day.start), end: parse(schedule.day.end) },\n dusk: { start: parse(schedule.dusk.start), end: parse(schedule.dusk.end) },\n night: { start: parse(schedule.night.start), end: parse(schedule.night.end) }\n };\n\n const order = [\"dawn\", \"day\", \"dusk\", \"night\"];\n let phase = \"night\";\n for (const key of order) {\n const window = normalized[key];\n if (isWithin(minutes, window.start, window.end)) {\n phase = key;\n break;\n }\n }\n\n let mode = fallbackMode;\n const persisted = persist && window.localStorage ? window.localStorage.getItem(storageKey) : null;\n if (persisted) {\n try {\n const parsed = JSON.parse(persisted);\n if (parsed.mode) mode = parsed.mode;\n if (parsed.phase) phase = parsed.phase;\n } catch {\n // ignore\n }\n }\n\n const root = document.documentElement;\n root.setAttribute(\"data-cui-phase\", phase);\n const vars = tokens[phase] || tokens.night;\n for (const key in vars) {\n root.style.setProperty(key, vars[key]);\n }\n } catch {\n // ignore\n }\n})();`;\n};\n\nexport const resolveInitialPhase = (date: Date, schedule?: Partial<CircadianSchedule>): Phase => {\n const merged = getMergedSchedule(schedule);\n const minutes = date.getHours() * 60 + date.getMinutes();\n const isWithin = (value: number, start: number, end: number) => {\n if (start === end) return true;\n if (start < end) return value >= start && value < end;\n return value >= start || value < end;\n };\n const parse = (time: string) => {\n const [h, m] = time.split(\":\").map(Number);\n return ((h % 24) * 60 + m) % 1440;\n };\n const order: Phase[] = [\"dawn\", \"day\", \"dusk\", \"night\"];\n for (const phase of order) {\n const window = merged[phase];\n if (isWithin(minutes, parse(window.start), parse(window.end))) {\n return phase;\n }\n }\n return \"night\";\n};\n"]}
1
+ {"version":3,"sources":["../src/core/schedule.ts","../src/core/storage.ts","../src/core/tokens.ts","../src/core/script.ts"],"names":["window"],"mappings":"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","file":"server.js","sourcesContent":["import { CircadianSchedule, Phase } from \"./types\";\n\nexport const defaultSchedule: CircadianSchedule = {\n dawn: { start: \"05:30\", end: \"08:30\" },\n day: { start: \"08:30\", end: \"17:30\" },\n dusk: { start: \"17:30\", end: \"21:30\" },\n night: { start: \"21:30\", end: \"05:30\" }\n};\n\nexport interface PhaseWindowMinutes {\n start: number;\n end: number;\n}\n\nexport type CircadianScheduleMinutes = Record<Phase, PhaseWindowMinutes>;\n\nconst minutesInDay = 24 * 60;\n\nexport const parseTimeToMinutes = (value: string): number => {\n const [hours, minutes] = value.split(\":\").map(Number);\n if (Number.isNaN(hours) || Number.isNaN(minutes)) {\n throw new Error(`Invalid time format: ${value}`);\n }\n return ((hours % 24) * 60 + minutes) % minutesInDay;\n};\n\nexport const normalizeSchedule = (\n schedule?: Partial<CircadianSchedule>\n): CircadianScheduleMinutes => {\n const merged: CircadianSchedule = {\n ...defaultSchedule,\n ...schedule,\n dawn: { ...defaultSchedule.dawn, ...schedule?.dawn },\n day: { ...defaultSchedule.day, ...schedule?.day },\n dusk: { ...defaultSchedule.dusk, ...schedule?.dusk },\n night: { ...defaultSchedule.night, ...schedule?.night }\n };\n\n return {\n dawn: {\n start: parseTimeToMinutes(merged.dawn.start),\n end: parseTimeToMinutes(merged.dawn.end)\n },\n day: {\n start: parseTimeToMinutes(merged.day.start),\n end: parseTimeToMinutes(merged.day.end)\n },\n dusk: {\n start: parseTimeToMinutes(merged.dusk.start),\n end: parseTimeToMinutes(merged.dusk.end)\n },\n night: {\n start: parseTimeToMinutes(merged.night.start),\n end: parseTimeToMinutes(merged.night.end)\n }\n };\n};\n\nexport const getMinutesFromDate = (date: Date): number => date.getHours() * 60 + date.getMinutes();\n\nconst isWithinRange = (minutes: number, start: number, end: number): boolean => {\n if (start === end) {\n return true;\n }\n if (start < end) {\n return minutes >= start && minutes < end;\n }\n return minutes >= start || minutes < end;\n};\n\nexport const getPhaseFromMinutes = (\n minutes: number,\n schedule: CircadianScheduleMinutes\n): Phase => {\n const phases: Phase[] = [\"dawn\", \"day\", \"dusk\", \"night\"];\n for (const phase of phases) {\n const window = schedule[phase];\n if (isWithinRange(minutes, window.start, window.end)) {\n return phase;\n }\n }\n return \"night\";\n};\n\nexport const getPhaseFromTime = (date: Date, schedule?: Partial<CircadianSchedule>): Phase => {\n const minutes = getMinutesFromDate(date);\n const normalized = normalizeSchedule(schedule);\n return getPhaseFromMinutes(minutes, normalized);\n};\n\nexport const computeNextTransition = (date: Date, schedule?: Partial<CircadianSchedule>): Date => {\n const normalized = normalizeSchedule(schedule);\n const currentPhase = getPhaseFromMinutes(getMinutesFromDate(date), normalized);\n const minutes = getMinutesFromDate(date);\n const endMinutes = normalized[currentPhase].end;\n let delta = endMinutes - minutes;\n if (delta <= 0) {\n delta += minutesInDay;\n }\n return new Date(date.getTime() + delta * 60 * 1000);\n};\n\nexport const computeNextTransitionFromMinutes = (\n date: Date,\n schedule: CircadianScheduleMinutes\n): Date => {\n const currentPhase = getPhaseFromMinutes(getMinutesFromDate(date), schedule);\n const minutes = getMinutesFromDate(date);\n const endMinutes = schedule[currentPhase].end;\n let delta = endMinutes - minutes;\n if (delta <= 0) {\n delta += minutesInDay;\n }\n return new Date(date.getTime() + delta * 60 * 1000);\n};\n","import { PersistedState } from \"./types\";\n\nexport const defaultStorageKey = \"cui:preferences\";\n\nconst hasStorage = (): boolean =>\n typeof window !== \"undefined\" && typeof window.localStorage !== \"undefined\";\n\nexport const loadPersistedState = (key: string = defaultStorageKey): PersistedState | null => {\n if (!hasStorage()) {\n return null;\n }\n try {\n const raw = window.localStorage.getItem(key);\n if (!raw) {\n return null;\n }\n return JSON.parse(raw) as PersistedState;\n } catch {\n return null;\n }\n};\n\nexport const persistState = (state: PersistedState, key: string = defaultStorageKey): void => {\n if (!hasStorage()) {\n return;\n }\n try {\n window.localStorage.setItem(key, JSON.stringify(state));\n } catch {\n // Ignore write errors\n }\n};\n\nexport const clearPersistedState = (key: string = defaultStorageKey): void => {\n if (!hasStorage()) {\n return;\n }\n try {\n window.localStorage.removeItem(key);\n } catch {\n // Ignore cleanup errors\n }\n};\n","import { CircadianTokens, ColorSchemeBias, Phase } from \"./types\";\n\nexport const defaultTokens: Record<Phase, CircadianTokens> = {\n dawn: {\n bg: \"27 60% 96%\",\n fg: \"24 18% 18%\",\n muted: \"27 40% 90%\",\n mutedFg: \"24 14% 35%\",\n card: \"0 0% 100%\",\n cardFg: \"24 18% 18%\",\n border: \"24 22% 84%\",\n ring: \"20 65% 45%\",\n accent: \"20 80% 92%\",\n accentFg: \"20 40% 30%\",\n destructive: \"0 74% 55%\",\n destructiveFg: \"0 0% 100%\"\n },\n day: {\n bg: \"0 0% 100%\",\n fg: \"222 28% 14%\",\n muted: \"210 20% 96%\",\n mutedFg: \"215 16% 35%\",\n card: \"0 0% 100%\",\n cardFg: \"222 28% 14%\",\n border: \"214 20% 90%\",\n ring: \"220 65% 45%\",\n accent: \"220 90% 95%\",\n accentFg: \"220 45% 30%\",\n destructive: \"0 72% 55%\",\n destructiveFg: \"0 0% 100%\"\n },\n dusk: {\n bg: \"240 24% 14%\",\n fg: \"30 40% 95%\",\n muted: \"245 20% 22%\",\n mutedFg: \"30 20% 80%\",\n card: \"240 22% 16%\",\n cardFg: \"30 40% 95%\",\n border: \"245 16% 30%\",\n ring: \"32 70% 60%\",\n accent: \"32 55% 25%\",\n accentFg: \"32 70% 85%\",\n destructive: \"0 70% 55%\",\n destructiveFg: \"0 0% 100%\"\n },\n night: {\n bg: \"230 22% 10%\",\n fg: \"210 40% 96%\",\n muted: \"230 18% 16%\",\n mutedFg: \"210 20% 80%\",\n card: \"230 20% 12%\",\n cardFg: \"210 40% 96%\",\n border: \"230 16% 24%\",\n ring: \"210 80% 60%\",\n accent: \"210 35% 20%\",\n accentFg: \"210 50% 90%\",\n destructive: \"0 65% 55%\",\n destructiveFg: \"0 0% 100%\"\n }\n};\n\nexport const cssVarMap: Record<keyof CircadianTokens, string> = {\n bg: \"--cui-bg\",\n fg: \"--cui-fg\",\n muted: \"--cui-muted\",\n mutedFg: \"--cui-muted-fg\",\n card: \"--cui-card\",\n cardFg: \"--cui-card-fg\",\n border: \"--cui-border\",\n ring: \"--cui-ring\",\n accent: \"--cui-accent\",\n accentFg: \"--cui-accent-fg\",\n destructive: \"--cui-destructive\",\n destructiveFg: \"--cui-destructive-fg\"\n};\n\nexport const resolveTokens = (\n phase: Phase,\n overrides?: Partial<Record<Phase, Partial<CircadianTokens>>>\n): CircadianTokens => {\n return {\n ...defaultTokens[phase],\n ...overrides?.[phase]\n };\n};\n\nexport const tokensToCssVars = (tokens: CircadianTokens): Record<string, string> => {\n const vars: Record<string, string> = {};\n for (const [key, value] of Object.entries(tokens)) {\n const cssVar = cssVarMap[key as keyof CircadianTokens];\n vars[cssVar] = value;\n }\n return vars;\n};\n\nexport const applyTokensToElement = (element: HTMLElement, tokens: CircadianTokens) => {\n const vars = tokensToCssVars(tokens);\n for (const [key, value] of Object.entries(vars)) {\n element.style.setProperty(key, value);\n }\n};\n\nexport const applyColorSchemeBias = (\n tokens: CircadianTokens,\n prefers: \"dark\" | \"light\" | \"no-preference\",\n bias: ColorSchemeBias\n): CircadianTokens => {\n if (prefers === \"no-preference\") {\n return tokens;\n }\n const delta = prefers === \"dark\" ? bias.dark : bias.light;\n const adjust = (value: string): string => {\n const [h, s, l] = value.split(\" \");\n const lightness = Math.max(0, Math.min(100, Number(l.replace(\"%\", \"\")) + delta));\n return `${h} ${s} ${lightness}%`;\n };\n\n return {\n ...tokens,\n bg: adjust(tokens.bg),\n fg: adjust(tokens.fg),\n muted: adjust(tokens.muted),\n mutedFg: adjust(tokens.mutedFg),\n card: adjust(tokens.card),\n cardFg: adjust(tokens.cardFg),\n border: adjust(tokens.border),\n ring: adjust(tokens.ring),\n accent: adjust(tokens.accent),\n accentFg: adjust(tokens.accentFg),\n destructive: adjust(tokens.destructive),\n destructiveFg: adjust(tokens.destructiveFg)\n };\n};\n","import { CircadianConfig, CircadianSchedule, Phase, ScheduleMode } from \"./types\";\nimport { defaultSchedule } from \"./schedule\";\nimport { defaultStorageKey } from \"./storage\";\nimport { defaultTokens, tokensToCssVars } from \"./tokens\";\n\nconst serialize = (value: unknown): string => JSON.stringify(value);\n\nconst getMergedSchedule = (schedule?: Partial<CircadianSchedule>): CircadianSchedule => ({\n ...defaultSchedule,\n ...schedule,\n dawn: { ...defaultSchedule.dawn, ...schedule?.dawn },\n day: { ...defaultSchedule.day, ...schedule?.day },\n dusk: { ...defaultSchedule.dusk, ...schedule?.dusk },\n night: { ...defaultSchedule.night, ...schedule?.night }\n});\n\nconst getMode = (mode?: ScheduleMode): ScheduleMode => mode ?? \"auto\";\n\nexport const createInlineScript = (config?: CircadianConfig): string => {\n const schedule = getMergedSchedule(config?.schedule);\n const storageKey = config?.storageKey ?? defaultStorageKey;\n const persist = config?.persist !== false;\n const initialPhase = config?.initialPhase ?? null;\n const setAttributeOn = config?.setAttributeOn ?? \"html\";\n const tokens = {\n dawn: tokensToCssVars({\n ...defaultTokens.dawn,\n ...config?.tokens?.dawn\n }),\n day: tokensToCssVars({\n ...defaultTokens.day,\n ...config?.tokens?.day\n }),\n dusk: tokensToCssVars({\n ...defaultTokens.dusk,\n ...config?.tokens?.dusk\n }),\n night: tokensToCssVars({\n ...defaultTokens.night,\n ...config?.tokens?.night\n })\n };\n\n return `(() => {\n try {\n const schedule = ${serialize(schedule)};\n const tokens = ${serialize(tokens)};\n const storageKey = ${serialize(storageKey)};\n const persist = ${serialize(persist)};\n const fallbackMode = ${serialize(getMode(config?.mode))};\n const initialPhase = ${serialize(initialPhase)};\n const setAttributeOn = ${serialize(setAttributeOn)};\n const now = new Date();\n const minutes = now.getHours() * 60 + now.getMinutes();\n\n const isWithin = (value, start, end) => {\n if (start === end) return true;\n if (start < end) return value >= start && value < end;\n return value >= start || value < end;\n };\n\n const parse = (time) => {\n const [h, m] = time.split(\":\").map(Number);\n return ((h % 24) * 60 + m) % 1440;\n };\n\n const normalized = {\n dawn: { start: parse(schedule.dawn.start), end: parse(schedule.dawn.end) },\n day: { start: parse(schedule.day.start), end: parse(schedule.day.end) },\n dusk: { start: parse(schedule.dusk.start), end: parse(schedule.dusk.end) },\n night: { start: parse(schedule.night.start), end: parse(schedule.night.end) }\n };\n\n const order = [\"dawn\", \"day\", \"dusk\", \"night\"];\n let phase = initialPhase || \"night\";\n if (!initialPhase) {\n for (const key of order) {\n const window = normalized[key];\n if (isWithin(minutes, window.start, window.end)) {\n phase = key;\n break;\n }\n }\n }\n\n let mode = fallbackMode;\n const persisted = persist && window.localStorage ? window.localStorage.getItem(storageKey) : null;\n if (persisted) {\n try {\n const parsed = JSON.parse(persisted);\n if (parsed.mode) mode = parsed.mode;\n if (parsed.phase) phase = parsed.phase;\n } catch {\n // ignore\n }\n }\n\n const root =\n setAttributeOn === \"body\" && document.body ? document.body : document.documentElement;\n root.setAttribute(\"data-cui-phase\", phase);\n const vars = tokens[phase] || tokens.night;\n for (const key in vars) {\n root.style.setProperty(key, vars[key]);\n }\n } catch {\n // ignore\n }\n})();`;\n};\n\nexport const resolveInitialPhase = (date: Date, schedule?: Partial<CircadianSchedule>): Phase => {\n const merged = getMergedSchedule(schedule);\n const minutes = date.getHours() * 60 + date.getMinutes();\n const isWithin = (value: number, start: number, end: number) => {\n if (start === end) return true;\n if (start < end) return value >= start && value < end;\n return value >= start || value < end;\n };\n const parse = (time: string) => {\n const [h, m] = time.split(\":\").map(Number);\n return ((h % 24) * 60 + m) % 1440;\n };\n const order: Phase[] = [\"dawn\", \"day\", \"dusk\", \"night\"];\n for (const phase of order) {\n const window = merged[phase];\n if (isWithin(minutes, parse(window.start), parse(window.end))) {\n return phase;\n }\n }\n return \"night\";\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shiftbloom-studio/circadian-ui",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Accessible, time-aware theming for React and Next.js.",
5
5
  "keywords": [
6
6
  "react",
@@ -67,21 +67,21 @@
67
67
  "typecheck": "tsc -p tsconfig.json --noEmit"
68
68
  },
69
69
  "devDependencies": {
70
- "@eslint/js": "^9.17.0",
70
+ "@eslint/js": "^10.0.1",
71
71
  "@changesets/changelog-git": "^0.2.1",
72
- "@changesets/cli": "^2.28.1",
73
- "@testing-library/jest-dom": "^6.8.0",
72
+ "@changesets/cli": "^2.30.0",
73
+ "@testing-library/jest-dom": "^6.9.1",
74
74
  "@types/jest": "^30.0.0",
75
- "@typescript-eslint/eslint-plugin": "^8.18.2",
76
- "@typescript-eslint/parser": "^8.18.2",
77
- "eslint": "^9.17.0",
75
+ "@typescript-eslint/eslint-plugin": "^8.58.1",
76
+ "@typescript-eslint/parser": "^8.58.1",
77
+ "eslint": "^10.2.0",
78
78
  "eslint-config-prettier": "^10.1.8",
79
- "globals": "^17.0.0",
80
- "jest": "^30.2.0",
81
- "jest-environment-jsdom": "^30.2.0",
82
- "prettier": "^3.4.2",
83
- "ts-jest": "^29.2.5",
84
- "tsup": "^8.3.6",
85
- "typescript": "^5.7.3"
79
+ "globals": "^17.5.0",
80
+ "jest": "^30.3.0",
81
+ "jest-environment-jsdom": "^30.3.0",
82
+ "prettier": "^3.8.2",
83
+ "ts-jest": "^29.4.9",
84
+ "tsup": "^8.5.1",
85
+ "typescript": "^6.0.2"
86
86
  }
87
87
  }