@jjlmoya/utils-motor 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/category/index.ts +3 -1
- package/src/entries.ts +5 -1
- package/src/index.ts +10 -0
- package/src/tests/locale_completeness.test.ts +1 -1
- package/src/tests/tool_validation.test.ts +1 -1
- package/src/tool/evChargingTimeCostPlanner/bibliography.astro +6 -0
- package/src/tool/evChargingTimeCostPlanner/bibliography.ts +16 -0
- package/src/tool/evChargingTimeCostPlanner/component.astro +147 -0
- package/src/tool/evChargingTimeCostPlanner/controller.ts +214 -0
- package/src/tool/evChargingTimeCostPlanner/dom-views.ts +124 -0
- package/src/tool/evChargingTimeCostPlanner/entry.ts +30 -0
- package/src/tool/evChargingTimeCostPlanner/ev-charging-time-cost-planner.css +598 -0
- package/src/tool/evChargingTimeCostPlanner/evaluator.ts +10 -0
- package/src/tool/evChargingTimeCostPlanner/i18n/de.ts +22 -0
- package/src/tool/evChargingTimeCostPlanner/i18n/en.ts +161 -0
- package/src/tool/evChargingTimeCostPlanner/i18n/es.ts +16 -0
- package/src/tool/evChargingTimeCostPlanner/i18n/factory.ts +113 -0
- package/src/tool/evChargingTimeCostPlanner/i18n/fr.ts +9 -0
- package/src/tool/evChargingTimeCostPlanner/i18n/id.ts +9 -0
- package/src/tool/evChargingTimeCostPlanner/i18n/it.ts +9 -0
- package/src/tool/evChargingTimeCostPlanner/i18n/ja.ts +13 -0
- package/src/tool/evChargingTimeCostPlanner/i18n/ko.ts +13 -0
- package/src/tool/evChargingTimeCostPlanner/i18n/nl.ts +9 -0
- package/src/tool/evChargingTimeCostPlanner/i18n/pl.ts +9 -0
- package/src/tool/evChargingTimeCostPlanner/i18n/pt.ts +9 -0
- package/src/tool/evChargingTimeCostPlanner/i18n/ru.ts +13 -0
- package/src/tool/evChargingTimeCostPlanner/i18n/sv.ts +9 -0
- package/src/tool/evChargingTimeCostPlanner/i18n/tr.ts +9 -0
- package/src/tool/evChargingTimeCostPlanner/i18n/zh.ts +13 -0
- package/src/tool/evChargingTimeCostPlanner/index.ts +10 -0
- package/src/tool/evChargingTimeCostPlanner/logic.test.ts +71 -0
- package/src/tool/evChargingTimeCostPlanner/logic.ts +174 -0
- package/src/tool/evChargingTimeCostPlanner/seo.astro +15 -0
- package/src/tool/evChargingTimeCostPlanner/storage.ts +27 -0
- package/src/tool/evChargingTimeCostPlanner/ui.ts +50 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/bibliography.astro +6 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/bibliography.ts +12 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/component.astro +166 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/controller.ts +207 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/dom-views.ts +101 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/entry.ts +30 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/evaluator.ts +7 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/i18n/de.ts +185 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/i18n/en.ts +185 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/i18n/es.ts +185 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/i18n/fr.ts +185 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/i18n/id.ts +185 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/i18n/it.ts +185 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/i18n/ja.ts +201 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/i18n/ko.ts +201 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/i18n/nl.ts +185 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/i18n/pl.ts +185 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/i18n/pt.ts +185 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/i18n/ru.ts +185 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/i18n/sv.ts +185 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/i18n/tr.ts +185 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/i18n/zh.ts +201 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/index.ts +10 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/logic.test.ts +229 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/logic.ts +179 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/motorcycle-skid-mark-speed-estimator.css +523 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/seo.astro +15 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/storage.ts +49 -0
- package/src/tool/motorcycleSkidMarkSpeedEstimator/ui.ts +46 -0
- package/src/tools.ts +5 -1
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { applyScene, buildSceneView, formatFriction, formatGrade, formatLength, lengthUnit } from './dom-views';
|
|
2
|
+
import { evaluateSkid } from './evaluator';
|
|
3
|
+
import {
|
|
4
|
+
clamp,
|
|
5
|
+
estimateSkidSpeed,
|
|
6
|
+
getSurfaceSpec,
|
|
7
|
+
isBrakeUse,
|
|
8
|
+
isSurfaceId,
|
|
9
|
+
isUnitSystem,
|
|
10
|
+
lengthToMeters,
|
|
11
|
+
MAX_FRICTION,
|
|
12
|
+
MAX_GRADE,
|
|
13
|
+
MAX_LENGTH_M,
|
|
14
|
+
MIN_FRICTION,
|
|
15
|
+
MIN_GRADE,
|
|
16
|
+
MIN_LENGTH_M,
|
|
17
|
+
parsePositiveNumber,
|
|
18
|
+
parseSignedNumber,
|
|
19
|
+
type BrakeUse,
|
|
20
|
+
type SkidInput,
|
|
21
|
+
type SurfaceId,
|
|
22
|
+
type UnitSystem,
|
|
23
|
+
} from './logic';
|
|
24
|
+
import { loadSkidState, saveSkidState } from './storage';
|
|
25
|
+
|
|
26
|
+
type SkidUI = Record<string, string>;
|
|
27
|
+
|
|
28
|
+
interface SkidState {
|
|
29
|
+
lengthMeters: number;
|
|
30
|
+
unit: UnitSystem;
|
|
31
|
+
surface: SurfaceId;
|
|
32
|
+
friction: number;
|
|
33
|
+
gradePercent: number;
|
|
34
|
+
brakeUse: BrakeUse;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const DEFAULT_STATE: SkidState = {
|
|
38
|
+
lengthMeters: 18,
|
|
39
|
+
unit: 'metric',
|
|
40
|
+
surface: 'dry-asphalt',
|
|
41
|
+
friction: 0.7,
|
|
42
|
+
gradePercent: 0,
|
|
43
|
+
brakeUse: 'both',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function toInput(state: SkidState): SkidInput {
|
|
47
|
+
return {
|
|
48
|
+
lengthMeters: state.lengthMeters,
|
|
49
|
+
friction: state.friction,
|
|
50
|
+
gradePercent: state.gradePercent,
|
|
51
|
+
surface: state.surface,
|
|
52
|
+
brakeUse: state.brakeUse,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function restoreState(): SkidState {
|
|
57
|
+
const saved = loadSkidState();
|
|
58
|
+
return saved ? { ...DEFAULT_STATE, ...saved } : { ...DEFAULT_STATE };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function queryEl(root: HTMLElement, selector: string): HTMLElement | null {
|
|
62
|
+
return root.querySelector(selector);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function queryInput(root: HTMLElement, selector: string): HTMLInputElement | null {
|
|
66
|
+
return root.querySelector(selector);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function bindHandle(root: HTMLElement, state: SkidState, ui: SkidUI): void {
|
|
70
|
+
const road = queryEl(root, '[data-trace-road]');
|
|
71
|
+
if (!road) return;
|
|
72
|
+
const move = (event: PointerEvent) => {
|
|
73
|
+
const box = road.getBoundingClientRect();
|
|
74
|
+
if (box.width <= 0) return;
|
|
75
|
+
const ratio = clamp((event.clientX - box.left) / box.width, 0.18, 0.92);
|
|
76
|
+
const meters = MIN_LENGTH_M + ((ratio - 0.18) / 0.74) * (MAX_LENGTH_M - MIN_LENGTH_M);
|
|
77
|
+
state.lengthMeters = clamp(meters, MIN_LENGTH_M, MAX_LENGTH_M);
|
|
78
|
+
render(root, state, ui);
|
|
79
|
+
};
|
|
80
|
+
road.addEventListener('pointerdown', (event) => {
|
|
81
|
+
const target = event.target;
|
|
82
|
+
if (target instanceof Element && target.closest('button, label, input, [data-brake-choice]')) return;
|
|
83
|
+
road.setPointerCapture(event.pointerId);
|
|
84
|
+
move(event);
|
|
85
|
+
});
|
|
86
|
+
road.addEventListener('pointermove', (event) => {
|
|
87
|
+
if (!road.hasPointerCapture(event.pointerId)) return;
|
|
88
|
+
move(event);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function markChoice(root: HTMLElement, attr: string, value: string, on: string): void {
|
|
93
|
+
root.querySelectorAll(`[${attr}]`).forEach((node) => {
|
|
94
|
+
node.classList.toggle(on, node.getAttribute(attr) === value);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function syncFields(root: HTMLElement, state: SkidState, ui: SkidUI): void {
|
|
99
|
+
const length = queryInput(root, '[data-trace-length]');
|
|
100
|
+
const grade = queryInput(root, '[data-trace-grade]');
|
|
101
|
+
const friction = queryInput(root, '[data-trace-friction]');
|
|
102
|
+
const frictionNumber = queryInput(root, '[data-trace-friction-number]');
|
|
103
|
+
const lengthUnitNode = queryEl(root, '[data-length-unit]');
|
|
104
|
+
const gradeValue = queryEl(root, '[data-grade-value]');
|
|
105
|
+
if (length) length.value = formatLength(state.lengthMeters, state.unit);
|
|
106
|
+
if (grade) grade.value = String(state.gradePercent);
|
|
107
|
+
if (friction) friction.value = formatFriction(state.friction);
|
|
108
|
+
if (frictionNumber) frictionNumber.value = formatFriction(state.friction);
|
|
109
|
+
if (lengthUnitNode) lengthUnitNode.textContent = lengthUnit(ui, state.unit);
|
|
110
|
+
if (gradeValue) gradeValue.textContent = `${formatGrade(state.gradePercent)}${ui.percentSuffix ?? '%'}`;
|
|
111
|
+
root.dataset.unit = state.unit;
|
|
112
|
+
root.dataset.brake = state.brakeUse;
|
|
113
|
+
markChoice(root, 'data-unit-choice', state.unit, 'is-stencil');
|
|
114
|
+
markChoice(root, 'data-surface-choice', state.surface, 'is-core');
|
|
115
|
+
markChoice(root, 'data-brake-choice', state.brakeUse, 'is-lock');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function render(root: HTMLElement, state: SkidState, ui: SkidUI): void {
|
|
119
|
+
const input = toInput(state);
|
|
120
|
+
root.dataset.eval = evaluateSkid(input);
|
|
121
|
+
applyScene(root, buildSceneView(input, estimateSkidSpeed(input), state.unit, ui));
|
|
122
|
+
syncFields(root, state, ui);
|
|
123
|
+
saveSkidState(state);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function onLengthInput(state: SkidState, raw: string): void {
|
|
127
|
+
const parsed = parsePositiveNumber(raw);
|
|
128
|
+
if (parsed === null) return;
|
|
129
|
+
state.lengthMeters = clamp(lengthToMeters(parsed, state.unit), MIN_LENGTH_M, MAX_LENGTH_M);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function onGradeInput(state: SkidState, raw: string): void {
|
|
133
|
+
const parsed = parseSignedNumber(raw);
|
|
134
|
+
if (parsed === null) return;
|
|
135
|
+
state.gradePercent = clamp(parsed, MIN_GRADE, MAX_GRADE);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function onFrictionInput(state: SkidState, raw: string): void {
|
|
139
|
+
const parsed = parsePositiveNumber(raw);
|
|
140
|
+
if (parsed === null) return;
|
|
141
|
+
state.friction = clamp(parsed, MIN_FRICTION, MAX_FRICTION);
|
|
142
|
+
state.surface = 'custom';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function onSurfaceChoice(state: SkidState, raw: string): void {
|
|
146
|
+
if (!isSurfaceId(raw)) return;
|
|
147
|
+
state.surface = raw;
|
|
148
|
+
if (raw === 'custom') return;
|
|
149
|
+
state.friction = getSurfaceSpec(raw).mu;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function onUnitChoice(state: SkidState, raw: string): void {
|
|
153
|
+
if (!isUnitSystem(raw)) return;
|
|
154
|
+
state.unit = raw;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function onBrakeChoice(state: SkidState, raw: string): void {
|
|
158
|
+
if (!isBrakeUse(raw)) return;
|
|
159
|
+
state.brakeUse = raw;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function bindClicks(root: HTMLElement, attr: string, apply: (value: string) => void): void {
|
|
163
|
+
root.querySelectorAll(`[${attr}]`).forEach((node) => {
|
|
164
|
+
node.addEventListener('click', (event) => {
|
|
165
|
+
event.stopPropagation();
|
|
166
|
+
const value = node.getAttribute(attr);
|
|
167
|
+
if (value) apply(value);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function bindNumber(
|
|
173
|
+
root: HTMLElement,
|
|
174
|
+
bind: { selector: string; apply: (raw: string) => void; state: SkidState; ui: SkidUI },
|
|
175
|
+
): void {
|
|
176
|
+
const input = queryInput(root, bind.selector);
|
|
177
|
+
input?.addEventListener('input', () => {
|
|
178
|
+
bind.apply(input.value);
|
|
179
|
+
render(root, bind.state, bind.ui);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function bindInputs(root: HTMLElement, state: SkidState, ui: SkidUI): void {
|
|
184
|
+
bindNumber(root, { selector: '[data-trace-length]', apply: (raw) => onLengthInput(state, raw), state, ui });
|
|
185
|
+
bindNumber(root, { selector: '[data-trace-grade]', apply: (raw) => onGradeInput(state, raw), state, ui });
|
|
186
|
+
bindNumber(root, { selector: '[data-trace-friction]', apply: (raw) => onFrictionInput(state, raw), state, ui });
|
|
187
|
+
bindNumber(root, { selector: '[data-trace-friction-number]', apply: (raw) => onFrictionInput(state, raw), state, ui });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function mountSkidEstimator(root: HTMLElement, ui: SkidUI): void {
|
|
191
|
+
const state = restoreState();
|
|
192
|
+
bindInputs(root, state, ui);
|
|
193
|
+
bindClicks(root, 'data-unit-choice', (value) => {
|
|
194
|
+
onUnitChoice(state, value);
|
|
195
|
+
render(root, state, ui);
|
|
196
|
+
});
|
|
197
|
+
bindClicks(root, 'data-surface-choice', (value) => {
|
|
198
|
+
onSurfaceChoice(state, value);
|
|
199
|
+
render(root, state, ui);
|
|
200
|
+
});
|
|
201
|
+
bindClicks(root, 'data-brake-choice', (value) => {
|
|
202
|
+
onBrakeChoice(state, value);
|
|
203
|
+
render(root, state, ui);
|
|
204
|
+
});
|
|
205
|
+
bindHandle(root, state, ui);
|
|
206
|
+
render(root, state, ui);
|
|
207
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
gradeTilt,
|
|
3
|
+
markRatio,
|
|
4
|
+
metersToLength,
|
|
5
|
+
speedFromMps,
|
|
6
|
+
type SkidEstimate,
|
|
7
|
+
type SkidInput,
|
|
8
|
+
type UnitSystem,
|
|
9
|
+
} from './logic';
|
|
10
|
+
|
|
11
|
+
function uiText(ui: Record<string, string>, key: string): string {
|
|
12
|
+
return ui[key] ?? '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function formatSpeed(mps: number, unit: UnitSystem): string {
|
|
16
|
+
const value = speedFromMps(mps, unit);
|
|
17
|
+
return value >= 100 ? value.toFixed(0) : value.toFixed(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function formatLength(meters: number, unit: UnitSystem): string {
|
|
21
|
+
return metersToLength(meters, unit).toFixed(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function formatFriction(value: number): string {
|
|
25
|
+
return value.toFixed(2);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function formatGrade(value: number): string {
|
|
29
|
+
if (Object.is(value, -0) || value === 0) return '0';
|
|
30
|
+
return value.toFixed(0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function speedUnit(ui: Record<string, string>, unit: UnitSystem): string {
|
|
34
|
+
return unit === 'imperial' ? uiText(ui, 'stencilUnitMph') : uiText(ui, 'stencilUnitKmh');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function lengthUnit(ui: Record<string, string>, unit: UnitSystem): string {
|
|
38
|
+
return unit === 'imperial' ? uiText(ui, 'lengthUnitFt') : uiText(ui, 'lengthUnitM');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function gradeCaption(gradePercent: number, ui: Record<string, string>): string {
|
|
42
|
+
if (gradePercent > 0) return uiText(ui, 'gradeUphill');
|
|
43
|
+
if (gradePercent < 0) return uiText(ui, 'gradeDownhill');
|
|
44
|
+
return uiText(ui, 'gradeLevel');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function formatRange(lowMps: number, highMps: number, unit: UnitSystem): string {
|
|
48
|
+
return `${formatSpeed(lowMps, unit)} to ${formatSpeed(highMps, unit)}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface SceneView {
|
|
52
|
+
tilt: number;
|
|
53
|
+
needle: number;
|
|
54
|
+
mark: number;
|
|
55
|
+
surface: string;
|
|
56
|
+
speedText: string;
|
|
57
|
+
rangeText: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function emptyScene(input: SkidInput, ui: Record<string, string>): SceneView {
|
|
61
|
+
return {
|
|
62
|
+
tilt: gradeTilt(input.gradePercent),
|
|
63
|
+
needle: input.gradePercent * 1.7,
|
|
64
|
+
mark: markRatio(input.lengthMeters),
|
|
65
|
+
surface: input.surface,
|
|
66
|
+
speedText: uiText(ui, 'invalidMessage'),
|
|
67
|
+
rangeText: '',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function buildSceneView(
|
|
72
|
+
input: SkidInput,
|
|
73
|
+
estimate: SkidEstimate,
|
|
74
|
+
unit: UnitSystem,
|
|
75
|
+
ui: Record<string, string>,
|
|
76
|
+
): SceneView {
|
|
77
|
+
if (estimate.status === 'invalid' || !estimate.band) return emptyScene(input, ui);
|
|
78
|
+
const unitLabel = speedUnit(ui, unit);
|
|
79
|
+
return {
|
|
80
|
+
tilt: gradeTilt(input.gradePercent),
|
|
81
|
+
needle: input.gradePercent * 1.7,
|
|
82
|
+
mark: markRatio(input.lengthMeters),
|
|
83
|
+
surface: input.surface,
|
|
84
|
+
speedText: `${formatSpeed(estimate.band.midMps, unit)} ${unitLabel}`,
|
|
85
|
+
rangeText: `${formatRange(estimate.band.lowMps, estimate.band.highMps, unit)} ${unitLabel}`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function applyScene(root: HTMLElement, view: SceneView): void {
|
|
90
|
+
root.dataset.surface = view.surface;
|
|
91
|
+
root.style.setProperty('--trace-tilt', `${view.tilt}deg`);
|
|
92
|
+
root.style.setProperty('--trace-needle', `${view.needle}deg`);
|
|
93
|
+
root.style.setProperty('--trace-span', view.mark.toFixed(3));
|
|
94
|
+
writeText(root, '[data-trace-speed]', view.speedText);
|
|
95
|
+
writeText(root, '[data-trace-range]', view.rangeText);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function writeText(root: HTMLElement, selector: string, text: string): void {
|
|
99
|
+
const node = root.querySelector(selector);
|
|
100
|
+
if (node) node.textContent = text;
|
|
101
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { MotorToolEntry, ToolLocaleContent } from '../../types';
|
|
2
|
+
import type { MotorcycleSkidMarkSpeedEstimatorUI } from './ui';
|
|
3
|
+
|
|
4
|
+
export type { MotorcycleSkidMarkSpeedEstimatorUI };
|
|
5
|
+
export type MotorcycleSkidMarkSpeedEstimatorLocaleContent = ToolLocaleContent<MotorcycleSkidMarkSpeedEstimatorUI>;
|
|
6
|
+
|
|
7
|
+
export const motorcycleSkidMarkSpeedEstimator: MotorToolEntry<MotorcycleSkidMarkSpeedEstimatorUI> = {
|
|
8
|
+
id: 'motorcycle-skid-mark-speed-estimator',
|
|
9
|
+
icons: {
|
|
10
|
+
bg: 'mdi:motorbike',
|
|
11
|
+
fg: 'mdi:road',
|
|
12
|
+
},
|
|
13
|
+
i18n: {
|
|
14
|
+
de: () => import('./i18n/de').then((module) => module.content),
|
|
15
|
+
en: () => import('./i18n/en').then((module) => module.content),
|
|
16
|
+
es: () => import('./i18n/es').then((module) => module.content),
|
|
17
|
+
fr: () => import('./i18n/fr').then((module) => module.content),
|
|
18
|
+
id: () => import('./i18n/id').then((module) => module.content),
|
|
19
|
+
it: () => import('./i18n/it').then((module) => module.content),
|
|
20
|
+
ja: () => import('./i18n/ja').then((module) => module.content),
|
|
21
|
+
ko: () => import('./i18n/ko').then((module) => module.content),
|
|
22
|
+
nl: () => import('./i18n/nl').then((module) => module.content),
|
|
23
|
+
pl: () => import('./i18n/pl').then((module) => module.content),
|
|
24
|
+
pt: () => import('./i18n/pt').then((module) => module.content),
|
|
25
|
+
ru: () => import('./i18n/ru').then((module) => module.content),
|
|
26
|
+
sv: () => import('./i18n/sv').then((module) => module.content),
|
|
27
|
+
tr: () => import('./i18n/tr').then((module) => module.content),
|
|
28
|
+
zh: () => import('./i18n/zh').then((module) => module.content),
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
|
|
2
|
+
import type { SEOSection } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { bibliographyEntries } from '../bibliography';
|
|
4
|
+
import type { MotorcycleSkidMarkSpeedEstimatorLocaleContent } from '../entry';
|
|
5
|
+
|
|
6
|
+
const slug = 'motorrad-bremsspur-geschwindigkeitsrechner';
|
|
7
|
+
const title = 'Motorrad Bremsspur Geschwindigkeitsrechner';
|
|
8
|
+
const description =
|
|
9
|
+
'Schätzen Sie die Motorradgeschwindigkeit anhand der Bremsspurlänge, Straßenreibung, Steigung und Bremsart. Ein lehrreiches Physikmodell zur Unfallanalyse.';
|
|
10
|
+
|
|
11
|
+
const faq = [
|
|
12
|
+
{
|
|
13
|
+
question: 'Welche Geschwindigkeit zeigt eine Bremsspur an?',
|
|
14
|
+
answer:
|
|
15
|
+
'Eine blockierte Reifenbremsspur misst die Distanz zur Umwandlung von kinetischer Energie in Reibungswärme. Die physikalische Energiegleichung ermittelt die Mindestgeschwindigkeit zu Beginn der sichtbaren Spur und nicht die ursprüngliche Fahrgeschwindigkeit vor Betätigung der Bremse.',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
question: 'Warum wird ein Geschwindigkeitsbereich angegeben?',
|
|
19
|
+
answer:
|
|
20
|
+
'Reibwerte von Straßenbelägen unterliegen Schwankungen, und die Bremskraftverteilung zwischen Vorder- und Hinterrad variiert je nach Fahrer. Der Rechner ermittelt daher Unter-, Mittel- und Oberwerte für eine realistische Einschätzung der Messunsicherheit.',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
question: 'Ist das Ergebnis als offizielles Unfallgutachten zulässig?',
|
|
24
|
+
answer:
|
|
25
|
+
'Nein. Ein gerichtsfestes Gutachten erfordert Messungen mit einem Verzögerungsschlitten vor Ort, Spuridentifizierung, ABS-Verhaltensanalysen und Sachverständige. Diese Anwendung dient ausschließlich als physikalisches Lernmodell.',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
question: 'Wie unterscheidet sich dies von einem Bremswegrechner?',
|
|
29
|
+
answer:
|
|
30
|
+
'Ein klassischer Bremswegrechner geht von einer bekannten Geschwindigkeit aus und berechnet den Anhalteweg. Dieses Werkzeug rechnet umgekehrt von einer gemessenen Reifenspur auf die Ausgangsgeschwindigkeit zurück.',
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const howTo = [
|
|
35
|
+
{ name: 'Spur messen', text: 'Messen Sie die sichtbare Blockierspur des Reifenabriebs präzise in Metern oder Fuß.' },
|
|
36
|
+
{ name: 'Fahrbahn wählen', text: 'Wählen Sie den entsprechenden Straßenbelag oder geben Sie einen gemessenen Reibwert manuell ein.' },
|
|
37
|
+
{ name: 'Neigung und Bremse einstellen', text: 'Geben Sie die Steigung der Straße an und wählen Sie, welche Räder blockiert haben.' },
|
|
38
|
+
{ name: 'Ergebnis ablesen', text: 'Nutzen Sie den Mittelwert als Richtwert und beachten Sie die angezeigte Schwankungsbreite.' },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const seo: SEOSection[] = [
|
|
42
|
+
{ type: 'title', level: 2, text: 'Aus einer Reifenabriebspur die Geschwindigkeit berechnen' },
|
|
43
|
+
{
|
|
44
|
+
type: 'paragraph',
|
|
45
|
+
html: 'Ein Motorrad hinterlässt bei einer Gefahrenbremsung häufig eine deutliche Spur des Hinterrades, eine schwächere Linie des Vorderrades oder eine unterbrochene ABS-Spur. Gemessen wird ausschließlich der Bereich mit vollständig blockiertem Reifen, nicht jedoch das spätere Rutschen des Motorrads auf der Verkleidung nach einem Sturz.',
|
|
46
|
+
},
|
|
47
|
+
{ type: 'title', level: 3, text: 'Physikalische Grundlagen der Geschwindigkeitsbestimmung' },
|
|
48
|
+
{
|
|
49
|
+
type: 'paragraph',
|
|
50
|
+
html: 'Das zugrundeliegende Modell nutzt die Energiebilanz des Rutschvorgangs: Die Geschwindigkeit entspricht der Quadratwurzel aus dem zweifachen Produkt der Erdbeschleunigung, des Reibungsbeiwerts und der gemessenen Spurlänge. Die Steigung der Fahrbahn fließt als Vektorkomponente ein. Eine Steigung unterstützt die Bremsung, während ein Gefälle den Bremsweg verlängert.',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: 'list',
|
|
54
|
+
items: [
|
|
55
|
+
'Alle Längenangaben werden intern exakt in Meter umgerechnet.',
|
|
56
|
+
'Integrierte Richtwerte für trockenen und nassen Asphalt, Beton, Schotter und Eis.',
|
|
57
|
+
'Berücksichtigung der Achslastverlagerung bei reiner Vorder- oder Hinterradbremsung.',
|
|
58
|
+
'Bei unbestimmter Bremsung wird die Spanne von der günstigsten bis zur ungünstigsten Annahme erweitert.',
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: 'tip',
|
|
63
|
+
title: 'Nur den blockierten Reifenabschnitt messen',
|
|
64
|
+
html: 'Beginnen Sie die Messung am ersten sichtbaren Reifenabrieb und beenden Sie sie dort, wo das Rad wieder rollt oder der Aufprall stattfindet. Addieren Sie nicht die Rutschstrecke des Motorrads auf der Karosserie.',
|
|
65
|
+
},
|
|
66
|
+
{ type: 'title', level: 3, text: 'Typische Reibbeiwerte im Straßenverkehr' },
|
|
67
|
+
{
|
|
68
|
+
type: 'table',
|
|
69
|
+
headers: ['Oberfläche', 'Mittlerer Reibbeiwert', 'Schwankungsbereich'],
|
|
70
|
+
rows: [
|
|
71
|
+
['Trockener Asphalt', '0.70', '0.55 bis 0.85'],
|
|
72
|
+
['Nasser Asphalt', '0.50', '0.35 bis 0.65'],
|
|
73
|
+
['Trockener Beton', '0.75', '0.60 bis 0.90'],
|
|
74
|
+
['Kies oder Schotter', '0.50', '0.35 bis 0.70'],
|
|
75
|
+
['Erde oder Gras', '0.40', '0.25 bis 0.55'],
|
|
76
|
+
['Eisfläche', '0.10', '0.05 bis 0.18'],
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
type: 'paragraph',
|
|
81
|
+
html: 'Diese Werte dienen der Veranschaulichung des Reibungsverhaltens blockierter Gummireifen auf unterschiedlichen Untergründen. Eine messtechnische Bestimmung vor Ort ist Tabellenwerten stets vorzuziehen.',
|
|
82
|
+
},
|
|
83
|
+
{ type: 'title', level: 3, text: 'Besonderheiten des Motorrad-Bremssystems' },
|
|
84
|
+
{
|
|
85
|
+
type: 'paragraph',
|
|
86
|
+
html: 'Da Motorräder getrennte Bremsbetätigungen für Vorder- und Hinterrad besitzen, erlaubt eine reine Hinterradspur keinen Rückschluss auf die Ausnutzung des Gesamtpotenzials. Der Rechner ermöglicht daher die getrennte Auswertung der Achsen.',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: 'diagnostic',
|
|
90
|
+
variant: 'warning',
|
|
91
|
+
title: 'Didaktisches Physikmodell',
|
|
92
|
+
badge: 'Lehrmodell',
|
|
93
|
+
html: 'ABS-Intervalle, Reifenverschleiß, Beladung und Aufprallenergie verändern die tatsächliche Ausgangsgeschwindigkeit. Bei extremem Gefälle stoppt das Modell die Berechnung aufgrund physikalischer Unmöglichkeit.',
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
const schemas: WithContext<SoftwareApplication | FAQPage | HowTo>[] = [
|
|
98
|
+
{
|
|
99
|
+
'@context': 'https://schema.org',
|
|
100
|
+
'@type': 'SoftwareApplication',
|
|
101
|
+
name: title,
|
|
102
|
+
applicationCategory: 'UtilitiesApplication',
|
|
103
|
+
operatingSystem: 'Web',
|
|
104
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
105
|
+
description,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
'@context': 'https://schema.org',
|
|
109
|
+
'@type': 'FAQPage',
|
|
110
|
+
mainEntity: faq.map((item) => ({
|
|
111
|
+
'@type': 'Question',
|
|
112
|
+
name: item.question,
|
|
113
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
114
|
+
})),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
'@context': 'https://schema.org',
|
|
118
|
+
'@type': 'HowTo',
|
|
119
|
+
name: title,
|
|
120
|
+
step: howTo.map((step) => ({
|
|
121
|
+
'@type': 'HowToStep',
|
|
122
|
+
name: step.name,
|
|
123
|
+
text: step.text,
|
|
124
|
+
})),
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
export const content: MotorcycleSkidMarkSpeedEstimatorLocaleContent = {
|
|
129
|
+
slug,
|
|
130
|
+
title,
|
|
131
|
+
description,
|
|
132
|
+
ui: {
|
|
133
|
+
unitMetric: 'Metrisch',
|
|
134
|
+
unitImperial: 'Imperial',
|
|
135
|
+
disclaimer: 'Nur für Bildungs- und Unterrichtszwecke.',
|
|
136
|
+
lengthLabel: 'Spurlänge',
|
|
137
|
+
lengthHint: 'Ziehen Sie die Bremsspur auf der Fahrbahn.',
|
|
138
|
+
gradeLabel: 'Steigung',
|
|
139
|
+
gradeHint: 'Bergan hilft beim Bremsen, bergab verlängert den Weg.',
|
|
140
|
+
surfaceLabel: 'Fahrbahnbelag',
|
|
141
|
+
frictionLabel: 'Reibwert',
|
|
142
|
+
frictionHint: 'Wählen Sie einen Belag oder geben Sie den Reibbeiwert an.',
|
|
143
|
+
brakeLabel: 'Blockiertes Rad',
|
|
144
|
+
brakeBoth: 'Beide Räder',
|
|
145
|
+
brakeFront: 'Vorderrad',
|
|
146
|
+
brakeRear: 'Hinterrad',
|
|
147
|
+
brakeUnknown: 'Unbekannt',
|
|
148
|
+
speedLabel: 'Geschwindigkeit auf der Spur',
|
|
149
|
+
rangeLabel: 'Toleranzbereich',
|
|
150
|
+
dragHint: 'Ziehen Sie die Spur auf der Fahrbahn.',
|
|
151
|
+
emptyMessage: 'Ziehen Sie die Spur zur Geschwindigkeitsberechnung.',
|
|
152
|
+
invalidMessage: 'Zu steiles Gefälle. Bremsung physikalisch nicht möglich.',
|
|
153
|
+
readyMessage: 'Geschwindigkeit berechnet.',
|
|
154
|
+
educationalBadge: 'Lehrmodell',
|
|
155
|
+
surfaceDryAsphalt: 'Trockener Asphalt',
|
|
156
|
+
surfaceWetAsphalt: 'Nasser Asphalt',
|
|
157
|
+
surfaceDryConcrete: 'Trockener Beton',
|
|
158
|
+
surfaceGravel: 'Kies',
|
|
159
|
+
surfaceGrass: 'Erde',
|
|
160
|
+
surfaceIce: 'Eis',
|
|
161
|
+
surfaceCustom: 'Manuell',
|
|
162
|
+
sensitivityLabel: 'Schätzbereich',
|
|
163
|
+
lowAssumption: 'Niedrig',
|
|
164
|
+
highAssumption: 'Hoch',
|
|
165
|
+
gradeUphill: 'Steigung',
|
|
166
|
+
gradeDownhill: 'Gefälle',
|
|
167
|
+
gradeLevel: 'Eben',
|
|
168
|
+
measureHint: 'Vor-Ort-Messungen ersetzen Richtwerttabellen.',
|
|
169
|
+
stencilUnitKmh: 'km/h',
|
|
170
|
+
stencilUnitMph: 'mph',
|
|
171
|
+
lengthUnitM: 'm',
|
|
172
|
+
lengthUnitFt: 'ft',
|
|
173
|
+
percentSuffix: '%',
|
|
174
|
+
dragLowLabel: 'Geringe Reibung',
|
|
175
|
+
dragMidLabel: 'Mittlere Reibung',
|
|
176
|
+
dragHighLabel: 'Hohe Reibung',
|
|
177
|
+
},
|
|
178
|
+
seo,
|
|
179
|
+
faqTitle: 'Häufige Fragen zur Bremsspurberechnung',
|
|
180
|
+
faq,
|
|
181
|
+
bibliographyTitle: 'Quellen',
|
|
182
|
+
bibliography: bibliographyEntries,
|
|
183
|
+
howTo,
|
|
184
|
+
schemas,
|
|
185
|
+
};
|