Implements Task 3 from Bible Reader 2025 plan: - Created lib/reading-preferences.ts with 4 presets (default, dyslexia, highContrast, minimal) - Implemented loadPreferences/savePreferences using localStorage - Added getCSSVariables for dynamic styling - Created ReadingView component with full mobile responsiveness - Touch interaction: tap top third shows header, bottom third toggles controls - Verse text is clickable with hover effects - Navigation controls (prev/next chapter, settings button) - Created test file for preferences 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
109 lines
2.7 KiB
TypeScript
109 lines
2.7 KiB
TypeScript
import { ReadingPreference } from '@/types'
|
|
|
|
const PRESETS: Record<string, ReadingPreference> = {
|
|
default: {
|
|
fontFamily: 'georgia',
|
|
fontSize: 18,
|
|
lineHeight: 1.8,
|
|
letterSpacing: 0,
|
|
textAlign: 'left',
|
|
backgroundColor: '#faf8f3',
|
|
textColor: '#333333',
|
|
margin: 'normal',
|
|
preset: 'default'
|
|
},
|
|
dyslexia: {
|
|
fontFamily: 'atkinson',
|
|
fontSize: 18,
|
|
lineHeight: 1.9,
|
|
letterSpacing: 0.08,
|
|
textAlign: 'left',
|
|
backgroundColor: '#f5f5dc',
|
|
textColor: '#333333',
|
|
margin: 'normal',
|
|
preset: 'dyslexia'
|
|
},
|
|
highContrast: {
|
|
fontFamily: 'inter',
|
|
fontSize: 16,
|
|
lineHeight: 1.6,
|
|
letterSpacing: 0,
|
|
textAlign: 'left',
|
|
backgroundColor: '#000000',
|
|
textColor: '#ffffff',
|
|
margin: 'wide',
|
|
preset: 'highContrast'
|
|
},
|
|
minimal: {
|
|
fontFamily: 'georgia',
|
|
fontSize: 16,
|
|
lineHeight: 1.6,
|
|
letterSpacing: 0,
|
|
textAlign: 'left',
|
|
backgroundColor: '#ffffff',
|
|
textColor: '#000000',
|
|
margin: 'narrow',
|
|
preset: 'minimal'
|
|
}
|
|
}
|
|
|
|
const STORAGE_KEY = 'bibleReaderPreferences'
|
|
|
|
export function getPreset(name: keyof typeof PRESETS): ReadingPreference {
|
|
return PRESETS[name]
|
|
}
|
|
|
|
export function loadPreferences(): ReadingPreference {
|
|
if (typeof window === 'undefined') {
|
|
return PRESETS.default
|
|
}
|
|
|
|
try {
|
|
const stored = localStorage.getItem(STORAGE_KEY)
|
|
return stored ? JSON.parse(stored) : PRESETS.default
|
|
} catch {
|
|
return PRESETS.default
|
|
}
|
|
}
|
|
|
|
export function savePreferences(prefs: ReadingPreference): void {
|
|
if (typeof window === 'undefined') return
|
|
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs))
|
|
} catch (e) {
|
|
console.error('Failed to save preferences:', e)
|
|
}
|
|
}
|
|
|
|
export function getCSSVariables(prefs: ReadingPreference): Record<string, string> {
|
|
return {
|
|
'--font-family': getFontStack(prefs.fontFamily),
|
|
'--font-size': `${prefs.fontSize}px`,
|
|
'--line-height': `${prefs.lineHeight}`,
|
|
'--letter-spacing': `${prefs.letterSpacing}em`,
|
|
'--bg-color': prefs.backgroundColor,
|
|
'--text-color': prefs.textColor,
|
|
'--margin-width': getMarginWidth(prefs.margin),
|
|
}
|
|
}
|
|
|
|
function getFontStack(fontFamily: string): string {
|
|
const stacks: Record<string, string> = {
|
|
georgia: 'Georgia, serif',
|
|
inter: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif',
|
|
atkinson: '"Atkinson Hyperlegible", sans-serif',
|
|
merriweather: '"Merriweather", serif',
|
|
}
|
|
return stacks[fontFamily] || stacks.georgia
|
|
}
|
|
|
|
function getMarginWidth(margin: string): string {
|
|
const margins: Record<string, string> = {
|
|
narrow: 'max(1rem, 5%)',
|
|
normal: 'max(2rem, 10%)',
|
|
wide: 'max(4rem, 15%)',
|
|
}
|
|
return margins[margin] || margins.normal
|
|
}
|