fleeting-garden/src/page/mirror-segment-control.ts
Andras Schmelczer 0fddad6b45
All checks were successful
Check & deploy / build (pull_request) Successful in 7m18s
Check & deploy / build (push) Successful in 7m6s
Clean up configs
2026-05-25 09:27:39 +01:00

89 lines
2.6 KiB
TypeScript

import {
MAX_MIRROR_SEGMENT_COUNT,
MIN_MIRROR_SEGMENT_COUNT,
} from '../game-loop/stroke-mirroring';
import { DEFAULT_MIRROR_SEGMENT_COUNT, settings } from '../settings';
import { queryRequiredElement } from '../utils/dom';
const MIRROR_SEGMENT_STEP = 1;
const MIRROR_SEGMENT_OFF_LABEL = 'Mirror off';
const MIRROR_SEGMENT_FALLBACK_NAME = 'slices';
const MIRROR_SEGMENT_NAMES = {
2: 'halves',
3: 'thirds',
4: 'quarters',
5: 'fifths',
6: 'sixths',
7: 'sevenths',
8: 'eighths',
9: 'ninths',
10: 'tenths',
11: 'elevenths',
12: 'twelfths',
} satisfies Record<number, string>;
const clampMirrorSegmentCount = (value: number): number => {
const safeValue = Number.isFinite(value) ? value : DEFAULT_MIRROR_SEGMENT_COUNT;
return Math.min(
MAX_MIRROR_SEGMENT_COUNT,
Math.max(MIN_MIRROR_SEGMENT_COUNT, Math.round(safeValue))
);
};
const getMirrorSegmentRatio = (count: number): number => {
return (
(count - MIN_MIRROR_SEGMENT_COUNT) /
(MAX_MIRROR_SEGMENT_COUNT - MIN_MIRROR_SEGMENT_COUNT)
);
};
const formatMirrorSegmentCount = (count: number): string =>
count <= 1
? MIRROR_SEGMENT_OFF_LABEL
: `${count} ${
MIRROR_SEGMENT_NAMES[count as keyof typeof MIRROR_SEGMENT_NAMES] ??
MIRROR_SEGMENT_FALLBACK_NAME
}`;
interface MirrorSegmentControlOptions {
onChange: () => void;
}
export class MirrorSegmentControl {
private readonly control = queryRequiredElement(
'.mirror-segment-control',
HTMLLabelElement
);
private readonly slider = queryRequiredElement(
'.mirror-segment-slider',
HTMLInputElement
);
public constructor(private readonly options: MirrorSegmentControlOptions) {
this.slider.addEventListener('input', () => {
settings.mirrorSegmentCount = clampMirrorSegmentCount(Number(this.slider.value));
this.render();
this.options.onChange();
});
}
public render(): void {
const count = clampMirrorSegmentCount(settings.mirrorSegmentCount);
if (settings.mirrorSegmentCount !== count) {
settings.mirrorSegmentCount = count;
}
this.slider.min = MIN_MIRROR_SEGMENT_COUNT.toString();
this.slider.max = MAX_MIRROR_SEGMENT_COUNT.toString();
this.slider.step = MIRROR_SEGMENT_STEP.toString();
this.slider.value = count.toString();
const label = formatMirrorSegmentCount(count);
const ratio = getMirrorSegmentRatio(count);
this.slider.setAttribute('aria-valuetext', label);
this.control.title = label;
this.control.classList.toggle('active', count > 1);
this.control.style.setProperty('--mirror-progress', `${ratio * 100}%`);
this.control.style.setProperty('--mirror-angle', `${(360 / count).toFixed(3)}deg`);
}
}