import { FONT_STACKS } from '$lib/theme/presets'; import { readerSettingsSchema, type ReaderSettings } from '$lib/schema/reader'; export const READER_SETTINGS_STORAGE_KEY = 'chitai:reader-settings'; /** Georgia — the app's own title face, and a reasonable default for long prose. */ export const DEFAULT_READER_SETTINGS: ReaderSettings = { fontFamily: FONT_STACKS[1].value, fontSize: 18, fontWeight: 400, lineHeight: 1.6, letterSpacing: 0, margin: 48, gap: 6, // foliate caps the reading area at this times the column count, so 1000 gives // a 2000px spread — enough to fill a large display rather than leaving the // book as a narrow strip. Its own default of 720 is noticeably tight. maxInlineSize: 1000, maxColumnCount: 2, flow: 'paginated', justify: true, hyphenate: true }; /** Reused from the app theme: system stacks only, so nothing silently falls back. */ export const READER_FONT_OPTIONS = FONT_STACKS; /** Slider bounds, kept next to the schema they mirror. */ export const READER_BOUNDS = { fontSize: { min: 12, max: 32, step: 1 }, fontWeight: { min: 300, max: 700, step: 100 }, lineHeight: { min: 1, max: 2.5, step: 0.05 }, letterSpacing: { min: -0.05, max: 0.2, step: 0.01 }, margin: { min: 0, max: 120, step: 4 }, gap: { min: 0, max: 15, step: 1 }, maxInlineSize: { min: 400, max: 2400, step: 20 } } as const; /** * Reads stored settings, falling back to defaults on anything unusable. * * Unknown keys are stripped and missing ones filled, so settings added in a * later release do not invalidate what a reader already has stored. */ export function loadReaderSettings(): ReaderSettings { try { const raw = localStorage.getItem(READER_SETTINGS_STORAGE_KEY); if (!raw) return DEFAULT_READER_SETTINGS; const parsed = readerSettingsSchema.safeParse({ ...DEFAULT_READER_SETTINGS, ...JSON.parse(raw) }); return parsed.success ? parsed.data : DEFAULT_READER_SETTINGS; } catch { return DEFAULT_READER_SETTINGS; } } export function saveReaderSettings(settings: ReaderSettings) { try { localStorage.setItem(READER_SETTINGS_STORAGE_KEY, JSON.stringify(settings)); } catch (error) { // A full quota must not stop anyone reading. console.warn('Could not save reader settings', error); } } /** * The half of the settings that foliate takes as renderer attributes. * * The renderer has no JS property API — these must be set with setAttribute. * Note there is no `margin` shorthand upstream, and no `spread`: on a reflowable * book a two-page spread is max-column-count 2. */ export function toRendererAttributes(s: ReaderSettings): Record { return { flow: s.flow, gap: `${s.gap}%`, 'margin-top': `${s.margin}px`, 'margin-bottom': `${s.margin}px`, 'margin-left': `${s.margin}px`, 'margin-right': `${s.margin}px`, 'max-inline-size': `${s.maxInlineSize}px`, 'max-column-count': String(s.maxColumnCount) }; }