Skip to content
1 change: 1 addition & 0 deletions frontend/src/html/pages/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,7 @@
<button data-config-value="hide">hide</button>
<button data-config-value="fade">fade</button>
<button data-config-value="dots">dots</button>
<button data-config-value="fall">fall</button>
</div>
</div>
<div class="section" data-config-name="tapeMode">
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/styles/media-queries.scss
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ body {

@media (prefers-reduced-motion) {
body:not(.ignore-reduced-motion)
*:not(.fa-spin, .animate-\[loader\], .preloader) {
*:not(.fa-spin, .animate-\[loader\], .preloader, .fall-clone) {
animation: none !important;
transition: none !important;

Expand Down
14 changes: 14 additions & 0 deletions frontend/src/styles/test.scss
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,12 @@
}
}

&.typed-effect-fall {
.word.typed:not(.error) {
opacity: 0;
}
}

&.typed-effect-dots {
/* transform already typed letters into appropriately colored dots */

Expand Down Expand Up @@ -597,6 +603,14 @@
}
}

.word.fall-clone {
display: inline-block;
position: fixed;
margin: 0;
pointer-events: none;
z-index: 1000;
}

.word {
position: relative;
font-size: 1em;
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/ts/test/test-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import * as ThemeController from "../controllers/theme-controller";
import * as ModesNotice from "../elements/modes-notice";
import * as Last10Average from "../elements/last-10-average";
import * as MemoryFunboxTimer from "./funbox/memory-funbox-timer";
import * as TypedEffects from "./typed-effects";
import {
ElementsWithUtils,
ElementWithUtils,
Expand Down Expand Up @@ -147,6 +148,7 @@ export function updateActiveElement(
if (previousActiveWord !== null) {
if (direction === "forward") {
previousActiveWord.addClass("typed");
TypedEffects.onWordTyped(previousActiveWord);
Ligatures.set(previousActiveWord, true);
Comment thread
d1rshan marked this conversation as resolved.
} else if (direction === "back") {
//
Expand Down Expand Up @@ -495,6 +497,7 @@ function updateWordWrapperClasses(): void {
}

function showWords(): void {
TypedEffects.clear();
wordsEl.setHtml("");

if (Config.mode === "zen") {
Expand Down Expand Up @@ -1939,6 +1942,7 @@ export function onTestRestart(source: "testPage" | "resultPage"): void {
}

export function onTestFinish(): void {
TypedEffects.clear();
Caret.hide();
LiveSpeed.hide();
LiveAcc.hide();
Expand Down Expand Up @@ -2083,6 +2087,9 @@ configEvent.subscribe(({ key, newValue }) => {
"tapeMargin",
].includes(key)
) {
if (key === "typedEffect" && newValue !== "fall") {
TypedEffects.clear();
}
if (key !== "fontFamily") updateWordWrapperClasses();
if (["typedEffect", "fontFamily", "fontSize"].includes(key)) {
Ligatures.update(key, wordsEl);
Expand Down
50 changes: 50 additions & 0 deletions frontend/src/ts/test/typed-effects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { animate } from "animejs"; // v4: named export, no default
import { Config } from "../config/store";
import { ElementWithUtils, qsa, qsr } from "../utils/dom";

const FALL_DURATION_MS = 1700;

export function onWordTyped(word: ElementWithUtils): void {
switch (Config.typedEffect) {
case "fall":
triggerFall(word);
return;
default:
return;
}
}

export function clear(): void {
qsa(".fall-clone").remove();
}

function triggerFall(word: ElementWithUtils): void {
if (word.hasClass("error")) return;

const rect = word.native.getBoundingClientRect();
if (rect.width === 0 && rect.height === 0) return;

const clone = word.native.cloneNode(true) as HTMLElement;

clone.classList.remove("active");
clone.classList.add("fall-clone");
clone.style.top = `${rect.top}px`;
clone.style.left = `${rect.left}px`;
clone.style.width = `${rect.width}px`;
clone.style.height = `${rect.height}px`;

qsr("#words").native.appendChild(clone);

const randomRotation = (Math.random() - 0.5) * 45;
const randomX = (Math.random() - 0.5) * 100;

animate(clone, {
translateX: randomX,
translateY: window.innerHeight - rect.top,
rotate: randomRotation,
opacity: [1, 1, 0],
duration: FALL_DURATION_MS,
easing: "easeInQuad",
onComplete: () => clone.remove(),
});
}
8 changes: 7 additions & 1 deletion packages/schemas/src/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,13 @@ export const HighlightModeSchema = z.enum([
]);
export type HighlightMode = z.infer<typeof HighlightModeSchema>;

export const TypedEffectSchema = z.enum(["keep", "hide", "fade", "dots"]);
export const TypedEffectSchema = z.enum([
"keep",
"hide",
"fade",
"dots",
"fall",
]);
export type TypedEffect = z.infer<typeof TypedEffectSchema>;

export const TapeModeSchema = z.enum(["off", "letter", "word"]);
Expand Down
Loading