@ertekinno/human-like
Version:
A sophisticated React typewriter effect library with realistic human typing behavior, mobile/desktop keyboards, and comprehensive theming support
1 lines • 139 kB
Source Map (JSON)
{"version":3,"file":"useHumanLike-BFxeOC2S.cjs","sources":["../src/constants/index.ts","../src/keyboard/mobile-layouts.ts","../src/keyboard/desktop-layouts.ts","../src/keyboard/timing-profiles.ts","../src/keyboard/KeyboardAnalyzer.ts","../src/utils/TypingEngine.ts","../src/hooks/useHumanLike.ts"],"sourcesContent":["import type { HumanLikeConfig } from '../types';\n\nexport const TIMING_CONSTANTS = {\n BASE_SPEED: 80, // Average milliseconds per character\n SPEED_VARIATION: 40, // Random timing variation (±40ms)\n MIN_CHAR_DELAY: 25, // Minimum delay between characters\n \n // Punctuation and Structure\n SENTENCE_PAUSE: 500, // Pause after . ! ?\n COMMA_PAUSE: 200, // Pause after , ; :\n WORD_SPACE: 150, // Pause between words\n LINE_BREAK: 800, // Pause for new paragraphs\n \n // Mistake Handling\n REALIZATION_DELAY: 300, // Time to notice mistake\n CORRECTION_PAUSE: 250, // Pause before retyping\n BACKSPACE_SPEED: 60, // Speed of corrections\n \n // Human Behavior\n THINKING_PAUSE: 400, // Pause before complex words\n FATIGUE_INCREMENT: 0.5, // Gradual slowdown per 100 chars\n BURST_SPEED_MULTIPLIER: 0.6, // Speed multiplier during bursts\n CONCENTRATION_PAUSE: 800, // Duration of concentration lapses\n \n // Enhanced Realism\n SHIFT_HESITATION: 100, // Extra delay for shift key characters (increased)\n CAPS_LOCK_ON_DELAY: 150, // Delay when turning CAPS LOCK on\n CAPS_LOCK_OFF_DELAY: 100, // Delay when turning CAPS LOCK off\n CAPS_SEQUENCE_THRESHOLD: 3, // Min consecutive caps to trigger CAPS LOCK mode\n NUMBER_ROW_PENALTY: 35, // Extra delay for number characters\n SYMBOL_BASE_PENALTY: 25, // Base extra delay for symbols\n LOOK_AHEAD_CHANCE: 0.08, // 8% chance of look-ahead typing\n} as const;\n\nexport const BEHAVIOR_RATES = {\n MISTAKE_FREQUENCY: 0.03, // 3% base mistake rate\n CONCENTRATION_LAPSE: 0.03, // 3% random pause chance\n BURST_TYPING: 0.15, // 15% rapid sequence chance\n FATIGUE_FACTOR: 0.0001, // Gradual slowdown rate\n OVERCORRECTION_RATE: 0.2, // 20% chance of making mistake while correcting\n} as const;\n\n// Desktop QWERTY keyboard layout for adjacent key mistakes\nexport const DESKTOP_ADJACENT: Record<string, string[]> = {\n 'q': ['w', 'a', 's'],\n 'w': ['q', 'e', 'a', 's', 'd'],\n 'e': ['w', 'r', 's', 'd', 'f'],\n 'r': ['e', 't', 'd', 'f', 'g'],\n 't': ['r', 'y', 'f', 'g', 'h'],\n 'y': ['t', 'u', 'g', 'h', 'j'],\n 'u': ['y', 'i', 'h', 'j', 'k'],\n 'i': ['u', 'o', 'j', 'k', 'l'],\n 'o': ['i', 'p', 'k', 'l', ';'],\n 'p': ['o', '[', 'l', ';', \"'\"],\n '[': ['p', ']', ';', \"'\"],\n ']': ['[', '\\\\', \"'\"],\n \n 'a': ['q', 'w', 's', 'z'],\n 's': ['q', 'w', 'e', 'a', 'd', 'z', 'x'],\n 'd': ['w', 'e', 'r', 's', 'f', 'x', 'c'],\n 'f': ['e', 'r', 't', 'd', 'g', 'c', 'v'],\n 'g': ['r', 't', 'y', 'f', 'h', 'v', 'b'],\n 'h': ['t', 'y', 'u', 'g', 'j', 'b', 'n'],\n 'j': ['y', 'u', 'i', 'h', 'k', 'n', 'm'],\n 'k': ['u', 'i', 'o', 'j', 'l', 'm', ','],\n 'l': ['i', 'o', 'p', 'k', ';', ',', '.'],\n ';': ['o', 'p', '[', 'l', \"'\", '.', '/'],\n \"'\": ['p', '[', ']', ';', '/', '.'],\n \n 'z': ['a', 's', 'x'],\n 'x': ['z', 's', 'd', 'c'],\n 'c': ['x', 'd', 'f', 'v', ' '],\n 'v': ['c', 'f', 'g', 'b', ' '],\n 'b': ['v', 'g', 'h', 'n', ' '],\n 'n': ['b', 'h', 'j', 'm', ' '],\n 'm': ['n', 'j', 'k', ',', ' '],\n ',': ['m', 'k', 'l', '.'],\n '.': [',', 'l', ';', '/', \"'\"],\n '/': ['.', ';', \"'\"],\n \n ' ': ['c', 'v', 'b', 'n', 'm'], // Space bar adjacent to bottom row\n};\n\n// Mobile keyboard layout for adjacent key mistakes (touch-based errors)\nexport const MOBILE_ADJACENT: Record<string, string[]> = {\n // Top row - mobile keyboards often have tighter spacing\n 'q': ['w', 'a', 's'], // Less diagonal mistakes on mobile\n 'w': ['q', 'e', 's', 'a'], // More focused on immediate neighbors\n 'e': ['w', 'r', 'd', 's'], \n 'r': ['e', 't', 'f', 'd'],\n 't': ['r', 'y', 'g', 'f'],\n 'y': ['t', 'u', 'h', 'g'],\n 'u': ['y', 'i', 'j', 'h'],\n 'i': ['u', 'o', 'k', 'j'],\n 'o': ['i', 'p', 'l', 'k'],\n 'p': ['o', 'l'],\n\n // Middle row - more fat finger mistakes due to touch\n 'a': ['q', 'w', 's', 'z'], \n 's': ['a', 'd', 'w', 'e', 'z', 'x'], // High mistake area on mobile\n 'd': ['s', 'f', 'e', 'r', 'x', 'c'],\n 'f': ['d', 'g', 'r', 't', 'c', 'v'],\n 'g': ['f', 'h', 't', 'y', 'v', 'b'],\n 'h': ['g', 'j', 'y', 'u', 'b', 'n'],\n 'j': ['h', 'k', 'u', 'i', 'n', 'm'],\n 'k': ['j', 'l', 'i', 'o', 'm'],\n 'l': ['k', 'o', 'p'],\n\n // Bottom row - spacebar interference more common on mobile\n 'z': ['a', 's', 'x'], \n 'x': ['z', 'c', 's', 'd'],\n 'c': ['x', 'v', 'd', 'f', ' '], // Space bar mistakes more common\n 'v': ['c', 'b', 'f', 'g', ' '],\n 'b': ['v', 'n', 'g', 'h', ' '],\n 'n': ['b', 'm', 'h', 'j', ' '],\n 'm': ['n', 'j', 'k', ' '],\n\n // Space bar - different pattern on mobile due to wider space key\n ' ': ['c', 'v', 'b', 'n', 'm', 'x', 'z'], // Includes more bottom row keys\n \n // Numbers often above letters on mobile, but closer spacing\n '1': ['2', 'q', 'w'],\n '2': ['1', '3', 'q', 'w', 'e'],\n '3': ['2', '4', 'w', 'e', 'r'],\n '4': ['3', '5', 'e', 'r', 't'],\n '5': ['4', '6', 'r', 't', 'y'],\n '6': ['5', '7', 't', 'y', 'u'],\n '7': ['6', '8', 'y', 'u', 'i'],\n '8': ['7', '9', 'u', 'i', 'o'],\n '9': ['8', '0', 'i', 'o', 'p'],\n '0': ['9', 'o', 'p'],\n};\n\n// Legacy export for backward compatibility\nexport const QWERTY_ADJACENT = DESKTOP_ADJACENT;\n\n// Helper function to get appropriate adjacent keys based on keyboard mode\nexport function getAdjacentKeys(keyboardMode: 'mobile' | 'desktop'): Record<string, string[]> {\n return keyboardMode === 'mobile' ? MOBILE_ADJACENT : DESKTOP_ADJACENT;\n}\n\n// Common words that are typed faster due to muscle memory\nexport const COMMON_WORDS = new Set([\n 'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', 'had', 'her', 'was', 'one',\n 'our', 'out', 'day', 'get', 'has', 'him', 'his', 'how', 'man', 'new', 'now', 'old', 'see',\n 'two', 'way', 'who', 'boy', 'did', 'its', 'let', 'put', 'say', 'she', 'too', 'use', 'that',\n 'with', 'have', 'this', 'will', 'your', 'from', 'they', 'know', 'want', 'been', 'good',\n 'much', 'some', 'time', 'very', 'when', 'come', 'here', 'just', 'like', 'long', 'make',\n 'many', 'over', 'such', 'take', 'than', 'them', 'well', 'were'\n]);\n\n// Common typos that humans make\nexport const COMMON_TYPOS: Record<string, string> = {\n 'the': 'teh',\n 'and': 'adn',\n 'for': 'fro',\n 'you': 'yuo',\n 'that': 'taht',\n 'this': 'tihs',\n 'with': 'wiht',\n 'have': 'ahve',\n 'from': 'form',\n 'they': 'thye',\n 'been': 'bene',\n 'than': 'htan',\n 'what': 'waht',\n 'your': 'yuor',\n 'when': 'wehn',\n 'there': 'tehre',\n 'their': 'thier',\n 'would': 'woudl',\n 'could': 'coudl',\n 'should': 'shoudl',\n 'through': 'trhough',\n 'because': 'becasue',\n 'before': 'beofre',\n 'after': 'aftre',\n 'where': 'whree',\n 'which': 'whihc',\n 'between': 'betwene',\n 'different': 'differnet',\n 'important': 'importnat',\n 'example': 'exmaple',\n 'without': 'withuot',\n 'another': 'antoher',\n 'development': 'developement',\n 'environment': 'enviroment',\n 'government': 'goverment',\n 'management': 'managment',\n 'information': 'infromation',\n 'available': 'availabe',\n 'business': 'buisness',\n 'complete': 'compelte',\n 'language': 'langauge',\n 'experience': 'experiance',\n 'position': 'postion',\n 'question': 'quesiton',\n 'remember': 'remeber',\n 'separate': 'seperate',\n 'something': 'somehting',\n 'together': 'togehter',\n 'understand': 'udnerstand'\n};\n\n// Special characters that typically take longer to type\nexport const SPECIAL_CHARS = new Set([\n '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '+', '=',\n '[', ']', '{', '}', '\\\\', '|', ';', ':', \"'\", '\"', ',', '.', '<', '>',\n '/', '?', '`', '~'\n]);\n\n// Characters requiring shift key (capital letters and symbols)\nexport const SHIFT_CHARS = new Set([\n 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',\n 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',\n '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '{', '}',\n '|', ':', '\"', '<', '>', '?', '~'\n]);\n\n// Number row characters (inherently more difficult)\nexport const NUMBER_CHARS = new Set(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']);\n\n// Symbol characters with their complexity levels (1 = simple, 3 = complex)\nexport const SYMBOL_COMPLEXITY: Record<string, number> = {\n // Simple punctuation\n '.': 1, ',': 1, '?': 1, '!': 1, ';': 1, ':': 1, \"'\": 1, '\"': 1,\n \n // Medium complexity\n '-': 2, '_': 2, '(': 2, ')': 2, '[': 2, ']': 2, '/': 2,\n \n // Complex symbols requiring precise finger movement\n '@': 3, '#': 3, '$': 3, '%': 3, '^': 3, '&': 3, '*': 3,\n '+': 3, '=': 3, '{': 3, '}': 3, '\\\\': 3, '|': 3, '`': 3, '~': 3,\n '<': 3, '>': 3\n};\n\n// Punctuation that affects timing\nexport const SENTENCE_ENDINGS = new Set(['.', '!', '?']);\nexport const CLAUSE_SEPARATORS = new Set([',', ';', ':']);\n\n// Line break characters\nexport const LINE_BREAK_CHARS = new Set(['\\n', '\\r\\n', '\\r']);\n\n// Default configuration\nexport const DEFAULT_CONFIG: HumanLikeConfig = {\n speed: TIMING_CONSTANTS.BASE_SPEED,\n speedVariation: TIMING_CONSTANTS.SPEED_VARIATION,\n mistakeFrequency: BEHAVIOR_RATES.MISTAKE_FREQUENCY,\n mistakeTypes: {\n adjacent: true,\n random: false,\n doubleChar: true,\n commonTypos: true,\n },\n fatigueEffect: true,\n concentrationLapses: true,\n overcorrection: true,\n debug: false,\n sentencePause: TIMING_CONSTANTS.SENTENCE_PAUSE,\n wordPause: TIMING_CONSTANTS.WORD_SPACE,\n thinkingPause: TIMING_CONSTANTS.THINKING_PAUSE,\n minCharDelay: TIMING_CONSTANTS.MIN_CHAR_DELAY,\n backspaceSpeed: TIMING_CONSTANTS.BACKSPACE_SPEED,\n realizationDelay: TIMING_CONSTANTS.REALIZATION_DELAY,\n correctionPause: TIMING_CONSTANTS.CORRECTION_PAUSE,\n // Keyboard simulation defaults\n keyboardMode: 'mobile'\n};\n\n// Letter frequency in English (affects typing speed)\nexport const LETTER_FREQUENCY: Record<string, number> = {\n 'e': 12.7, 't': 9.1, 'a': 8.2, 'o': 7.5, 'i': 7.0, 'n': 6.7, 's': 6.3, 'h': 6.1,\n 'r': 6.0, 'd': 4.3, 'l': 4.0, 'c': 2.8, 'u': 2.8, 'm': 2.4, 'w': 2.4, 'f': 2.2,\n 'g': 2.0, 'y': 2.0, 'p': 1.9, 'b': 1.3, 'v': 1.0, 'k': 0.8, 'j': 0.15, 'x': 0.15,\n 'q': 0.10, 'z': 0.07\n};\n\n// Vowels are typically typed faster\nexport const VOWELS = new Set(['a', 'e', 'i', 'o', 'u']);\n\n// Hand mapping for alternating hand sequences (affects rhythm)\nexport const LEFT_HAND_KEYS = new Set([\n 'q', 'w', 'e', 'r', 't', 'a', 's', 'd', 'f', 'g', 'z', 'x', 'c', 'v', 'b',\n '1', '2', '3', '4', '5', '!', '@', '#', '$', '%'\n]);\n\nexport const RIGHT_HAND_KEYS = new Set([\n 'y', 'u', 'i', 'o', 'p', 'h', 'j', 'k', 'l', ';', 'n', 'm', ',', '.', '/',\n '6', '7', '8', '9', '0', '^', '&', '*', '(', ')', '[', ']', \"'\", '\"'\n]);","import type { KeyboardLayoutDefinition } from './types';\n\n/**\n * Standard mobile keyboard layout (iOS/Android style)\n * Represents the most common mobile keyboard behavior\n */\nexport const MOBILE_LAYOUT: KeyboardLayoutDefinition = {\n views: {\n letters: [\n 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p',\n 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l',\n 'z', 'x', 'c', 'v', 'b', 'n', 'm'\n ],\n numbers: [\n '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',\n '-', '/', ':', ';', '(', ')', '$', '&', '@', '\"',\n '.', ',', '?', '!', \"'\", '[', ']', '{', '}', '#',\n '%', '^', '*', '+', '=', '_', '\\\\', '|', '~', '<',\n '>', '€', '£', '¥', '•', '...'\n ],\n symbols: [\n '[', ']', '{', '}', '#', '%', '^', '*', '+', '=',\n '_', '\\\\', '|', '~', '<', '>', '€', '£', '¥', '•',\n '.', ',', '?', '!', \"'\", '\"', '/', ':', ';', '(',\n ')', '$', '&', '@', '`', '§', '¿', '¡', '«', '»',\n '°', '†', '‡', '…', '‰', '′', '″', '‹', '›'\n ],\n emoji: [\n '😀', '😃', '😄', '😁', '😆', '😅', '🤣', '😂', '🙂', '🙃',\n '😉', '😊', '😇', '🥰', '😍', '🤩', '😘', '😗', '☺', '😚',\n '😙', '🥲', '😋', '😛', '😜', '🤪', '😝', '🤑', '🤗', '🤭'\n ]\n },\n \n viewSwitchers: {\n toNumbers: '123',\n toSymbols: '#+=', \n toLetters: 'ABC',\n toEmoji: '😀'\n },\n \n modifiers: {\n shift: '⇧',\n caps: 'CAPS',\n space: 'space',\n enter: 'return',\n backspace: '⌫'\n },\n \n keyDurations: {\n letter: 80, // Fast letter typing\n number: 90, // Slightly slower for numbers\n symbol: 100, // Slower for symbols\n modifier: 120, // Shift/caps key press\n viewSwitch: 110, // View switching (123, ABC, etc.)\n space: 75, // Space bar\n enter: 90, // Return key\n backspace: 110 // Backspace key\n }\n};\n\n/**\n * iOS-specific layout variations\n */\nexport const IOS_LAYOUT: KeyboardLayoutDefinition = {\n ...MOBILE_LAYOUT,\n \n viewSwitchers: {\n toNumbers: '.?123',\n toSymbols: '#+=',\n toLetters: 'ABC',\n toEmoji: '🙂'\n },\n \n modifiers: {\n shift: '⇧',\n caps: 'caps lock',\n space: 'space',\n enter: 'return',\n backspace: '⌫'\n }\n};\n\n/**\n * Android-specific layout variations\n */\nexport const ANDROID_LAYOUT: KeyboardLayoutDefinition = {\n ...MOBILE_LAYOUT,\n \n viewSwitchers: {\n toNumbers: '?123',\n toSymbols: '=\\\\<',\n toLetters: 'ABC',\n toEmoji: '😀'\n },\n \n modifiers: {\n shift: '⇧',\n caps: 'CAPS',\n space: 'space',\n enter: 'return',\n backspace: '⌫'\n }\n};\n\n/**\n * Map character to the view it belongs to\n */\nexport const MOBILE_CHARACTER_TO_VIEW: Record<string, 'letters' | 'numbers' | 'symbols'> = {\n // Letters (lowercase and uppercase)\n ...Object.fromEntries('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map(c => [c, 'letters' as const])),\n \n // Numbers and basic symbols on number view\n '1': 'numbers', '2': 'numbers', '3': 'numbers', '4': 'numbers', '5': 'numbers',\n '6': 'numbers', '7': 'numbers', '8': 'numbers', '9': 'numbers', '0': 'numbers',\n '-': 'numbers', '/': 'numbers', ':': 'numbers', ';': 'numbers', '(': 'numbers',\n ')': 'numbers', '$': 'numbers', '&': 'numbers', '@': 'numbers', '\"': 'numbers',\n '.': 'numbers', ',': 'numbers', '?': 'numbers', '!': 'numbers', \"'\": 'numbers',\n \n // Complex symbols on symbol view\n '[': 'symbols', ']': 'symbols', '{': 'symbols', '}': 'symbols', '#': 'symbols',\n '%': 'symbols', '^': 'symbols', '*': 'symbols', '+': 'symbols', '=': 'symbols',\n '_': 'symbols', '\\\\': 'symbols', '|': 'symbols', '~': 'symbols', '<': 'symbols',\n '>': 'symbols', '€': 'symbols', '£': 'symbols', '¥': 'symbols', '•': 'symbols',\n '`': 'symbols', '§': 'symbols', '¿': 'symbols', '¡': 'symbols', '«': 'symbols',\n '»': 'symbols', '°': 'symbols', '†': 'symbols', '‡': 'symbols', '…': 'symbols',\n '‰': 'symbols', '′': 'symbols', '″': 'symbols', '‹': 'symbols', '›': 'symbols'\n};\n\n/**\n * Special mobile keyboard shortcuts and gestures\n */\nexport const MOBILE_SHORTCUTS = {\n // Double space = period + space (common mobile behavior)\n DOUBLE_SPACE_PERIOD: true,\n \n // Long press behaviors\n LONG_PRESS_ACCENTS: {\n 'a': ['à', 'á', 'â', 'ä', 'æ', 'ã', 'å', 'ā'],\n 'e': ['è', 'é', 'ê', 'ë', 'ē', 'ė', 'ę'],\n 'i': ['î', 'ï', 'í', 'ī', 'į', 'ì'],\n 'o': ['ô', 'ö', 'ò', 'ó', 'œ', 'ø', 'ō', 'õ'],\n 'u': ['û', 'ü', 'ù', 'ú', 'ū'],\n 'c': ['ç', 'ć', 'č'],\n 'n': ['ñ', 'ń'],\n 's': ['ś', 'š'],\n 'z': ['ž', 'ź', 'ż']\n },\n \n // Auto-correction common patterns\n AUTO_CAPITALIZE_AFTER: ['.', '!', '?', '\\n'],\n AUTO_SPACE_AFTER: ['.', ',', '!', '?', ':', ';']\n};","import type { KeyboardLayoutDefinition } from './types';\n\n/**\n * Standard US QWERTY desktop keyboard layout\n */\nexport const DESKTOP_QWERTY_LAYOUT: KeyboardLayoutDefinition = {\n views: {\n letters: [\n 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p',\n 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l',\n 'z', 'x', 'c', 'v', 'b', 'n', 'm'\n ],\n numbers: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],\n symbols: [\n '`', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '+', '=',\n '[', ']', '{', '}', '\\\\', '|', ';', ':', \"'\", '\"', ',', '.', '<', '>', '/', '?'\n ]\n },\n \n viewSwitchers: {\n toNumbers: 'numbers', // No view switching on desktop\n toSymbols: 'symbols', // Symbols accessed via shift\n toLetters: 'letters'\n },\n \n modifiers: {\n shift: 'shift',\n caps: 'caps lock',\n space: 'space',\n enter: 'enter',\n backspace: 'backspace'\n },\n \n keyDurations: {\n letter: 70, // Fast letter typing on desktop\n number: 85, // Numbers require reaching up\n symbol: 95, // Symbols often need shift\n modifier: 90, // Modifier key press\n viewSwitch: 0, // No view switching on desktop\n space: 60, // Space bar\n enter: 80, // Enter key\n backspace: 100 // Backspace key\n }\n};\n\n/**\n * Desktop key mappings - what physical key produces each character\n * Format: [key, requiresShift]\n */\nexport const DESKTOP_KEY_MAPPING: Record<string, [string, boolean]> = {\n // Letters (lowercase)\n 'a': ['a', false], 'b': ['b', false], 'c': ['c', false], 'd': ['d', false], 'e': ['e', false],\n 'f': ['f', false], 'g': ['g', false], 'h': ['h', false], 'i': ['i', false], 'j': ['j', false],\n 'k': ['k', false], 'l': ['l', false], 'm': ['m', false], 'n': ['n', false], 'o': ['o', false],\n 'p': ['p', false], 'q': ['q', false], 'r': ['r', false], 's': ['s', false], 't': ['t', false],\n 'u': ['u', false], 'v': ['v', false], 'w': ['w', false], 'x': ['x', false], 'y': ['y', false],\n 'z': ['z', false],\n \n // Letters (uppercase) - same key + shift\n 'A': ['a', true], 'B': ['b', true], 'C': ['c', true], 'D': ['d', true], 'E': ['e', true],\n 'F': ['f', true], 'G': ['g', true], 'H': ['h', true], 'I': ['i', true], 'J': ['j', true],\n 'K': ['k', true], 'L': ['l', true], 'M': ['m', true], 'N': ['n', true], 'O': ['o', true],\n 'P': ['p', true], 'Q': ['q', true], 'R': ['r', true], 'S': ['s', true], 'T': ['t', true],\n 'U': ['u', true], 'V': ['v', true], 'W': ['w', true], 'X': ['x', true], 'Y': ['y', true],\n 'Z': ['z', true],\n \n // Numbers (no shift)\n '1': ['1', false], '2': ['2', false], '3': ['3', false], '4': ['4', false], '5': ['5', false],\n '6': ['6', false], '7': ['7', false], '8': ['8', false], '9': ['9', false], '0': ['0', false],\n \n // Numbers with shift (top row symbols)\n '!': ['1', true], '@': ['2', true], '#': ['3', true], '$': ['4', true], '%': ['5', true],\n '^': ['6', true], '&': ['7', true], '*': ['8', true], '(': ['9', true], ')': ['0', true],\n \n // Other keys without shift\n '`': ['`', false], '-': ['-', false], '=': ['=', false], '[': ['[', false], ']': [']', false],\n '\\\\': ['\\\\', false], ';': [';', false], \"'\": [\"'\", false], ',': [',', false], '.': ['.', false],\n '/': ['/', false], ' ': ['space', false], '\\n': ['enter', false], '\\t': ['tab', false],\n \n // Other keys with shift\n '~': ['`', true], '_': ['-', true], '+': ['=', true], '{': ['[', true], '}': [']', true],\n '|': ['\\\\', true], ':': [';', true], '\"': [\"'\", true], '<': [',', true], '>': ['.', true],\n '?': ['/', true]\n};\n\n/**\n * Desktop keyboard physical layout for visual representation\n */\nexport const DESKTOP_PHYSICAL_LAYOUT = {\n rows: [\n // Number row\n {\n keys: [\n { key: '`', shift: '~' },\n { key: '1', shift: '!' },\n { key: '2', shift: '@' },\n { key: '3', shift: '#' },\n { key: '4', shift: '$' },\n { key: '5', shift: '%' },\n { key: '6', shift: '^' },\n { key: '7', shift: '&' },\n { key: '8', shift: '*' },\n { key: '9', shift: '(' },\n { key: '0', shift: ')' },\n { key: '-', shift: '_' },\n { key: '=', shift: '+' }\n ]\n },\n \n // QWERTY row\n {\n keys: [\n { key: 'q' }, { key: 'w' }, { key: 'e' }, { key: 'r' }, { key: 't' },\n { key: 'y' }, { key: 'u' }, { key: 'i' }, { key: 'o' }, { key: 'p' },\n { key: '[', shift: '{' },\n { key: ']', shift: '}' },\n { key: '\\\\', shift: '|' }\n ]\n },\n \n // ASDF row (home row)\n {\n keys: [\n { key: 'a' }, { key: 's' }, { key: 'd' }, { key: 'f' }, { key: 'g' },\n { key: 'h' }, { key: 'j' }, { key: 'k' }, { key: 'l' },\n { key: ';', shift: ':' },\n { key: \"'\", shift: '\"' }\n ]\n },\n \n // ZXCV row\n {\n keys: [\n { key: 'z' }, { key: 'x' }, { key: 'c' }, { key: 'v' }, { key: 'b' },\n { key: 'n' }, { key: 'm' },\n { key: ',', shift: '<' },\n { key: '.', shift: '>' },\n { key: '/', shift: '?' }\n ]\n },\n \n // Space row\n {\n keys: [\n { key: 'space', width: 'wide' }\n ]\n }\n ],\n \n modifiers: {\n left: ['shift', 'ctrl', 'alt', 'cmd'],\n right: ['shift', 'ctrl', 'alt', 'cmd']\n }\n};\n\n/**\n * Common desktop keyboard shortcuts and behaviors\n */\nexport const DESKTOP_SHORTCUTS = {\n // Copy/paste/cut\n COPY: ['cmd+c', 'ctrl+c'],\n PASTE: ['cmd+v', 'ctrl+v'],\n CUT: ['cmd+x', 'ctrl+x'],\n \n // Selection\n SELECT_ALL: ['cmd+a', 'ctrl+a'],\n SELECT_WORD: ['shift+alt+arrow', 'shift+ctrl+arrow'],\n \n // Navigation\n HOME: ['cmd+left', 'home'],\n END: ['cmd+right', 'end'],\n WORD_LEFT: ['alt+left', 'ctrl+left'],\n WORD_RIGHT: ['alt+right', 'ctrl+right'],\n \n // Deletion\n DELETE_WORD_LEFT: ['alt+backspace', 'ctrl+backspace'],\n DELETE_WORD_RIGHT: ['alt+delete', 'ctrl+delete'],\n DELETE_LINE: ['cmd+backspace', 'ctrl+u'],\n \n // Auto-behaviors\n AUTO_CAPITALIZE_SENTENCES: true,\n SMART_QUOTES: false, // Usually disabled for coding\n AUTO_CORRECTION: false // Usually disabled for coding\n};","import type { KeyboardLayoutDefinition } from './types';\n\n/**\n * Platform-specific timing profiles for more realistic keyboard simulation\n */\n\nexport interface TimingProfile {\n name: string;\n description: string;\n baseMultiplier: number;\n keyDurations: {\n letter: number;\n number: number;\n symbol: number;\n modifier: number;\n viewSwitch: number;\n space: number;\n enter: number;\n backspace: number;\n };\n // Additional behavioral modifiers\n viewSwitchDelay: number; // Extra delay for view switching\n consecutiveKeyBonus: number; // Speed bonus for typing same-hand sequences\n complexSymbolPenalty: number; // Extra delay for complex symbols\n capsLockTransitionDelay: number; // Extra delay for caps lock on/off\n}\n\n/**\n * Mobile device timing profiles\n */\nexport const MOBILE_TIMING_PROFILES: Record<string, TimingProfile> = {\n // Standard smartphone typing\n MOBILE_CASUAL: {\n name: 'Mobile Casual',\n description: 'Average smartphone user, thumb typing',\n baseMultiplier: 1.0,\n keyDurations: {\n letter: 120,\n number: 140,\n symbol: 160,\n modifier: 180,\n viewSwitch: 150,\n space: 100,\n enter: 130,\n backspace: 150\n },\n viewSwitchDelay: 50,\n consecutiveKeyBonus: 0.9,\n complexSymbolPenalty: 40,\n capsLockTransitionDelay: 60\n },\n\n // Fast mobile typist\n MOBILE_FAST: {\n name: 'Mobile Fast',\n description: 'Experienced mobile user, swipe/predictive text habits',\n baseMultiplier: 0.7,\n keyDurations: {\n letter: 80,\n number: 95,\n symbol: 110,\n modifier: 120,\n viewSwitch: 100,\n space: 70,\n enter: 90,\n backspace: 110\n },\n viewSwitchDelay: 30,\n consecutiveKeyBonus: 0.85,\n complexSymbolPenalty: 25,\n capsLockTransitionDelay: 40\n },\n\n // Slow/careful mobile typing\n MOBILE_CAREFUL: {\n name: 'Mobile Careful',\n description: 'Deliberate mobile typing, hunt-and-peck style',\n baseMultiplier: 1.5,\n keyDurations: {\n letter: 180,\n number: 220,\n symbol: 280,\n modifier: 250,\n viewSwitch: 200,\n space: 150,\n enter: 180,\n backspace: 220\n },\n viewSwitchDelay: 80,\n consecutiveKeyBonus: 0.95,\n complexSymbolPenalty: 60,\n capsLockTransitionDelay: 100\n },\n\n // Tablet typing (larger screen, different hand position)\n TABLET: {\n name: 'Tablet',\n description: 'Tablet device, often landscape mode with more fingers',\n baseMultiplier: 0.8,\n keyDurations: {\n letter: 90,\n number: 110,\n symbol: 130,\n modifier: 140,\n viewSwitch: 120,\n space: 80,\n enter: 100,\n backspace: 130\n },\n viewSwitchDelay: 40,\n consecutiveKeyBonus: 0.8, // More fingers = better hand alternation\n complexSymbolPenalty: 30,\n capsLockTransitionDelay: 50\n }\n};\n\n/**\n * Desktop timing profiles\n */\nexport const DESKTOP_TIMING_PROFILES: Record<string, TimingProfile> = {\n // Standard desktop typing\n DESKTOP_AVERAGE: {\n name: 'Desktop Average',\n description: 'Average desktop user, ~40 WPM',\n baseMultiplier: 1.0,\n keyDurations: {\n letter: 80,\n number: 100,\n symbol: 120,\n modifier: 90,\n viewSwitch: 0, // No view switching on desktop\n space: 70,\n enter: 90,\n backspace: 100\n },\n viewSwitchDelay: 0,\n consecutiveKeyBonus: 0.85, // Good hand alternation on QWERTY\n complexSymbolPenalty: 30,\n capsLockTransitionDelay: 50\n },\n\n // Fast desktop typist\n DESKTOP_FAST: {\n name: 'Desktop Fast',\n description: 'Experienced typist, ~70+ WPM, touch typing',\n baseMultiplier: 0.6,\n keyDurations: {\n letter: 50,\n number: 65,\n symbol: 80,\n modifier: 60,\n viewSwitch: 0,\n space: 45,\n enter: 60,\n backspace: 70\n },\n viewSwitchDelay: 0,\n consecutiveKeyBonus: 0.8,\n complexSymbolPenalty: 15,\n capsLockTransitionDelay: 25\n },\n\n // Programming-focused typing\n DESKTOP_PROGRAMMER: {\n name: 'Desktop Programmer',\n description: 'Developer typing, frequent symbols and modifiers',\n baseMultiplier: 0.7,\n keyDurations: {\n letter: 60,\n number: 70,\n symbol: 75, // Programmers are fast with symbols\n modifier: 65,\n viewSwitch: 0,\n space: 50,\n enter: 70,\n backspace: 80\n },\n viewSwitchDelay: 0,\n consecutiveKeyBonus: 0.8,\n complexSymbolPenalty: 10, // Less penalty for complex symbols\n capsLockTransitionDelay: 30\n },\n\n // Hunt-and-peck desktop typing\n DESKTOP_SLOW: {\n name: 'Desktop Slow',\n description: 'Hunt-and-peck typing, looking at keyboard',\n baseMultiplier: 2.0,\n keyDurations: {\n letter: 160,\n number: 200,\n symbol: 250,\n modifier: 180,\n viewSwitch: 0,\n space: 140,\n enter: 160,\n backspace: 180\n },\n viewSwitchDelay: 0,\n consecutiveKeyBonus: 0.95, // Less benefit from hand alternation\n complexSymbolPenalty: 80,\n capsLockTransitionDelay: 120\n },\n\n // Gaming keyboard (mechanical, fast response)\n DESKTOP_GAMING: {\n name: 'Desktop Gaming',\n description: 'Mechanical keyboard, gaming-optimized typing',\n baseMultiplier: 0.5,\n keyDurations: {\n letter: 40,\n number: 50,\n symbol: 60,\n modifier: 45,\n viewSwitch: 0,\n space: 35,\n enter: 50,\n backspace: 60\n },\n viewSwitchDelay: 0,\n consecutiveKeyBonus: 0.75,\n complexSymbolPenalty: 10,\n capsLockTransitionDelay: 20\n }\n};\n\n/**\n * Auto-detect appropriate timing profile based on config\n */\nexport function getDefaultTimingProfile(keyboardMode: 'mobile' | 'desktop', userHint?: string): TimingProfile {\n if (keyboardMode === 'mobile') {\n if (userHint === 'fast') return MOBILE_TIMING_PROFILES.MOBILE_FAST;\n if (userHint === 'slow' || userHint === 'careful') return MOBILE_TIMING_PROFILES.MOBILE_CAREFUL;\n if (userHint === 'tablet') return MOBILE_TIMING_PROFILES.TABLET;\n return MOBILE_TIMING_PROFILES.MOBILE_CASUAL; // Default\n } else {\n if (userHint === 'fast') return DESKTOP_TIMING_PROFILES.DESKTOP_FAST;\n if (userHint === 'slow') return DESKTOP_TIMING_PROFILES.DESKTOP_SLOW;\n if (userHint === 'programmer' || userHint === 'developer') return DESKTOP_TIMING_PROFILES.DESKTOP_PROGRAMMER;\n if (userHint === 'gaming') return DESKTOP_TIMING_PROFILES.DESKTOP_GAMING;\n return DESKTOP_TIMING_PROFILES.DESKTOP_AVERAGE; // Default\n }\n}\n\n/**\n * Apply timing profile to keyboard layout\n */\nexport function applyTimingProfile(layout: KeyboardLayoutDefinition, profile: TimingProfile): KeyboardLayoutDefinition {\n return {\n ...layout,\n keyDurations: {\n ...profile.keyDurations\n }\n };\n}\n\n/**\n * Calculate dynamic timing adjustments based on context\n */\nexport function calculateContextualTiming(\n baseDelay: number, \n profile: TimingProfile, \n context: {\n isConsecutiveSameHand?: boolean;\n isComplexSymbol?: boolean;\n isViewSwitch?: boolean;\n isCapsLockTransition?: boolean;\n }\n): number {\n let adjustedDelay = baseDelay * profile.baseMultiplier;\n\n // Apply profile-specific adjustments\n if (context.isConsecutiveSameHand) {\n adjustedDelay *= profile.consecutiveKeyBonus;\n }\n\n if (context.isComplexSymbol) {\n adjustedDelay += profile.complexSymbolPenalty;\n }\n\n if (context.isViewSwitch) {\n adjustedDelay += profile.viewSwitchDelay;\n }\n\n if (context.isCapsLockTransition) {\n adjustedDelay += profile.capsLockTransitionDelay;\n }\n\n return adjustedDelay;\n}","import type { \n KeyInfo, \n KeySequence, \n KeyboardState, \n KeyboardView,\n KeyboardAnalyzerConfig,\n KeyboardLayoutDefinition \n} from './types';\nimport { MOBILE_LAYOUT, MOBILE_CHARACTER_TO_VIEW } from './mobile-layouts';\nimport { DESKTOP_QWERTY_LAYOUT, DESKTOP_KEY_MAPPING } from './desktop-layouts';\nimport { getDefaultTimingProfile, applyTimingProfile, calculateContextualTiming, type TimingProfile } from './timing-profiles';\n\n/**\n * Analyzes characters and converts them to realistic keyboard key sequences\n * Handles both mobile and desktop keyboard behaviors\n */\nexport class KeyboardAnalyzer {\n private config: KeyboardAnalyzerConfig;\n private layout: KeyboardLayoutDefinition;\n private state: KeyboardState;\n private timingProfile: TimingProfile;\n\n constructor(config: Partial<KeyboardAnalyzerConfig> = {}) {\n this.config = {\n keyboardMode: 'mobile',\n capsLockThreshold: 3,\n useNaturalTiming: true,\n debug: false,\n ...config\n };\n\n // Select appropriate timing profile\n this.timingProfile = getDefaultTimingProfile(this.config.keyboardMode, this.config.typingSpeed);\n \n // Select appropriate layout and apply timing profile\n const baseLayout = config.customLayout || \n (this.config.keyboardMode === 'mobile' ? MOBILE_LAYOUT : DESKTOP_QWERTY_LAYOUT);\n \n this.layout = applyTimingProfile(baseLayout, this.timingProfile);\n\n // Initialize keyboard state\n this.state = {\n currentView: 'letters',\n capsLockActive: false,\n shiftActive: false,\n recentCharacters: [],\n mode: this.config.keyboardMode\n };\n\n this.debug('KeyboardAnalyzer initialized', { \n config: this.config, \n timingProfile: this.timingProfile.name,\n layout: this.layout.viewSwitchers \n });\n }\n\n /**\n * Analyzes a character and returns the key sequence needed to type it\n */\n public analyzeCharacter(character: string, charIndex: number, fullText: string): KeySequence {\n this.debug(`Analyzing character: \"${character}\" at index ${charIndex}`);\n \n // Update recent characters for caps lock detection\n this.updateRecentCharacters(character);\n \n // Determine if this is part of a caps lock sequence\n const capsLockInfo = this.analyzeCapsLockSequence(character, charIndex, fullText);\n \n let keys: KeyInfo[] = [];\n \n if (this.config.keyboardMode === 'mobile') {\n keys = this.analyzeMobileCharacter(character, capsLockInfo);\n } else {\n keys = this.analyzeDesktopCharacter(character, capsLockInfo);\n }\n \n // Calculate total duration\n const totalDuration = keys.reduce((sum, key) => sum + key.duration, 0);\n \n const sequence: KeySequence = {\n character,\n keys,\n totalDuration,\n usesCapsLock: capsLockInfo.isCapsLockSequence\n };\n \n this.debug(`Generated sequence for \"${character}\":`, sequence);\n return sequence;\n }\n\n /**\n * Analyzes mobile keyboard character input\n */\n private analyzeMobileCharacter(character: string, capsInfo: any): KeyInfo[] {\n const keys: KeyInfo[] = [];\n let sequenceIndex = 0;\n \n // Handle special characters\n if (character === ' ') {\n return [{\n key: this.layout.modifiers.space,\n character,\n type: 'space',\n keyboardView: this.state.currentView,\n isCapsLock: false,\n duration: this.layout.keyDurations.space,\n sequenceIndex: 0,\n sequenceLength: 1\n }];\n }\n \n if (character === '\\n') {\n return [{\n key: this.layout.modifiers.enter,\n character,\n type: 'enter',\n keyboardView: this.state.currentView,\n isCapsLock: false,\n duration: this.layout.keyDurations.enter,\n sequenceIndex: 0,\n sequenceLength: 1\n }];\n }\n \n // Determine target view for this character\n const targetView = this.getCharacterView(character);\n \n // Add view switching keys if needed\n if (this.state.currentView !== targetView) {\n const viewSwitchKeys = this.getViewSwitchSequence(this.state.currentView, targetView);\n viewSwitchKeys.forEach(viewKey => {\n keys.push({\n ...viewKey,\n sequenceIndex: sequenceIndex++,\n sequenceLength: 0 // Will be updated after we know total length\n });\n });\n this.state.currentView = targetView;\n }\n \n // Handle caps lock for letters\n if (character.match(/[A-Z]/)) {\n if (capsInfo.isCapsLockSequence) {\n // CAPS LOCK mode - only add caps key for first and last\n if (capsInfo.isFirst) {\n keys.push(this.createCapsKey(true, sequenceIndex++));\n this.state.capsLockActive = true;\n } else if (capsInfo.isLast) {\n // Add the letter first, then caps off\n keys.push(this.createLetterKey(character.toLowerCase(), sequenceIndex++, capsInfo.isCapsLockSequence));\n keys.push(this.createCapsKey(false, sequenceIndex++));\n this.state.capsLockActive = false;\n \n // Update sequence lengths and return early\n keys.forEach(key => key.sequenceLength = keys.length);\n return keys;\n }\n // Middle of caps lock sequence - just add the letter (no caps key)\n } else {\n // Single capital - use shift\n keys.push(this.createShiftKey(sequenceIndex++));\n }\n }\n \n // Add the main character key\n const mainKey = this.createCharacterKey(character, sequenceIndex++, capsInfo.isCapsLockSequence);\n keys.push(mainKey);\n \n // Update sequence lengths\n keys.forEach(key => key.sequenceLength = keys.length);\n \n return keys;\n }\n\n /**\n * Analyzes desktop keyboard character input\n */\n private analyzeDesktopCharacter(character: string, capsInfo: any): KeyInfo[] {\n const keys: KeyInfo[] = [];\n let sequenceIndex = 0;\n \n // Handle special characters\n if (character === ' ') {\n return [{\n key: 'space',\n character,\n type: 'space',\n keyboardView: 'letters',\n isCapsLock: false,\n duration: this.layout.keyDurations.space,\n sequenceIndex: 0,\n sequenceLength: 1\n }];\n }\n \n if (character === '\\n') {\n return [{\n key: 'enter',\n character,\n type: 'enter',\n keyboardView: 'letters',\n isCapsLock: false,\n duration: this.layout.keyDurations.enter,\n sequenceIndex: 0,\n sequenceLength: 1\n }];\n }\n \n // Get desktop key mapping\n const mapping = DESKTOP_KEY_MAPPING[character];\n if (!mapping) {\n this.debug(`No desktop mapping found for character: \"${character}\"`);\n // Fallback to basic letter key\n return [{\n key: character.toLowerCase(),\n character,\n type: 'letter',\n keyboardView: 'letters',\n isCapsLock: false,\n duration: this.layout.keyDurations.letter,\n sequenceIndex: 0,\n sequenceLength: 1\n }];\n }\n \n const [physicalKey, requiresShift] = mapping;\n \n // Handle caps lock for letters\n if (character.match(/[A-Z]/)) {\n if (capsInfo.isCapsLockSequence) {\n // CAPS LOCK mode\n if (capsInfo.isFirst) {\n keys.push({\n key: 'caps lock',\n character,\n type: 'modifier',\n keyboardView: 'letters',\n isCapsLock: true,\n duration: this.layout.keyDurations.modifier,\n sequenceIndex: sequenceIndex++,\n sequenceLength: 0\n });\n } else if (capsInfo.isLast) {\n // Add letter first, then turn off caps lock\n keys.push({\n key: physicalKey,\n character,\n type: 'letter',\n keyboardView: 'letters',\n isCapsLock: true,\n duration: this.layout.keyDurations.letter,\n sequenceIndex: sequenceIndex++,\n sequenceLength: 0\n });\n keys.push({\n key: 'caps lock',\n character,\n type: 'modifier',\n keyboardView: 'letters',\n isCapsLock: true,\n duration: this.layout.keyDurations.modifier,\n sequenceIndex: sequenceIndex++,\n sequenceLength: 0\n });\n \n // Update sequence lengths and return\n keys.forEach(key => key.sequenceLength = keys.length);\n return keys;\n }\n // Middle of caps lock - just the letter\n } else if (requiresShift) {\n // Single capital with shift\n keys.push({\n key: 'shift',\n character,\n type: 'modifier',\n keyboardView: 'letters',\n isCapsLock: false,\n duration: this.layout.keyDurations.modifier,\n sequenceIndex: sequenceIndex++,\n sequenceLength: 0\n });\n }\n } else if (requiresShift) {\n // Non-letter that requires shift (symbols)\n keys.push({\n key: 'shift',\n character,\n type: 'modifier',\n keyboardView: 'symbols',\n isCapsLock: false,\n duration: this.layout.keyDurations.modifier,\n sequenceIndex: sequenceIndex++,\n sequenceLength: 0\n });\n }\n \n // Add the main key\n const keyType = character.match(/[a-zA-Z]/) ? 'letter' : \n character.match(/[0-9]/) ? 'number' : 'symbol';\n \n keys.push({\n key: physicalKey,\n character,\n type: keyType,\n keyboardView: keyType === 'letter' ? 'letters' : keyType === 'number' ? 'numbers' : 'symbols',\n isCapsLock: capsInfo.isCapsLockSequence,\n duration: this.layout.keyDurations[keyType],\n sequenceIndex: sequenceIndex++,\n sequenceLength: 0\n });\n \n // Update sequence lengths\n keys.forEach(key => key.sequenceLength = keys.length);\n \n return keys;\n }\n\n /**\n * Determines which keyboard view a character belongs to (mobile)\n */\n private getCharacterView(character: string): KeyboardView {\n if (this.config.keyboardMode === 'desktop') {\n return 'letters'; // Desktop doesn't have view switching\n }\n \n return MOBILE_CHARACTER_TO_VIEW[character] || 'letters';\n }\n\n /**\n * Gets the sequence of view switch keys needed\n */\n private getViewSwitchSequence(from: KeyboardView, to: KeyboardView): KeyInfo[] {\n if (from === to) return [];\n \n const keys: KeyInfo[] = [];\n \n // Mobile view switching logic\n switch (to) {\n case 'numbers':\n keys.push({\n key: this.layout.viewSwitchers.toNumbers,\n character: '',\n type: 'view-switch',\n keyboardView: from,\n isCapsLock: false,\n duration: this.layout.keyDurations.viewSwitch,\n sequenceIndex: 0,\n sequenceLength: 0\n });\n break;\n \n case 'symbols':\n // Might need to go through numbers first\n if (from === 'letters') {\n keys.push({\n key: this.layout.viewSwitchers.toNumbers,\n character: '',\n type: 'view-switch',\n keyboardView: from,\n isCapsLock: false,\n duration: this.layout.keyDurations.viewSwitch,\n sequenceIndex: 0,\n sequenceLength: 0\n });\n }\n keys.push({\n key: this.layout.viewSwitchers.toSymbols,\n character: '',\n type: 'view-switch',\n keyboardView: 'numbers',\n isCapsLock: false,\n duration: this.layout.keyDurations.viewSwitch,\n sequenceIndex: 0,\n sequenceLength: 0\n });\n break;\n \n case 'letters':\n keys.push({\n key: this.layout.viewSwitchers.toLetters,\n character: '',\n type: 'view-switch',\n keyboardView: from,\n isCapsLock: false,\n duration: this.layout.keyDurations.viewSwitch,\n sequenceIndex: 0,\n sequenceLength: 0\n });\n break;\n }\n \n return keys;\n }\n\n /**\n * Creates a caps lock key press\n */\n private createCapsKey(turningOn: boolean, sequenceIndex: number): KeyInfo {\n const baseDuration = this.layout.keyDurations.modifier;\n const contextualDuration = calculateContextualTiming(baseDuration, this.timingProfile, {\n isCapsLockTransition: true\n });\n \n return {\n key: this.layout.modifiers.caps,\n character: '',\n type: 'modifier',\n keyboardView: this.state.currentView,\n isCapsLock: true,\n duration: turningOn ? contextualDuration + 20 : contextualDuration, // Slightly longer for turning on\n sequenceIndex,\n sequenceLength: 0\n };\n }\n\n /**\n * Creates a shift key press\n */\n private createShiftKey(sequenceIndex: number): KeyInfo {\n return {\n key: this.layout.modifiers.shift,\n character: '',\n type: 'modifier',\n keyboardView: this.state.currentView,\n isCapsLock: false,\n duration: this.layout.keyDurations.modifier,\n sequenceIndex,\n sequenceLength: 0\n };\n }\n\n /**\n * Creates a letter key press\n */\n private createLetterKey(letter: string, sequenceIndex: number, isCapsLock: boolean): KeyInfo {\n return {\n key: letter,\n character: letter,\n type: 'letter',\n keyboardView: 'letters',\n isCapsLock,\n duration: this.layout.keyDurations.letter,\n sequenceIndex,\n sequenceLength: 0\n };\n }\n\n /**\n * Creates a character key press\n */\n private createCharacterKey(character: string, sequenceIndex: number, isCapsLock: boolean): KeyInfo {\n const key = character.toLowerCase();\n const type = character.match(/[a-zA-Z]/) ? 'letter' : \n character.match(/[0-9]/) ? 'number' : 'symbol';\n \n // Apply contextual timing based on character complexity\n const baseDuration = this.layout.keyDurations[type];\n const isComplexSymbol = type === 'symbol' && this.isComplexSymbol(character);\n const contextualDuration = calculateContextualTiming(baseDuration, this.timingProfile, {\n isComplexSymbol\n });\n \n return {\n key,\n character,\n type,\n keyboardView: this.state.currentView,\n isCapsLock,\n duration: contextualDuration,\n sequenceIndex,\n sequenceLength: 0\n };\n }\n\n /**\n * Updates recent characters for caps lock detection\n */\n private updateRecentCharacters(character: string) {\n this.state.recentCharacters.push(character);\n // Keep only last 10 characters for analysis\n if (this.state.recentCharacters.length > 10) {\n this.state.recentCharacters.shift();\n }\n }\n\n /**\n * Analyzes whether character is part of a caps lock sequence\n * Uses the same logic as the original TypingEngine\n */\n private analyzeCapsLockSequence(character: string, charIndex: number, fullText: string) {\n if (!character.match(/[A-Z]/)) {\n return { isCapsLockSequence: false, isFirst: false, isLast: false };\n }\n \n // Find the start and end of the caps region\n let sequenceStart = charIndex;\n let sequenceEnd = charIndex;\n \n // Scan backwards to find start of caps sequence\n while (sequenceStart > 0) {\n const prevChar = fullText[sequenceStart - 1];\n if (prevChar.match(/[A-Z]/) || prevChar === ' ') {\n sequenceStart--;\n } else {\n break;\n }\n }\n \n // Scan forwards to find end of caps sequence \n while (sequenceEnd < fullText.length - 1) {\n const nextChar = fullText[sequenceEnd + 1];\n if (nextChar.match(/[A-Z]/) || nextChar === ' ') {\n sequenceEnd++;\n } else {\n break;\n }\n }\n \n // Count capital letters (excluding spaces)\n let capitalCount = 0;\n for (let i = sequenceStart; i <= sequenceEnd; i++) {\n if (fullText[i].match(/[A-Z]/)) {\n capitalCount++;\n }\n }\n \n const isCapsLockSequence = capitalCount >= this.config.capsLockThreshold;\n \n // Find first and last capital letters\n let firstCapitalIndex = sequenceStart;\n while (firstCapitalIndex <= sequenceEnd && !fullText[firstCapitalIndex].match(/[A-Z]/)) {\n firstCapitalIndex++;\n }\n \n let lastCapitalIndex = sequenceEnd;\n while (lastCapitalIndex >= sequenceStart && !fullText[lastCapitalIndex].match(/[A-Z]/)) {\n lastCapitalIndex--;\n }\n \n return {\n isCapsLockSequence,\n isFirst: isCapsLockSequence && charIndex === firstCapitalIndex,\n isLast: isCapsLockSequence && charIndex === lastCapitalIndex\n };\n }\n\n /**\n * Check if a symbol is considered complex (requires more precise movement)\n */\n private isComplexSymbol(character: string): boolean {\n const complexSymbols = new Set(['@', '#', '$', '%', '^', '&', '*', '+', '=', '{', '}', '\\\\', '|', '`', '~', '<', '>']);\n return complexSymbols.has(character);\n }\n\n /**\n * Debug logging\n */\n private debug(message: string, data?: any) {\n if (this.config.debug) {\n console.log(`[KeyboardAnalyzer] ${message}`, data || '');\n }\n }\n\n /**\n * Reset keyboard state (useful for testing)\n */\n public resetState() {\n this.state = {\n currentView: 'letters',\n capsLockActive: false,\n shiftActive: false,\n recentCharacters: [],\n mode: this.config.keyboardMode\n };\n }\n\n /**\n * Analyzes a backspace key press and returns the key sequence\n */\n public analyzeBackspace(): KeySequence {\n this.debug('Analyzing backspace key');\n \n const backspaceKey: KeyInfo = {\n key: this.config.keyboardMode === 'mobile' ? this.layout.modifiers.backspace : 'backspace',\n character: '\\b', // Backspace character\n type: 'backspace',\n keyboardView: this.state.currentView,\n isCapsLock: false,\n duration: this.layout.keyDurations.backspace || this.layout.keyDurations.modifier || 120,\n sequenceIndex: 0,\n sequenceLength: 1\n };\n \n const sequence: KeySequence = {\n character: '\\b',\n keys: [backspaceKey],\n totalDuration: backspaceKey.duration,\n usesCapsLock: false\n };\n \n this.debug('Generated backspace sequence:', sequence);\n return sequence;\n }\n\n /**\n * Get current keyboard state (useful for debugging)\n */\n public getState(): KeyboardState {\n return { ...this.state };\n }\n}","import type {\n HumanLikeConfig,\n MistakeInfo,\n MistakeType,\n TypingState,\n TypingEvent,\n TypingStats,\n KeyInfo\n} from '../types';\nimport {\n TIMING_CONSTANTS,\n BEHAVIOR_RATES,\n getAdjacentKeys,\n COMMON_WORDS,\n COMMON_TYPOS,\n SPECIAL_CHARS,\n SHIFT_CHARS,\n NUMBER_CHARS,\n SYMBOL_COMPLEXITY,\n SENTENCE_ENDINGS,\n CLAUSE_SEPARATORS,\n LINE_BREAK_CHARS,\n DEFAULT_CONFIG\n} from '../constants';\nimport { KeyboardAnalyzer } from '../keyboard/KeyboardAnalyzer';\n\nexport class TypingEngine {\n private config: HumanLikeConfig;\n private text: string;\n private currentIndex: number = 0;\n private displayText: string = '';\n private state: TypingState = 'idle';\n private timeoutId: number | null = null;\n private keyTimeouts: Set<number> = new Set(); // Track all keyboard timing timeouts\n private stats: TypingStats;\n private events: TypingEvent[] = [];\n private mistakes: MistakeInfo[] = [];\n private correctionQueue: MistakeInfo[] = [];\n private isCorrectingMistake: boolean = false;\n private charactersTyped: number = 0;\n private fatigueLevel: number = 0;\n private pauseStartTime: number = 0;\n private totalPausedTime: number = 0;\n \n // Keyboard simulation - always enabled now\n private keyboar