Includes all Phase 1 features: - Search-first navigation with auto-complete - Responsive reading interface (desktop/tablet/mobile) - 4 customization presets + full fine-tuning controls - Layered details panel with notes, bookmarks, highlights - Smart offline caching with IndexedDB and auto-sync - Full accessibility (WCAG 2.1 AA) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1105 lines
30 KiB
Markdown
1105 lines
30 KiB
Markdown
# Focus Mode Enhanced - Implementation Plan
|
|
|
|
## 📋 Overview
|
|
|
|
Focus Mode Enhanced builds upon the existing distraction-free reading mode to provide advanced concentration aids and reading tools that help users maintain focus and improve reading comprehension.
|
|
|
|
**Status:** Planning Phase
|
|
**Priority:** Medium
|
|
**Estimated Time:** 1 week (40 hours)
|
|
**Target Completion:** TBD
|
|
|
|
---
|
|
|
|
## 🎯 Goals & Objectives
|
|
|
|
### Primary Goals
|
|
1. Reduce eye strain and improve reading comfort
|
|
2. Help users maintain focus during extended reading sessions
|
|
3. Accommodate different reading styles and preferences
|
|
4. Improve reading comprehension through guided techniques
|
|
5. Provide accessibility features for users with reading difficulties
|
|
|
|
### User Value Proposition
|
|
- **For students/scholars**: Enhanced focus during deep Bible study
|
|
- **For users with ADHD/dyslexia**: Guided reading aids
|
|
- **For elderly users**: Reduced eye strain with reading guides
|
|
- **For speed readers**: Tools to maintain pace and rhythm
|
|
|
|
---
|
|
|
|
## ✨ Feature Specifications
|
|
|
|
### 1. Dimming/Masking Surrounding Text
|
|
**Description:** Reduce visual clutter by dimming text that isn't currently in focus.
|
|
|
|
**Modes:**
|
|
- **Paragraph Spotlight**: Highlight only the current paragraph
|
|
- **Sentence Spotlight**: Highlight only the current sentence
|
|
- **Verse Spotlight**: Highlight only the current verse
|
|
- **Custom Range**: Highlight N lines above and below cursor
|
|
|
|
**Implementation Details:**
|
|
```typescript
|
|
interface DimmingConfig {
|
|
enabled: boolean
|
|
mode: 'paragraph' | 'sentence' | 'verse' | 'custom'
|
|
intensity: number // 0-100% opacity for dimmed text
|
|
customLinesAbove: number
|
|
customLinesBelow: number
|
|
blurAmount: number // 0-10px blur for dimmed text
|
|
}
|
|
```
|
|
|
|
**Visual Behavior:**
|
|
- Active text: 100% opacity, sharp
|
|
- Dimmed text: 20-60% opacity (user adjustable)
|
|
- Optional: 0-5px blur on dimmed text
|
|
- Smooth transitions (300ms) when focus changes
|
|
|
|
---
|
|
|
|
### 2. Guided Reading Line
|
|
**Description:** A horizontal line or overlay that follows the user's reading position.
|
|
|
|
**Types:**
|
|
- **Static Line**: Fixed position on screen, text scrolls beneath
|
|
- **Following Line**: Moves with scroll to track reading position
|
|
- **Reading Ruler**: Semi-transparent bar that highlights current line
|
|
- **Dual Lines**: Top and bottom lines create a reading window
|
|
|
|
**Implementation Details:**
|
|
```typescript
|
|
interface ReadingLineConfig {
|
|
enabled: boolean
|
|
type: 'static' | 'following' | 'ruler' | 'dual'
|
|
position: number // 0-100% from top of viewport
|
|
color: string
|
|
thickness: number // 1-5px
|
|
opacity: number // 0-100%
|
|
windowHeight: number // For ruler/dual mode (lines of text)
|
|
}
|
|
```
|
|
|
|
**Visual Examples:**
|
|
```
|
|
Static Line (position: 33%):
|
|
┌─────────────────┐
|
|
│ verse text... │
|
|
│ verse text... │
|
|
├═════════════════┤ ← Reading line
|
|
│ verse text... │
|
|
│ verse text... │
|
|
└─────────────────┘
|
|
|
|
Reading Ruler:
|
|
┌─────────────────┐
|
|
│ verse text... │
|
|
┌═════════════════┐ ← Top border
|
|
│█ verse text... █│ ← Highlighted
|
|
└═════════════════┘ ← Bottom border
|
|
│ verse text... │
|
|
└─────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
### 3. Spotlight Mode
|
|
**Description:** Highlight the current paragraph/verse with visual emphasis.
|
|
|
|
**Effects:**
|
|
- **Background Highlight**: Colored background behind active text
|
|
- **Border Highlight**: Subtle border around active element
|
|
- **Shadow Effect**: Drop shadow on active text
|
|
- **Scale Effect**: Slightly enlarge active text (1.05x)
|
|
- **Glow Effect**: Soft glow around active text
|
|
|
|
**Implementation Details:**
|
|
```typescript
|
|
interface SpotlightConfig {
|
|
enabled: boolean
|
|
effect: 'background' | 'border' | 'shadow' | 'scale' | 'glow' | 'combined'
|
|
backgroundColor: string
|
|
borderColor: string
|
|
borderWidth: number
|
|
shadowIntensity: number
|
|
scaleAmount: number // 1.0 - 1.1
|
|
glowColor: string
|
|
transitionSpeed: number // 100-500ms
|
|
}
|
|
```
|
|
|
|
**CSS Implementation:**
|
|
```css
|
|
.verse-spotlight {
|
|
background-color: var(--spotlight-bg);
|
|
box-shadow: 0 0 20px var(--spotlight-glow);
|
|
border-left: 4px solid var(--spotlight-border);
|
|
padding: 8px 12px;
|
|
margin: 4px -12px;
|
|
border-radius: 4px;
|
|
transform: scale(1.02);
|
|
transition: all 0.3s ease;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 4. Reading Ruler Overlay
|
|
**Description:** A semi-transparent overlay that creates a focused reading window.
|
|
|
|
**Features:**
|
|
- Top and bottom masks that darken text above/below
|
|
- Adjustable window height (1-5 lines)
|
|
- Follows scroll automatically
|
|
- Keyboard shortcuts to move window up/down
|
|
- Touch gestures to adjust window size
|
|
|
|
**Implementation Details:**
|
|
```typescript
|
|
interface ReadingRulerConfig {
|
|
enabled: boolean
|
|
windowHeight: number // lines of text (1-5)
|
|
maskOpacity: number // 0-90%
|
|
maskColor: string
|
|
autoFollow: boolean // Follow scroll
|
|
smoothTracking: boolean
|
|
}
|
|
```
|
|
|
|
**DOM Structure:**
|
|
```html
|
|
<div class="reading-ruler-overlay">
|
|
<div class="ruler-mask ruler-mask-top"></div>
|
|
<div class="ruler-window">
|
|
<!-- Active reading area (transparent) -->
|
|
</div>
|
|
<div class="ruler-mask ruler-mask-bottom"></div>
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
### 5. Auto-Scroll with Adjustable Speed
|
|
**Description:** Automatic scrolling at a consistent pace to maintain reading rhythm.
|
|
|
|
**Features:**
|
|
- Speed control: 10-300 words per minute
|
|
- Pause/resume with spacebar
|
|
- Visual indicator of scroll progress
|
|
- Speed adjustment while scrolling (arrow keys)
|
|
- Auto-pause at chapter end
|
|
- Remembers speed preference per user
|
|
|
|
**Implementation Details:**
|
|
```typescript
|
|
interface AutoScrollConfig {
|
|
enabled: boolean
|
|
wordsPerMinute: number // 10-300
|
|
isPaused: boolean
|
|
smoothness: 'linear' | 'ease' | 'ease-in-out'
|
|
showProgressBar: boolean
|
|
pauseAtChapterEnd: boolean
|
|
}
|
|
|
|
class AutoScroller {
|
|
private scrollInterval: number | null
|
|
private currentSpeed: number
|
|
|
|
start(config: AutoScrollConfig): void
|
|
pause(): void
|
|
resume(): void
|
|
adjustSpeed(delta: number): void
|
|
stop(): void
|
|
}
|
|
```
|
|
|
|
**Controls:**
|
|
- Play/Pause button (spacebar)
|
|
- Speed slider (10-300 WPM)
|
|
- +/- buttons (adjust by 10 WPM)
|
|
- Progress indicator
|
|
- Keyboard shortcuts:
|
|
- Space: Pause/Resume
|
|
- Up Arrow: +10 WPM
|
|
- Down Arrow: -10 WPM
|
|
- Escape: Stop
|
|
|
|
---
|
|
|
|
### 6. Bionic Reading Format
|
|
**Description:** Bold the first few letters of each word to guide eye movement.
|
|
|
|
**Algorithm:**
|
|
```typescript
|
|
function bionicFormat(word: string): string {
|
|
const length = word.length
|
|
if (length <= 1) return word
|
|
|
|
// Bold first 40-60% of letters
|
|
const boldCount = Math.ceil(length * 0.5)
|
|
const bold = word.substring(0, boldCount)
|
|
const regular = word.substring(boldCount)
|
|
|
|
return `<strong>${bold}</strong>${regular}`
|
|
}
|
|
|
|
// Example:
|
|
// "reading" → "<strong>read</strong>ing"
|
|
// "Bible" → "<strong>Bib</strong>le"
|
|
```
|
|
|
|
**Implementation Details:**
|
|
```typescript
|
|
interface BionicReadingConfig {
|
|
enabled: boolean
|
|
intensity: number // 30-70% of word to bold
|
|
fontWeight: number // 400-700
|
|
applyToVerseNumbers: boolean
|
|
preserveExistingFormatting: boolean
|
|
}
|
|
```
|
|
|
|
**Visual Example:**
|
|
```
|
|
Normal:
|
|
In the beginning God created the heaven and the earth.
|
|
|
|
Bionic:
|
|
In th**e** beg**inn**ing G**od** cre**at**ed th**e** hea**v**en a**nd** th**e** ea**rth**.
|
|
```
|
|
|
|
---
|
|
|
|
### 7. Sentence-by-Sentence Mode
|
|
**Description:** Display one sentence at a time, advancing with user input.
|
|
|
|
**Features:**
|
|
- Show current sentence in full opacity
|
|
- Hide/fade other sentences
|
|
- Advance with click, tap, or arrow key
|
|
- Show sentence counter (1/15)
|
|
- Option to show N sentences at a time
|
|
- Smooth transitions between sentences
|
|
|
|
**Implementation Details:**
|
|
```typescript
|
|
interface SentenceModeConfig {
|
|
enabled: boolean
|
|
sentencesVisible: number // 1-3
|
|
advanceMethod: 'click' | 'tap' | 'key' | 'auto'
|
|
autoAdvanceDelay: number // ms (for auto mode)
|
|
showCounter: boolean
|
|
fadeTransition: boolean
|
|
}
|
|
|
|
class SentenceNavigator {
|
|
private sentences: string[]
|
|
private currentIndex: number
|
|
|
|
next(): void
|
|
previous(): void
|
|
jumpTo(index: number): void
|
|
getCurrentSentence(): string
|
|
getTotalSentences(): number
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 8. Breathing Reminders
|
|
**Description:** Periodic gentle reminders to take breaks and breathe.
|
|
|
|
**Features:**
|
|
- Configurable interval (5-60 minutes)
|
|
- Subtle visual cue (pulsing icon or border)
|
|
- Optional sound notification
|
|
- Guided breathing animation (4-7-8 technique)
|
|
- Auto-pause reading during reminder
|
|
- "I'm ready" button to resume
|
|
|
|
**Implementation Details:**
|
|
```typescript
|
|
interface BreathingReminderConfig {
|
|
enabled: boolean
|
|
intervalMinutes: number // 5-60
|
|
visualCue: 'pulse' | 'fade' | 'border' | 'icon'
|
|
soundEnabled: boolean
|
|
showGuidedBreathing: boolean
|
|
autoPauseReading: boolean
|
|
}
|
|
|
|
class BreathingReminder {
|
|
private timer: NodeJS.Timeout | null
|
|
|
|
start(config: BreathingReminderConfig): void
|
|
stop(): void
|
|
reset(): void
|
|
showReminder(): void
|
|
hideReminder(): void
|
|
}
|
|
```
|
|
|
|
**Guided Breathing Animation:**
|
|
```
|
|
┌─────────────────────┐
|
|
│ │
|
|
│ ◉ Breathe In │ ← Expanding circle
|
|
│ (4 seconds) │
|
|
│ │
|
|
└─────────────────────┘
|
|
|
|
┌─────────────────────┐
|
|
│ │
|
|
│ ◉ Hold │ ← Static circle
|
|
│ (7 seconds) │
|
|
│ │
|
|
└─────────────────────┘
|
|
|
|
┌─────────────────────┐
|
|
│ │
|
|
│ ◉ Breathe Out │ ← Contracting circle
|
|
│ (8 seconds) │
|
|
│ │
|
|
└─────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 🎨 UI/UX Design
|
|
|
|
### Settings Panel Location
|
|
Add "Focus Mode" tab to existing Reading Settings panel:
|
|
|
|
```
|
|
┌─────────────────────────┐
|
|
│ Reading Settings │
|
|
├─────────────────────────┤
|
|
│ ▸ Typography │
|
|
│ ▸ Theme │
|
|
│ ▾ Focus Mode ← NEW │
|
|
│ ├─ Dimming │
|
|
│ ├─ Reading Line │
|
|
│ ├─ Spotlight │
|
|
│ ├─ Auto-Scroll │
|
|
│ ├─ Bionic Reading │
|
|
│ ├─ Sentence Mode │
|
|
│ └─ Breathing Reminders│
|
|
└─────────────────────────┘
|
|
```
|
|
|
|
### Controls UI
|
|
```typescript
|
|
// Focus Mode Quick Toggle Bar
|
|
<Box className="focus-mode-controls">
|
|
<IconButton
|
|
title="Enable Focus Mode"
|
|
onClick={toggleFocusMode}
|
|
>
|
|
<VisibilityIcon />
|
|
</IconButton>
|
|
|
|
{focusModeEnabled && (
|
|
<>
|
|
<Divider orientation="vertical" />
|
|
|
|
{/* Dimming Toggle */}
|
|
<ToggleButton value="dimming" selected={dimming.enabled}>
|
|
<OpacityIcon />
|
|
</ToggleButton>
|
|
|
|
{/* Reading Line Toggle */}
|
|
<ToggleButton value="line" selected={readingLine.enabled}>
|
|
<LinearScaleIcon />
|
|
</ToggleButton>
|
|
|
|
{/* Auto-Scroll */}
|
|
<ToggleButton value="scroll" selected={autoScroll.enabled}>
|
|
<PlayArrowIcon />
|
|
</ToggleButton>
|
|
|
|
{/* Speed Control (when auto-scroll active) */}
|
|
{autoScroll.enabled && (
|
|
<>
|
|
<IconButton size="small" onClick={decreaseSpeed}>
|
|
<RemoveIcon />
|
|
</IconButton>
|
|
<Typography variant="caption">
|
|
{autoScroll.wordsPerMinute} WPM
|
|
</Typography>
|
|
<IconButton size="small" onClick={increaseSpeed}>
|
|
<AddIcon />
|
|
</IconButton>
|
|
</>
|
|
)}
|
|
|
|
{/* Settings Menu */}
|
|
<IconButton onClick={openFocusSettings}>
|
|
<SettingsIcon />
|
|
</IconButton>
|
|
</>
|
|
)}
|
|
</Box>
|
|
```
|
|
|
|
### Mobile Considerations
|
|
- Floating toolbar (bottom of screen)
|
|
- Swipe gestures:
|
|
- Swipe up: Show settings
|
|
- Swipe down: Hide controls
|
|
- Pinch: Adjust dimming intensity
|
|
- Touch and hold: Quick access to reading line
|
|
|
|
---
|
|
|
|
## 🏗️ Technical Implementation
|
|
|
|
### File Structure
|
|
```
|
|
/components/bible-reader/
|
|
├── focus-mode/
|
|
│ ├── FocusModeProvider.tsx # Context provider
|
|
│ ├── FocusModeControls.tsx # Control toolbar
|
|
│ ├── FocusModeSettings.tsx # Settings panel
|
|
│ ├── DimmingOverlay.tsx # Dimming effect
|
|
│ ├── ReadingLine.tsx # Reading line/ruler
|
|
│ ├── SpotlightEffect.tsx # Spotlight highlighting
|
|
│ ├── AutoScroller.tsx # Auto-scroll logic
|
|
│ ├── BionicFormatter.tsx # Bionic reading formatter
|
|
│ ├── SentenceNavigator.tsx # Sentence-by-sentence
|
|
│ ├── BreathingReminder.tsx # Breathing reminders
|
|
│ └── hooks/
|
|
│ ├── useFocusMode.ts # Main hook
|
|
│ ├── useAutoScroll.ts # Auto-scroll hook
|
|
│ ├── useSentenceDetection.ts # Sentence parsing
|
|
│ └── useReadingPosition.ts # Track reading position
|
|
└── reader.tsx # Updated main reader
|
|
```
|
|
|
|
### Context Provider
|
|
```typescript
|
|
// FocusModeProvider.tsx
|
|
interface FocusModeContextType {
|
|
// State
|
|
enabled: boolean
|
|
dimming: DimmingConfig
|
|
readingLine: ReadingLineConfig
|
|
spotlight: SpotlightConfig
|
|
readingRuler: ReadingRulerConfig
|
|
autoScroll: AutoScrollConfig
|
|
bionicReading: BionicReadingConfig
|
|
sentenceMode: SentenceModeConfig
|
|
breathingReminder: BreathingReminderConfig
|
|
|
|
// Actions
|
|
toggleFocusMode: () => void
|
|
updateDimming: (config: Partial<DimmingConfig>) => void
|
|
updateReadingLine: (config: Partial<ReadingLineConfig>) => void
|
|
updateSpotlight: (config: Partial<SpotlightConfig>) => void
|
|
updateAutoScroll: (config: Partial<AutoScrollConfig>) => void
|
|
updateBionicReading: (config: Partial<BionicReadingConfig>) => void
|
|
updateSentenceMode: (config: Partial<SentenceModeConfig>) => void
|
|
updateBreathingReminder: (config: Partial<BreathingReminderConfig>) => void
|
|
|
|
// Reading position
|
|
currentElement: HTMLElement | null
|
|
setCurrentElement: (el: HTMLElement | null) => void
|
|
}
|
|
|
|
export const FocusModeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
const [enabled, setEnabled] = useState(false)
|
|
const [dimming, setDimming] = useState<DimmingConfig>(defaultDimmingConfig)
|
|
// ... other state
|
|
|
|
// Load from localStorage on mount
|
|
useEffect(() => {
|
|
const saved = localStorage.getItem('focusModeConfig')
|
|
if (saved) {
|
|
const config = JSON.parse(saved)
|
|
// Apply saved config
|
|
}
|
|
}, [])
|
|
|
|
// Save to localStorage on change
|
|
useEffect(() => {
|
|
localStorage.setItem('focusModeConfig', JSON.stringify({
|
|
enabled,
|
|
dimming,
|
|
readingLine,
|
|
spotlight,
|
|
// ... all configs
|
|
}))
|
|
}, [enabled, dimming, readingLine, spotlight, /* ... */])
|
|
|
|
return (
|
|
<FocusModeContext.Provider value={{ /* ... */ }}>
|
|
{children}
|
|
</FocusModeContext.Provider>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Dimming Implementation
|
|
```typescript
|
|
// DimmingOverlay.tsx
|
|
export const DimmingOverlay: React.FC = () => {
|
|
const { dimming, currentElement } = useFocusMode()
|
|
const [activeRangeIds, setActiveRangeIds] = useState<string[]>([])
|
|
|
|
useEffect(() => {
|
|
if (!dimming.enabled || !currentElement) return
|
|
|
|
// Determine which elements to keep visible
|
|
const getActiveElements = () => {
|
|
switch (dimming.mode) {
|
|
case 'paragraph':
|
|
return [currentElement.id]
|
|
|
|
case 'sentence':
|
|
// Find sentence boundaries
|
|
return [findCurrentSentence(currentElement)]
|
|
|
|
case 'verse':
|
|
return [currentElement.closest('.verse')?.id]
|
|
|
|
case 'custom':
|
|
return getCustomRange(
|
|
currentElement,
|
|
dimming.customLinesAbove,
|
|
dimming.customLinesBelow
|
|
)
|
|
}
|
|
}
|
|
|
|
setActiveRangeIds(getActiveElements())
|
|
}, [currentElement, dimming])
|
|
|
|
// Apply dimming via CSS classes
|
|
useEffect(() => {
|
|
const verses = document.querySelectorAll('.verse')
|
|
verses.forEach(verse => {
|
|
if (activeRangeIds.includes(verse.id)) {
|
|
verse.classList.remove('dimmed')
|
|
verse.classList.add('active')
|
|
} else {
|
|
verse.classList.add('dimmed')
|
|
verse.classList.remove('active')
|
|
}
|
|
})
|
|
}, [activeRangeIds])
|
|
|
|
return null // Pure effect component
|
|
}
|
|
|
|
// CSS
|
|
.verse {
|
|
transition: opacity 0.3s ease, filter 0.3s ease;
|
|
}
|
|
|
|
.verse.dimmed {
|
|
opacity: var(--dimming-opacity);
|
|
filter: blur(var(--dimming-blur));
|
|
}
|
|
|
|
.verse.active {
|
|
opacity: 1;
|
|
filter: none;
|
|
}
|
|
```
|
|
|
|
### Reading Line Implementation
|
|
```typescript
|
|
// ReadingLine.tsx
|
|
export const ReadingLine: React.FC = () => {
|
|
const { readingLine } = useFocusMode()
|
|
const [position, setPosition] = useState(0)
|
|
const lineRef = useRef<HTMLDivElement>(null)
|
|
|
|
useEffect(() => {
|
|
if (!readingLine.enabled) return
|
|
|
|
if (readingLine.type === 'static') {
|
|
// Fixed position on screen
|
|
setPosition(readingLine.position)
|
|
} else if (readingLine.type === 'following') {
|
|
// Follow scroll position
|
|
const handleScroll = () => {
|
|
const scrollY = window.scrollY
|
|
const viewportHeight = window.innerHeight
|
|
const linePosition = scrollY + (viewportHeight * readingLine.position / 100)
|
|
setPosition(linePosition)
|
|
}
|
|
|
|
window.addEventListener('scroll', handleScroll, { passive: true })
|
|
return () => window.removeEventListener('scroll', handleScroll)
|
|
}
|
|
}, [readingLine])
|
|
|
|
if (!readingLine.enabled) return null
|
|
|
|
const styles = {
|
|
position: readingLine.type === 'static' ? 'fixed' : 'absolute',
|
|
top: readingLine.type === 'static'
|
|
? `${readingLine.position}%`
|
|
: `${position}px`,
|
|
left: 0,
|
|
right: 0,
|
|
height: `${readingLine.thickness}px`,
|
|
backgroundColor: readingLine.color,
|
|
opacity: readingLine.opacity / 100,
|
|
zIndex: 1000,
|
|
pointerEvents: 'none',
|
|
transition: 'top 0.1s ease-out'
|
|
}
|
|
|
|
return <div ref={lineRef} style={styles} className="reading-line" />
|
|
}
|
|
```
|
|
|
|
### Auto-Scroll Implementation
|
|
```typescript
|
|
// useAutoScroll.ts
|
|
export const useAutoScroll = (config: AutoScrollConfig) => {
|
|
const [isActive, setIsActive] = useState(false)
|
|
const intervalRef = useRef<NodeJS.Timeout | null>(null)
|
|
|
|
const calculateScrollAmount = () => {
|
|
// Average word length: 5 characters
|
|
// Average line: 60-80 characters = 12-16 words
|
|
// Assume 14 words per line on average
|
|
const wordsPerLine = 14
|
|
const linesPerMinute = config.wordsPerMinute / wordsPerLine
|
|
const pixelsPerLine = 24 // line height
|
|
const pixelsPerMinute = linesPerMinute * pixelsPerLine
|
|
const pixelsPerInterval = pixelsPerMinute / (60000 / 50) // 50ms intervals
|
|
|
|
return pixelsPerInterval
|
|
}
|
|
|
|
const start = () => {
|
|
if (intervalRef.current) return
|
|
|
|
setIsActive(true)
|
|
const scrollAmount = calculateScrollAmount()
|
|
|
|
intervalRef.current = setInterval(() => {
|
|
window.scrollBy({
|
|
top: scrollAmount,
|
|
behavior: config.smoothness === 'linear' ? 'auto' : 'smooth'
|
|
})
|
|
|
|
// Check if at bottom
|
|
const atBottom = (window.innerHeight + window.scrollY) >= document.body.offsetHeight
|
|
if (atBottom && config.pauseAtChapterEnd) {
|
|
pause()
|
|
}
|
|
}, 50) // 20 FPS
|
|
}
|
|
|
|
const pause = () => {
|
|
if (intervalRef.current) {
|
|
clearInterval(intervalRef.current)
|
|
intervalRef.current = null
|
|
}
|
|
setIsActive(false)
|
|
}
|
|
|
|
const adjustSpeed = (delta: number) => {
|
|
const newSpeed = Math.max(10, Math.min(300, config.wordsPerMinute + delta))
|
|
// Update config through context
|
|
}
|
|
|
|
// Keyboard shortcuts
|
|
useEffect(() => {
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
if (e.code === 'Space' && config.enabled) {
|
|
e.preventDefault()
|
|
isActive ? pause() : start()
|
|
} else if (e.code === 'ArrowUp' && isActive) {
|
|
e.preventDefault()
|
|
adjustSpeed(10)
|
|
} else if (e.code === 'ArrowDown' && isActive) {
|
|
e.preventDefault()
|
|
adjustSpeed(-10)
|
|
} else if (e.code === 'Escape' && isActive) {
|
|
pause()
|
|
}
|
|
}
|
|
|
|
window.addEventListener('keydown', handleKeyDown)
|
|
return () => window.removeEventListener('keydown', handleKeyDown)
|
|
}, [isActive, config.enabled])
|
|
|
|
return { isActive, start, pause, adjustSpeed }
|
|
}
|
|
```
|
|
|
|
### Bionic Reading Implementation
|
|
```typescript
|
|
// BionicFormatter.tsx
|
|
export const formatTextBionic = (text: string, intensity: number): string => {
|
|
const words = text.split(/(\s+)/)
|
|
|
|
return words.map(word => {
|
|
// Skip whitespace and punctuation-only
|
|
if (/^\s+$/.test(word) || /^[^\w]+$/.test(word)) {
|
|
return word
|
|
}
|
|
|
|
// Extract leading punctuation
|
|
const leadMatch = word.match(/^([^\w]*)(.*)$/)
|
|
const leadPunct = leadMatch?.[1] || ''
|
|
const remaining = leadMatch?.[2] || word
|
|
|
|
// Extract trailing punctuation
|
|
const trailMatch = remaining.match(/^(.*?)([^\w]*)$/)
|
|
const core = trailMatch?.[1] || remaining
|
|
const trailPunct = trailMatch?.[2] || ''
|
|
|
|
if (!core) return word
|
|
|
|
// Calculate bold count based on intensity
|
|
const boldCount = Math.max(1, Math.ceil(core.length * (intensity / 100)))
|
|
const boldPart = core.substring(0, boldCount)
|
|
const regularPart = core.substring(boldCount)
|
|
|
|
return `${leadPunct}<strong>${boldPart}</strong>${regularPart}${trailPunct}`
|
|
}).join('')
|
|
}
|
|
|
|
// Apply to verse content
|
|
export const BionicFormatter: React.FC<{ children: string }> = ({ children }) => {
|
|
const { bionicReading } = useFocusMode()
|
|
|
|
if (!bionicReading.enabled) {
|
|
return <>{children}</>
|
|
}
|
|
|
|
const formatted = formatTextBionic(children, bionicReading.intensity)
|
|
|
|
return (
|
|
<span
|
|
dangerouslySetInnerHTML={{ __html: formatted }}
|
|
style={{ fontWeight: bionicReading.fontWeight }}
|
|
/>
|
|
)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 💾 Data Persistence
|
|
|
|
### LocalStorage Schema
|
|
```typescript
|
|
interface FocusModeStorage {
|
|
version: number // Schema version
|
|
enabled: boolean
|
|
lastUsed: string // ISO timestamp
|
|
|
|
dimming: DimmingConfig
|
|
readingLine: ReadingLineConfig
|
|
spotlight: SpotlightConfig
|
|
readingRuler: ReadingRulerConfig
|
|
autoScroll: AutoScrollConfig
|
|
bionicReading: BionicReadingConfig
|
|
sentenceMode: SentenceModeConfig
|
|
breathingReminder: BreathingReminderConfig
|
|
|
|
// Usage stats
|
|
stats: {
|
|
totalTimeUsed: number // seconds
|
|
sessionsCount: number
|
|
favoriteMode: string
|
|
}
|
|
}
|
|
|
|
// Key: 'bible-reader:focus-mode'
|
|
```
|
|
|
|
### User Preferences API (Optional)
|
|
For logged-in users, sync settings to database:
|
|
|
|
```typescript
|
|
// Add to UserPreference model in Prisma
|
|
model UserPreference {
|
|
// ... existing fields
|
|
focusModeConfig Json? // Store entire focus mode config
|
|
}
|
|
|
|
// API endpoint
|
|
POST /api/user/preferences/focus-mode
|
|
Body: FocusModeStorage
|
|
Response: { success: boolean }
|
|
```
|
|
|
|
---
|
|
|
|
## 🧪 Testing Strategy
|
|
|
|
### Unit Tests
|
|
```typescript
|
|
// __tests__/focus-mode/bionic-formatter.test.ts
|
|
describe('BionicFormatter', () => {
|
|
it('should bold first 50% of word by default', () => {
|
|
expect(formatTextBionic('reading', 50)).toBe('<strong>read</strong>ing')
|
|
})
|
|
|
|
it('should handle punctuation correctly', () => {
|
|
expect(formatTextBionic('Hello,', 50)).toBe('<strong>Hel</strong>lo,')
|
|
})
|
|
|
|
it('should preserve whitespace', () => {
|
|
expect(formatTextBionic('word word', 50)).toContain(' ')
|
|
})
|
|
})
|
|
|
|
// __tests__/focus-mode/auto-scroll.test.ts
|
|
describe('AutoScroller', () => {
|
|
it('should calculate correct scroll speed', () => {
|
|
const scroller = new AutoScroller({ wordsPerMinute: 200 })
|
|
expect(scroller.getScrollSpeed()).toBeCloseTo(5.71, 2)
|
|
})
|
|
|
|
it('should pause at chapter end', () => {
|
|
// Mock scroll position at bottom
|
|
// Verify pause is called
|
|
})
|
|
})
|
|
```
|
|
|
|
### Integration Tests
|
|
```typescript
|
|
// __tests__/focus-mode/integration.test.tsx
|
|
describe('Focus Mode Integration', () => {
|
|
it('should activate all features together', () => {
|
|
render(
|
|
<FocusModeProvider>
|
|
<BibleReader />
|
|
</FocusModeProvider>
|
|
)
|
|
|
|
// Enable focus mode
|
|
// Verify dimming is applied
|
|
// Verify reading line appears
|
|
// Verify spotlight works
|
|
})
|
|
})
|
|
```
|
|
|
|
### Manual Testing Checklist
|
|
- [ ] Dimming works in all 4 modes
|
|
- [ ] Reading line follows scroll smoothly
|
|
- [ ] Spotlight highlights correct element
|
|
- [ ] Auto-scroll maintains consistent speed
|
|
- [ ] Bionic reading formats correctly
|
|
- [ ] Sentence mode advances properly
|
|
- [ ] Breathing reminders appear on schedule
|
|
- [ ] Settings persist across sessions
|
|
- [ ] Mobile gestures work correctly
|
|
- [ ] Keyboard shortcuts function properly
|
|
- [ ] Performance is smooth (60 FPS)
|
|
- [ ] Works with all themes (light/dark/sepia)
|
|
- [ ] Accessibility preserved (screen readers)
|
|
|
|
---
|
|
|
|
## 📊 Success Metrics
|
|
|
|
### Quantitative Metrics
|
|
- **Engagement**: Average reading session time increases by 20%
|
|
- **Adoption**: 30% of users try focus mode within first month
|
|
- **Retention**: 60% of users who try it use it regularly
|
|
- **Performance**: No impact on page load time (<100ms overhead)
|
|
- **Error Rate**: <0.1% JavaScript errors related to focus mode
|
|
|
|
### Qualitative Metrics
|
|
- User feedback surveys (focus mode helpfulness rating)
|
|
- Support ticket volume (should not increase)
|
|
- Accessibility audit (maintain WCAG AAA)
|
|
|
|
### Analytics Events to Track
|
|
```typescript
|
|
// Analytics events
|
|
track('focus_mode_enabled', {
|
|
features_enabled: ['dimming', 'reading_line', 'auto_scroll'],
|
|
session_duration: 1234, // seconds
|
|
})
|
|
|
|
track('focus_mode_setting_changed', {
|
|
setting: 'dimming.mode',
|
|
old_value: 'paragraph',
|
|
new_value: 'sentence'
|
|
})
|
|
|
|
track('auto_scroll_used', {
|
|
duration: 600, // seconds
|
|
average_wpm: 180,
|
|
speed_adjustments: 3
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## 📅 Implementation Timeline
|
|
|
|
### Phase 1: Core Infrastructure (Day 1-2)
|
|
- [ ] Create focus mode context provider
|
|
- [ ] Set up file structure
|
|
- [ ] Implement settings panel UI
|
|
- [ ] Add localStorage persistence
|
|
- [ ] Implement quick toggle controls
|
|
|
|
**Deliverable:** Basic focus mode on/off toggle working
|
|
|
|
### Phase 2: Dimming & Spotlight (Day 2-3)
|
|
- [ ] Implement dimming overlay
|
|
- [ ] Add all 4 dimming modes
|
|
- [ ] Implement spotlight effects
|
|
- [ ] Add smooth transitions
|
|
- [ ] Test with different themes
|
|
|
|
**Deliverable:** Dimming and spotlight features complete
|
|
|
|
### Phase 3: Reading Line & Ruler (Day 3-4)
|
|
- [ ] Implement static reading line
|
|
- [ ] Implement following reading line
|
|
- [ ] Implement reading ruler overlay
|
|
- [ ] Add position controls
|
|
- [ ] Test scroll performance
|
|
|
|
**Deliverable:** Reading line and ruler working smoothly
|
|
|
|
### Phase 4: Auto-Scroll (Day 4-5)
|
|
- [ ] Implement auto-scroll logic
|
|
- [ ] Add speed controls
|
|
- [ ] Implement keyboard shortcuts
|
|
- [ ] Add progress indicator
|
|
- [ ] Test with different speeds
|
|
|
|
**Deliverable:** Auto-scroll feature complete
|
|
|
|
### Phase 5: Advanced Features (Day 5-6)
|
|
- [ ] Implement bionic reading formatter
|
|
- [ ] Implement sentence-by-sentence mode
|
|
- [ ] Add breathing reminders
|
|
- [ ] Test all features together
|
|
|
|
**Deliverable:** All features implemented
|
|
|
|
### Phase 6: Polish & Testing (Day 6-7)
|
|
- [ ] Mobile optimization
|
|
- [ ] Accessibility audit
|
|
- [ ] Performance optimization
|
|
- [ ] Bug fixes
|
|
- [ ] Documentation
|
|
- [ ] User testing
|
|
|
|
**Deliverable:** Production-ready feature
|
|
|
|
---
|
|
|
|
## 🚀 Deployment Plan
|
|
|
|
### Pre-Launch Checklist
|
|
- [ ] All unit tests passing
|
|
- [ ] Integration tests passing
|
|
- [ ] Manual QA complete
|
|
- [ ] Accessibility audit passed
|
|
- [ ] Performance benchmarks met
|
|
- [ ] Documentation updated
|
|
- [ ] Analytics events configured
|
|
|
|
### Rollout Strategy
|
|
1. **Beta Release** (Week 1)
|
|
- Deploy to 10% of users
|
|
- Monitor error rates and performance
|
|
- Collect initial feedback
|
|
|
|
2. **Staged Rollout** (Week 2)
|
|
- Increase to 50% of users
|
|
- Continue monitoring
|
|
- Make adjustments based on feedback
|
|
|
|
3. **Full Release** (Week 3)
|
|
- Deploy to 100% of users
|
|
- Announce feature in newsletter
|
|
- Create tutorial/help content
|
|
|
|
### Monitoring Plan
|
|
- Real-time error tracking (Sentry)
|
|
- Performance monitoring (Web Vitals)
|
|
- Usage analytics (custom events)
|
|
- User feedback collection (in-app survey)
|
|
|
|
---
|
|
|
|
## 📚 Documentation
|
|
|
|
### User Documentation
|
|
Create help articles:
|
|
1. "Introduction to Focus Mode"
|
|
2. "Using Dimming and Spotlight Effects"
|
|
3. "Reading Line and Auto-Scroll Guide"
|
|
4. "Bionic Reading: What and Why"
|
|
5. "Focus Mode Keyboard Shortcuts"
|
|
|
|
### Developer Documentation
|
|
Update technical docs:
|
|
- `docs/components/focus-mode.md`
|
|
- API reference for focus mode hooks
|
|
- Architecture decision records (ADR)
|
|
- Performance optimization notes
|
|
|
|
---
|
|
|
|
## 🔄 Future Enhancements
|
|
|
|
### Phase 2 Additions (Future)
|
|
- [ ] Custom color schemes for focus mode
|
|
- [ ] Focus mode profiles (save multiple configurations)
|
|
- [ ] AI-powered reading pace recommendations
|
|
- [ ] Eye-tracking integration (for supported devices)
|
|
- [ ] Collaborative focus sessions (sync with study groups)
|
|
- [ ] Reading comprehension quizzes (test focus effectiveness)
|
|
- [ ] Advanced analytics (heatmaps of focus patterns)
|
|
- [ ] Voice commands for hands-free control
|
|
- [ ] Integration with reading plans (auto-enable for daily readings)
|
|
|
|
---
|
|
|
|
## 📝 Notes & Considerations
|
|
|
|
### Performance Considerations
|
|
- Use `will-change` CSS property sparingly
|
|
- Implement throttling/debouncing for scroll events
|
|
- Use `requestAnimationFrame` for smooth animations
|
|
- Consider virtual scrolling for very long chapters
|
|
- Profile with Chrome DevTools Performance tab
|
|
|
|
### Accessibility Considerations
|
|
- Ensure focus mode doesn't interfere with screen readers
|
|
- Maintain keyboard navigation
|
|
- Provide alternatives for color-dependent features
|
|
- Test with screen readers (NVDA, JAWS, VoiceOver)
|
|
- Add ARIA labels where appropriate
|
|
|
|
### Browser Compatibility
|
|
- Test on Chrome, Firefox, Safari, Edge
|
|
- Provide fallbacks for unsupported features
|
|
- Consider mobile browser limitations
|
|
- Test on iOS Safari (webkit-specific issues)
|
|
|
|
### User Privacy
|
|
- Focus mode settings stored locally by default
|
|
- Option to sync to account (opt-in)
|
|
- No tracking of reading content
|
|
- Clear data on logout (if user prefers)
|
|
|
|
---
|
|
|
|
**Document Version:** 1.0
|
|
**Last Updated:** 2025-10-13
|
|
**Owner:** Development Team
|
|
**Status:** Ready for Implementation
|