feat: initial implementation of Focus RSVP speed reader
- Pure frontend SPA (Vite + React 19 + TypeScript + Tailwind CSS v4) - Spritz ORP engine with absolute center crosshair gaze fixation - Multilingual hybrid segmentation via Intl.Segmenter with punctuation attachment - Adaptive breathing rhythm delay algorithm for natural comprehension - IndexedDB local bookshelf, progress persistence, and client-side EPUB/TXT/MD parsing - Curated sci-fi, philosophy, and classic speech samples - Dark/OLED/Sepia/Slate/Nord themes and rich keyboard controls Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
74
src/core/orp.ts
Normal file
74
src/core/orp.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Optimal Recognition Point (ORP) Calculation for Spritz RSVP Reading.
|
||||
* Fixates the eye at ~30%-35% into the word where human ocular fixation naturally rests.
|
||||
*/
|
||||
|
||||
const CJK_REGEX = /[一-龥-ヿ가-]/;
|
||||
|
||||
export function isCJKChar(char: string): boolean {
|
||||
return CJK_REGEX.test(char);
|
||||
}
|
||||
|
||||
export function isCJKWord(word: string): boolean {
|
||||
return CJK_REGEX.test(word);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the 0-based index of the ORP character in cleanWord.
|
||||
*/
|
||||
export function calculateOrpIndex(cleanWord: string): number {
|
||||
const len = cleanWord.length;
|
||||
if (len <= 1) return 0;
|
||||
|
||||
if (isCJKWord(cleanWord)) {
|
||||
// CJK characters have high semantic density per glyph
|
||||
if (len === 2) return 0;
|
||||
if (len <= 4) return 1;
|
||||
if (len <= 6) return 2;
|
||||
return Math.min(len - 1, Math.floor((len - 1) * 0.35));
|
||||
}
|
||||
|
||||
// Standard Spritz ORP Table for alphabetic words
|
||||
if (len <= 5) return 1;
|
||||
if (len <= 9) return 2;
|
||||
if (len <= 13) return 3;
|
||||
return 4;
|
||||
}
|
||||
|
||||
export interface SplitWord {
|
||||
prefix: string;
|
||||
pivot: string;
|
||||
suffix: string;
|
||||
orpIndex: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a token into prefix, pivot (ORP character), and suffix.
|
||||
* Handles leading punctuation (e.g. "“", "《", "(") and trailing punctuation.
|
||||
*/
|
||||
export function splitOrp(
|
||||
coreWord: string,
|
||||
leadingPunct: string = '',
|
||||
trailingPunct: string = ''
|
||||
): SplitWord {
|
||||
if (!coreWord) {
|
||||
return {
|
||||
prefix: leadingPunct,
|
||||
pivot: '',
|
||||
suffix: trailingPunct,
|
||||
orpIndex: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const orpIndex = calculateOrpIndex(coreWord);
|
||||
const prefix = leadingPunct + coreWord.slice(0, orpIndex);
|
||||
const pivot = coreWord[orpIndex] || '';
|
||||
const suffix = coreWord.slice(orpIndex + 1) + trailingPunct;
|
||||
|
||||
return {
|
||||
prefix,
|
||||
pivot,
|
||||
suffix,
|
||||
orpIndex,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user