[{"created":"20260514151214468","text":"a: a\nb: b\nc: c\nd: d\ne: e\nf: f\ng: g\nh: h\ni: i\nj: j\nk: k\nl: l\nm: m\nn: n\no: o\np: p\nq: q\nr: r\ns: s\nt: t\nu: u\nv: v\nw: w\nx: x\ny: y\nz: z\n0: key-0\n1: key-1\n2: key-2\n3: key-3\n4: key-4\n5: key-5\n6: key-6\n7: key-7\n8: key-8\n9: key-9\n-: minus\n=: equal\n[: open-bracket\n]: close-bracket\n;: semicolon\n,: comma\n.: dot\n/: slash\n\\: backslash\n`: tilda\n~: tilda\nenter: enter\ntab: tab\nspace: space\n´: tilda\n': tilda\ndead: quote","tags":"","title":"$:/data/keyboard-mapping","modified":"20260514160006937","type":"application/x-tiddler-dictionary"},{"created":"20260514161735957","text":"\\define left-hand()\nkey-1 key-2 key-3 key-4 key-5 q w e r t a s d f g z x c v b tilda tab shift-left\n\\end\n\n\\define right-hand()\nkey-6 key-7 key-8 key-9 key-0 minus equal y u i o p open-bracket close-bracket h j k l semicolon quote enter n m comma dot slash backslash shift-right\n\\end\n\n\\procedure update-expected-key(nextChar)\n<$let \n    nextCharLower={{{ [<nextChar>lowercase[]] }}}\n    safeKey={{{ [<nextCharLower>match[ ]then[space]else<nextCharLower>] }}}\n    mappedId={{{ [[$:/data/keyboard-mapping]getindex<safeKey>] }}}\n>\n    <$action-setfield $tiddler=<<currentTiddler>> debug=<<mappedId>> />\n</$let>\n\\end\n\n\\procedure key-press-actions()\n<$let \n    rawKey={{{ [<eventJSON>jsonget[key]] }}}\n    expectedChar={{{ [{!!remaining}split[]first[]] }}}\n>\n    <$action-setfield last-key=<<rawKey>> expected-key=<<expectedChar>> />\n\n    <!-- Compare (uppercase L is not lowercase l!) -->\n    <% if [<rawKey>match<expectedChar>] %>\n        <$let \n            newTyped={{{ [{!!typed}addsuffix<rawKey>] }}}\n            newRemaining={{{ [{!!remaining}split[]rest[]join[]] }}}\n            nextChar={{{ [<newRemaining>split[]first[]] }}}\n        >\n            <$action-setfield $tiddler=<<currentTiddler>> typed=<<newTyped>> remaining=<<newRemaining>> />\n            <$macrocall $name=\"update-expected-key\" nextChar=<<nextChar>> />\n        </$let>\n    <% endif %>\n</$let>\n\\end\n\n<!-- ========================================== -->\n<!-- USER INTERFACE                             -->\n<!-- ========================================== -->\n\n<%if [[vkeyboardkeys.svg]!has[text]]%>\nPaste the svg code for the keyboard here: <$edit class=\"tc-edit-texteditor\" tiddler=\"vkeyboardkeys.svg\"/>\n<%endif%>\n\n<%if [[hand-both.svg]!has[text]]%>\nPaste the [[svg code for the hands|https://www.edclub.com/m/engine/img/hand/hand-both.svg]] here: <$edit class=\"tc-edit-texteditor\" tiddler=\"hand-both.svg\"/>\n<%endif%>\n\n<div style=\"margin-bottom: 20px;\">\n<$edit-text field=\"target\" class=\"tc-edit-texteditor\"/>\n    <$button>\n        Start\n        <$action-sendmessage $message=\"tm-focus-selector\" $param=\".typing-area\"/>\n        <$action-setfield \n            typed=\"\" \n            remaining={{!!target}}\n            last-key=\"\"\n            expected-key={{{ [{!!target}split[]first[]] }}}\n        />\n        <$macrocall $name=\"update-expected-key\" nextChar=\"T\" />\n    </$button>\n</div>\n\n<!-- DEBUG BAR (To understand what is blocking) -->\n<div style=\"background: #444; color: #e6a23c; padding: 8px 15px; border-radius: 4px; font-family: monospace; font-size: 14px; margin-bottom: 10px;\">\n    You typed [ <strong>{{!!last-key}}</strong> ] | System expects [ <strong>{{!!expected-key}}</strong> ]\n</div>\n\n<!-- Game zone intercepted by the EventCatcher -->\n<$eventcatcher selector=\".capture-clavier\" $keydown=<<key-press-actions>>>\n    <div class=\"capture-clavier typing-area\" tabindex=\"0\">\n        <span class=\"typed-text\">{{!!typed}}</span><!--\n        --><span class=\"cursor-text\">{{{ [{!!remaining}split[]first[]] }}}</span><!--\n        --><span class=\"remaining-text\">{{{ [{!!remaining}split[]rest[]join[]] }}}</span>\n    </div>\n</$eventcatcher>\n\n<div class=\"keyboard-hand-container\">\n    <div class=\"keyboard\">{{vkeyboardkeys.svg}}</div>\n    <div class=\"hands\">{{hand-both.svg}}</div>\n</div>\n\n<style>\n.typing-area {\n    font-family: monospace;\n    font-size: 24px;\n    background: #2b2b2b;\n    color: #fff;\n    padding: 20px;\n    border-radius: 8px;\n    white-space: pre-wrap; /* Essential for spaces */\n    outline: none;\n    cursor: text;\n    border: 2px solid transparent;\n    transition: border 0.2s;\n    min-height: 30px;\n}\n\n.typing-area:focus {\n    border: 2px solid #79bbff;\n    box-shadow: 0 0 10px rgba(121, 187, 255, 0.3);\n}\n\n.typing-area:not(:focus)::after {\n    content: \" (Click here to focus)\";\n    font-size: 14px;\n    color: #888;\n    position: absolute;\n    margin-left: 10px;\n}\n\n.typed-text { color: #67c23a; } \n.cursor-text { background-color: #e6a23c; color: #000; border-radius: 2px; } \n.remaining-text { color: #909399; } \n\n.tc-tiddler-body:has(.keyboard-hand-container){ overflow: clip; }\n.keyboard-hand-container { margin-inline:auto;pointer-events: none; position: relative; width: 685px; margin-top: 20px; }\n.hands { inset: 0; position: absolute; pointer-events: none; height: 363px; top: -10px; left: 0px; opacity: 0.8; transform: translateX(55px) scale(1.55); }\n\n#{{!!debug}} { display: initial !important; opacity: 1 !important; }\n#neutral-left, #neutral-right { display: initial !important; }\n\n<% if [enlist<left-hand>match{!!debug}] %>\n    #neutral-left { display: none !important; }\n<% endif %>\n<% if [enlist<right-hand>match{!!debug}] %>\n    #neutral-right { display: none !important; }\n<% endif %>\n\n\n@keyframes keyFade<$text text={{!!modified}} /> {\n  0% { fill: #e97e7e; }      /* Couleur flash au moment du clic */\n  100% { fill: transparent;/*#f0f0f0*/}  /* Retour à la couleur d'origine (à ajuster) */\n}\n\n.keyboard { \n<% if [{!!last-key}!match{!!expected-key}] %>\n  #{{!!last-key}} { \n    animation: keyFade<$text text={{!!modified}} /> .5s ease forwards; \n  } \n<%endif%>\n\n  #{{!!debug}} { fill: rgb(121, 187, 255); } \n}\n</style>","tags":"","title":"TouchTyping","modified":"20260514201141097","debug":"f","event-json":"{\"isTrusted\":true,\"charCode\":0,\"keyCode\":48,\"altKey\":false,\"ctrlKey\":false,\"shiftKey\":false,\"metaKey\":false,\"location\":0,\"repeat\":false,\"isComposing\":false,\"key\":\"0\",\"code\":\"Digit0\",\"DOM_KEY_LOCATION_STANDARD\":0,\"DOM_KEY_LOCATION_LEFT\":1,\"DOM_KEY_LOCATION_RIGHT\":2,\"DOM_KEY_LOCATION_NUMPAD\":3,\"DOM_VK_CANCEL\":3,\"DOM_VK_HELP\":6,\"DOM_VK_BACK_SPACE\":8,\"DOM_VK_TAB\":9,\"DOM_VK_CLEAR\":12,\"DOM_VK_RETURN\":13,\"DOM_VK_SHIFT\":16,\"DOM_VK_CONTROL\":17,\"DOM_VK_ALT\":18,\"DOM_VK_PAUSE\":19,\"DOM_VK_CAPS_LOCK\":20,\"DOM_VK_KANA\":21,\"DOM_VK_HANGUL\":21,\"DOM_VK_EISU\":22,\"DOM_VK_JUNJA\":23,\"DOM_VK_FINAL\":24,\"DOM_VK_HANJA\":25,\"DOM_VK_KANJI\":25,\"DOM_VK_ESCAPE\":27,\"DOM_VK_CONVERT\":28,\"DOM_VK_NONCONVERT\":29,\"DOM_VK_ACCEPT\":30,\"DOM_VK_MODECHANGE\":31,\"DOM_VK_SPACE\":32,\"DOM_VK_PAGE_UP\":33,\"DOM_VK_PAGE_DOWN\":34,\"DOM_VK_END\":35,\"DOM_VK_HOME\":36,\"DOM_VK_LEFT\":37,\"DOM_VK_UP\":38,\"DOM_VK_RIGHT\":39,\"DOM_VK_DOWN\":40,\"DOM_VK_SELECT\":41,\"DOM_VK_PRINT\":42,\"DOM_VK_EXECUTE\":43,\"DOM_VK_PRINTSCREEN\":44,\"DOM_VK_INSERT\":45,\"DOM_VK_DELETE\":46,\"DOM_VK_0\":48,\"DOM_VK_1\":49,\"DOM_VK_2\":50,\"DOM_VK_3\":51,\"DOM_VK_4\":52,\"DOM_VK_5\":53,\"DOM_VK_6\":54,\"DOM_VK_7\":55,\"DOM_VK_8\":56,\"DOM_VK_9\":57,\"DOM_VK_COLON\":58,\"DOM_VK_SEMICOLON\":59,\"DOM_VK_LESS_THAN\":60,\"DOM_VK_EQUALS\":61,\"DOM_VK_GREATER_THAN\":62,\"DOM_VK_QUESTION_MARK\":63,\"DOM_VK_AT\":64,\"DOM_VK_A\":65,\"DOM_VK_B\":66,\"DOM_VK_C\":67,\"DOM_VK_D\":68,\"DOM_VK_E\":69,\"DOM_VK_F\":70,\"DOM_VK_G\":71,\"DOM_VK_H\":72,\"DOM_VK_I\":73,\"DOM_VK_J\":74,\"DOM_VK_K\":75,\"DOM_VK_L\":76,\"DOM_VK_M\":77,\"DOM_VK_N\":78,\"DOM_VK_O\":79,\"DOM_VK_P\":80,\"DOM_VK_Q\":81,\"DOM_VK_R\":82,\"DOM_VK_S\":83,\"DOM_VK_T\":84,\"DOM_VK_U\":85,\"DOM_VK_V\":86,\"DOM_VK_W\":87,\"DOM_VK_X\":88,\"DOM_VK_Y\":89,\"DOM_VK_Z\":90,\"DOM_VK_WIN\":91,\"DOM_VK_CONTEXT_MENU\":93,\"DOM_VK_SLEEP\":95,\"DOM_VK_NUMPAD0\":96,\"DOM_VK_NUMPAD1\":97,\"DOM_VK_NUMPAD2\":98,\"DOM_VK_NUMPAD3\":99,\"DOM_VK_NUMPAD4\":100,\"DOM_VK_NUMPAD5\":101,\"DOM_VK_NUMPAD6\":102,\"DOM_VK_NUMPAD7\":103,\"DOM_VK_NUMPAD8\":104,\"DOM_VK_NUMPAD9\":105,\"DOM_VK_MULTIPLY\":106,\"DOM_VK_ADD\":107,\"DOM_VK_SEPARATOR\":108,\"DOM_VK_SUBTRACT\":109,\"DOM_VK_DECIMAL\":110,\"DOM_VK_DIVIDE\":111,\"DOM_VK_F1\":112,\"DOM_VK_F2\":113,\"DOM_VK_F3\":114,\"DOM_VK_F4\":115,\"DOM_VK_F5\":116,\"DOM_VK_F6\":117,\"DOM_VK_F7\":118,\"DOM_VK_F8\":119,\"DOM_VK_F9\":120,\"DOM_VK_F10\":121,\"DOM_VK_F11\":122,\"DOM_VK_F12\":123,\"DOM_VK_F13\":124,\"DOM_VK_F14\":125,\"DOM_VK_F15\":126,\"DOM_VK_F16\":127,\"DOM_VK_F17\":128,\"DOM_VK_F18\":129,\"DOM_VK_F19\":130,\"DOM_VK_F20\":131,\"DOM_VK_F21\":132,\"DOM_VK_F22\":133,\"DOM_VK_F23\":134,\"DOM_VK_F24\":135,\"DOM_VK_NUM_LOCK\":144,\"DOM_VK_SCROLL_LOCK\":145,\"DOM_VK_WIN_OEM_FJ_JISHO\":146,\"DOM_VK_WIN_OEM_FJ_MASSHOU\":147,\"DOM_VK_WIN_OEM_FJ_TOUROKU\":148,\"DOM_VK_WIN_OEM_FJ_LOYA\":149,\"DOM_VK_WIN_OEM_FJ_ROYA\":150,\"DOM_VK_CIRCUMFLEX\":160,\"DOM_VK_EXCLAMATION\":161,\"DOM_VK_DOUBLE_QUOTE\":162,\"DOM_VK_HASH\":163,\"DOM_VK_DOLLAR\":164,\"DOM_VK_PERCENT\":165,\"DOM_VK_AMPERSAND\":166,\"DOM_VK_UNDERSCORE\":167,\"DOM_VK_OPEN_PAREN\":168,\"DOM_VK_CLOSE_PAREN\":169,\"DOM_VK_ASTERISK\":170,\"DOM_VK_PLUS\":171,\"DOM_VK_PIPE\":172,\"DOM_VK_HYPHEN_MINUS\":173,\"DOM_VK_OPEN_CURLY_BRACKET\":174,\"DOM_VK_CLOSE_CURLY_BRACKET\":175,\"DOM_VK_TILDE\":176,\"DOM_VK_VOLUME_MUTE\":181,\"DOM_VK_VOLUME_DOWN\":182,\"DOM_VK_VOLUME_UP\":183,\"DOM_VK_COMMA\":188,\"DOM_VK_PERIOD\":190,\"DOM_VK_SLASH\":191,\"DOM_VK_BACK_QUOTE\":192,\"DOM_VK_OPEN_BRACKET\":219,\"DOM_VK_BACK_SLASH\":220,\"DOM_VK_CLOSE_BRACKET\":221,\"DOM_VK_QUOTE\":222,\"DOM_VK_META\":224,\"DOM_VK_ALTGR\":225,\"DOM_VK_WIN_ICO_HELP\":227,\"DOM_VK_WIN_ICO_00\":228,\"DOM_VK_PROCESSKEY\":229,\"DOM_VK_WIN_ICO_CLEAR\":230,\"DOM_VK_WIN_OEM_RESET\":233,\"DOM_VK_WIN_OEM_JUMP\":234,\"DOM_VK_WIN_OEM_PA1\":235,\"DOM_VK_WIN_OEM_PA2\":236,\"DOM_VK_WIN_OEM_PA3\":237,\"DOM_VK_WIN_OEM_WSCTRL\":238,\"DOM_VK_WIN_OEM_CUSEL\":239,\"DOM_VK_WIN_OEM_ATTN\":240,\"DOM_VK_WIN_OEM_FINISH\":241,\"DOM_VK_WIN_OEM_COPY\":242,\"DOM_VK_WIN_OEM_AUTO\":243,\"DOM_VK_WIN_OEM_ENLW\":244,\"DOM_VK_WIN_OEM_BACKTAB\":245,\"DOM_VK_ATTN\":246,\"DOM_VK_CRSEL\":247,\"DOM_VK_EXSEL\":248,\"DOM_VK_EREOF\":249,\"DOM_VK_PLAY\":250,\"DOM_VK_ZOOM\":251,\"DOM_VK_PA1\":253,\"DOM_VK_WIN_OEM_CLEAR\":254,\"detail\":0,\"layerX\":0,\"layerY\":0,\"which\":48,\"rangeParent\":null,\"rangeOffset\":8,\"SCROLL_PAGE_UP\":-32768,\"SCROLL_PAGE_DOWN\":32768,\"type\":\"keydown\",\"eventPhase\":3,\"bubbles\":true,\"cancelable\":true,\"returnValue\":true,\"defaultPrevented\":false,\"composed\":true,\"timeStamp\":1556901,\"cancelBubble\":false,\"NONE\":0,\"CAPTURING_PHASE\":1,\"AT_TARGET\":2,\"BUBBLING_PHASE\":3,\"ALT_MASK\":1,\"CONTROL_MASK\":2,\"SHIFT_MASK\":4,\"META_MASK\":8}","typed":"The quick ","target":"The quick fox jumps over the lazy dog.","last-key":" ","expected-key":" ","remaining":"fox jumps over the lazy dog.","error-toggle":"a"},{"created":"20260514143604287","text":"","title":"hand-both.svg","type":"","modified":"20260514200558941"},{"created":"20260514152128335","text":"","tags":"","title":"vkeyboardkeys.svg","modified":"20260514200044392"}]