diff --git a/frontend/src/html/pages/settings.html b/frontend/src/html/pages/settings.html
index 7049cbd4ee1f..c8c3f579abf0 100644
--- a/frontend/src/html/pages/settings.html
+++ b/frontend/src/html/pages/settings.html
@@ -131,12 +131,27 @@
Normal is the classic typing test experience. Expert fails the test if
you submit (press space) an incorrect word. Master fails if you press a
- single incorrect key (meaning you have to achieve 100% accuracy).
+ single incorrect key (meaning you have to achieve 100% accuracy). Custom
+ allows you to set a custom accuracy threshold in words mode.
-
diff --git a/frontend/src/ts/config/metadata.ts b/frontend/src/ts/config/metadata.ts
index 481faf990236..120aa7fecf15 100644
--- a/frontend/src/ts/config/metadata.ts
+++ b/frontend/src/ts/config/metadata.ts
@@ -246,6 +246,21 @@ export const configMetadata: ConfigMetadataObject = {
changeRequiresRestart: true,
group: "behavior",
},
+ difficultyCustomAccuracy: {
+ key: "difficultyCustomAccuracy",
+ fa: { icon: "fa-star" },
+ displayString: "custom difficulty accuracy",
+ changeRequiresRestart: true,
+ group: "behavior",
+ overrideConfig: ({ currentConfig }) => {
+ if (currentConfig.difficulty !== "custom") {
+ return {
+ difficulty: "custom",
+ };
+ }
+ return {};
+ },
+ },
quickRestart: {
key: "quickRestart",
fa: { icon: "fa-redo-alt" },
diff --git a/frontend/src/ts/constants/default-config.ts b/frontend/src/ts/constants/default-config.ts
index c2fea2f0d058..ec659bac3573 100644
--- a/frontend/src/ts/constants/default-config.ts
+++ b/frontend/src/ts/constants/default-config.ts
@@ -33,6 +33,7 @@ const obj: Config = {
fontSize: 2,
freedomMode: false,
difficulty: "normal",
+ difficultyCustomAccuracy: 100,
blindMode: false,
quickEnd: false,
caretStyle: "default",
diff --git a/frontend/src/ts/elements/modes-notice.ts b/frontend/src/ts/elements/modes-notice.ts
index 39c5e41679a8..d873ad7ae13e 100644
--- a/frontend/src/ts/elements/modes-notice.ts
+++ b/frontend/src/ts/elements/modes-notice.ts
@@ -141,6 +141,10 @@ export async function update(): Promise {
testModesNotice.appendHtml(
` master `,
);
+ } else if (Config.difficulty === "custom") {
+ testModesNotice.appendHtml(
+ ` ${Config.difficultyCustomAccuracy}% acc `,
+ );
}
if (Config.blindMode) {
diff --git a/frontend/src/ts/pages/settings.ts b/frontend/src/ts/pages/settings.ts
index 62fe8c4f5057..1f5ed8fb1248 100644
--- a/frontend/src/ts/pages/settings.ts
+++ b/frontend/src/ts/pages/settings.ts
@@ -438,6 +438,15 @@ async function fillSettingsPage(): Promise {
},
});
+ handleConfigInput({
+ input: qsr(".pageSettings .section[data-config-name='difficulty'] input"),
+ configName: "difficultyCustomAccuracy",
+ validation: {
+ schema: true,
+ inputValueConvert: Number,
+ },
+ });
+
handleConfigInput({
input: qsr(".pageSettings .section[data-config-name='minBurst'] input"),
configName: "minBurstCustomSpeed",
diff --git a/frontend/src/ts/test/test-timer.ts b/frontend/src/ts/test/test-timer.ts
index 26cf66b694e3..0188771d2897 100644
--- a/frontend/src/ts/test/test-timer.ts
+++ b/frontend/src/ts/test/test-timer.ts
@@ -157,17 +157,16 @@ function checkIfFailed(
): boolean {
if (timerDebug) console.time("fail conditions");
TestInput.pushKeypressesToHistory();
- TestInput.pushErrorToHistory();
- TestInput.pushAfkToHistory();
+
if (
- Config.minWpm === "custom" &&
- wpmAndRaw.wpm < Config.minWpmCustomSpeed &&
- TestState.activeWordIndex > 3
+ Config.difficulty === "custom" &&
+ Config.mode === "words" &&
+ acc < Config.difficultyCustomAccuracy
) {
if (timer !== null) clearTimeout(timer);
SlowTimer.clear();
slowTimerCount = 0;
- timerEvent.dispatch({ key: "fail", value: "min speed" });
+ timerEvent.dispatch({ key: "fail", value: "custom difficulty" });
return true;
}
if (Config.minAcc === "custom" && acc < Config.minAccCustom) {
diff --git a/packages/schemas/src/configs.ts b/packages/schemas/src/configs.ts
index 77c257a54834..67497c6bdf45 100644
--- a/packages/schemas/src/configs.ts
+++ b/packages/schemas/src/configs.ts
@@ -336,6 +336,11 @@ export type MinWpmCustomSpeed = z.infer;
export const MinimumAccuracyCustomSchema = z.number().nonnegative().max(100);
export type MinimumAccuracyCustom = z.infer;
+export const DifficultyCustomAccuracySchema = z.number().nonnegative().max(100);
+export type DifficultyCustomAccuracy = z.infer<
+ typeof DifficultyCustomAccuracySchema
+>;
+
export const MinimumBurstCustomSpeedSchema = z.number().nonnegative();
export type MinimumBurstCustomSpeed = z.infer<
typeof MinimumBurstCustomSpeedSchema
@@ -392,6 +397,7 @@ export const ConfigSchema = z
// behavior
difficulty: DifficultySchema,
+ difficultyCustomAccuracy: DifficultyCustomAccuracySchema,
quickRestart: QuickRestartSchema,
repeatQuotes: RepeatQuotesSchema,
resultSaving: z.boolean(),
diff --git a/packages/schemas/src/shared.ts b/packages/schemas/src/shared.ts
index 7a1f5b570d07..2915428cbdf0 100644
--- a/packages/schemas/src/shared.ts
+++ b/packages/schemas/src/shared.ts
@@ -3,7 +3,12 @@ import { StringNumberSchema } from "./util";
import { LanguageSchema } from "./languages";
//used by config and shared
-export const DifficultySchema = z.enum(["normal", "expert", "master"]);
+export const DifficultySchema = z.enum([
+ "normal",
+ "expert",
+ "master",
+ "custom",
+]);
export type Difficulty = z.infer;
//used by user and config