First implementation of 10 finger typing

This commit is contained in:
2026-09-11 21:55:45 +02:00
parent 7e5fd5ab75
commit f97de193d8
63 changed files with 8275 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
/** Tauchgang - the core drill, and the run that counts for the unlock.
*
* A line of chunks with a moving cursor, the finger hint above it and the keyboard
* below. Everything else in the game is a variation on this; this is the one that is
* measured. */
import { useEffect } from "react";
import { currentChar } from "../../lib/engine";
import type { RunResult } from "../../lib/grading";
import type { Progress } from "../../lib/progress";
import { say, spellKey } from "../../lib/speech";
import { useRun } from "../../hooks/useRun";
import { HandHint } from "../HandHint";
import { Keyboard } from "../Keyboard";
import { Target } from "../Target";
interface Props {
chunks: readonly string[];
text: string;
spaceActive: boolean;
activeKeys: readonly string[];
progress: Progress;
paused: boolean;
onFinished: (result: RunResult) => void;
}
export function TauchgangRun({
chunks,
text,
spaceActive,
activeKeys,
progress,
paused,
onFinished,
}: Props) {
const { state, daneben } = useRun({
target: text,
sound: progress.settings.sound,
paused,
onFinished,
});
const next = currentChar(state);
// Say the first key of a line, and any key she gets stuck on. Not every key: a voice
// talking over every keystroke is noise, and it would lag behind a good streak.
useEffect(() => {
if (state.index === 0 && next) say(spellKey(next), progress.settings.speech);
}, [state.index, next, progress.settings.speech]);
useEffect(() => {
if (daneben && next) say(spellKey(next), progress.settings.speech);
}, [daneben, next, progress.settings.speech]);
return (
<div
className="view-enter"
style={{
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 26,
padding: "0 32px",
minHeight: 0,
}}
>
<HandHint nextKey={next} />
<Target chunks={chunks} spaceActive={spaceActive} index={state.index} daneben={daneben} />
<Fortschritt done={state.index} total={text.length} />
<Keyboard
activeKeys={activeKeys}
nextKey={next}
progress={progress}
mode={progress.settings.keyboardHint}
/>
</div>
);
}
/** How far through the line she is - a bar, not a number, because "18 von 24" is a
* reading task and a filling bar is not. */
function Fortschritt({ done, total }: { done: number; total: number }) {
return (
<div
style={{
width: "min(520px, 80%)",
height: 8,
borderRadius: 999,
background: "oklch(97% 0.01 175 / 0.16)",
overflow: "hidden",
}}
>
<div
style={{
width: `${total === 0 ? 0 : (done / total) * 100}%`,
height: "100%",
background: "var(--paper)",
transition: "width 120ms ease-out",
}}
/>
</div>
);
}