@jjlmoya/utils-motor 1.1.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.
Files changed (82) hide show
  1. package/package.json +68 -0
  2. package/scripts/postinstall.mjs +27 -0
  3. package/src/category/i18n/de.ts +17 -0
  4. package/src/category/i18n/en.ts +17 -0
  5. package/src/category/i18n/es.ts +17 -0
  6. package/src/category/i18n/fr.ts +17 -0
  7. package/src/category/i18n/id.ts +17 -0
  8. package/src/category/i18n/it.ts +17 -0
  9. package/src/category/i18n/ja.ts +17 -0
  10. package/src/category/i18n/ko.ts +17 -0
  11. package/src/category/i18n/nl.ts +17 -0
  12. package/src/category/i18n/pl.ts +17 -0
  13. package/src/category/i18n/pt.ts +17 -0
  14. package/src/category/i18n/ru.ts +17 -0
  15. package/src/category/i18n/sv.ts +17 -0
  16. package/src/category/i18n/tr.ts +17 -0
  17. package/src/category/i18n/zh.ts +17 -0
  18. package/src/category/index.ts +24 -0
  19. package/src/category/seo.astro +15 -0
  20. package/src/components/PreviewNavSidebar.astro +116 -0
  21. package/src/components/PreviewToolbar.astro +143 -0
  22. package/src/data.ts +11 -0
  23. package/src/entries.ts +7 -0
  24. package/src/env.d.ts +5 -0
  25. package/src/index.ts +23 -0
  26. package/src/layouts/PreviewLayout.astro +117 -0
  27. package/src/pages/[locale]/[slug].astro +153 -0
  28. package/src/pages/[locale].astro +251 -0
  29. package/src/pages/index.astro +4 -0
  30. package/src/tests/diacritics_density.test.ts +118 -0
  31. package/src/tests/faq_count.test.ts +19 -0
  32. package/src/tests/i18n_coverage.test.ts +36 -0
  33. package/src/tests/inverted_punctuation.test.ts +84 -0
  34. package/src/tests/locale_completeness.test.ts +29 -0
  35. package/src/tests/mocks/astro_mock.js +2 -0
  36. package/src/tests/no_en_dash.test.ts +70 -0
  37. package/src/tests/no_h1_in_components.test.ts +48 -0
  38. package/src/tests/pagespeed_best_practices.test.ts +198 -0
  39. package/src/tests/schemas_fulfillment.test.ts +23 -0
  40. package/src/tests/script_density.test.ts +94 -0
  41. package/src/tests/seo_length.test.ts +23 -0
  42. package/src/tests/seo_parity.test.ts +60 -0
  43. package/src/tests/shared-test-helpers.ts +56 -0
  44. package/src/tests/slug_language_code_format.test.ts +23 -0
  45. package/src/tests/slug_uniqueness.test.ts +81 -0
  46. package/src/tests/spanish_leakage.test.ts +177 -0
  47. package/src/tests/title_quality.test.ts +55 -0
  48. package/src/tests/tool_exports.test.ts +34 -0
  49. package/src/tests/tool_validation.test.ts +17 -0
  50. package/src/tests/translation_copy.test.ts +125 -0
  51. package/src/tool/brakingDistanceCalculator/bibliography.astro +6 -0
  52. package/src/tool/brakingDistanceCalculator/bibliography.ts +12 -0
  53. package/src/tool/brakingDistanceCalculator/braking-distance-calculator.css +601 -0
  54. package/src/tool/brakingDistanceCalculator/component.astro +117 -0
  55. package/src/tool/brakingDistanceCalculator/controller.ts +114 -0
  56. package/src/tool/brakingDistanceCalculator/dom-views.ts +58 -0
  57. package/src/tool/brakingDistanceCalculator/entry.ts +30 -0
  58. package/src/tool/brakingDistanceCalculator/evaluator.ts +45 -0
  59. package/src/tool/brakingDistanceCalculator/i18n/de.ts +45 -0
  60. package/src/tool/brakingDistanceCalculator/i18n/en.ts +208 -0
  61. package/src/tool/brakingDistanceCalculator/i18n/es.ts +46 -0
  62. package/src/tool/brakingDistanceCalculator/i18n/factory.ts +114 -0
  63. package/src/tool/brakingDistanceCalculator/i18n/fr.ts +40 -0
  64. package/src/tool/brakingDistanceCalculator/i18n/id.ts +38 -0
  65. package/src/tool/brakingDistanceCalculator/i18n/it.ts +39 -0
  66. package/src/tool/brakingDistanceCalculator/i18n/ja.ts +38 -0
  67. package/src/tool/brakingDistanceCalculator/i18n/ko.ts +38 -0
  68. package/src/tool/brakingDistanceCalculator/i18n/nl.ts +38 -0
  69. package/src/tool/brakingDistanceCalculator/i18n/pl.ts +38 -0
  70. package/src/tool/brakingDistanceCalculator/i18n/pt.ts +39 -0
  71. package/src/tool/brakingDistanceCalculator/i18n/ru.ts +38 -0
  72. package/src/tool/brakingDistanceCalculator/i18n/sv.ts +38 -0
  73. package/src/tool/brakingDistanceCalculator/i18n/tr.ts +38 -0
  74. package/src/tool/brakingDistanceCalculator/i18n/zh.ts +38 -0
  75. package/src/tool/brakingDistanceCalculator/index.ts +10 -0
  76. package/src/tool/brakingDistanceCalculator/logic.test.ts +84 -0
  77. package/src/tool/brakingDistanceCalculator/logic.ts +82 -0
  78. package/src/tool/brakingDistanceCalculator/seo.astro +15 -0
  79. package/src/tool/brakingDistanceCalculator/storage.ts +28 -0
  80. package/src/tool/brakingDistanceCalculator/ui.ts +42 -0
  81. package/src/tools.ts +4 -0
  82. package/src/types.ts +72 -0
@@ -0,0 +1,601 @@
1
+ .bdc-shell {
2
+ --n-ink: #17211f;
3
+ --n-muted: #66736f;
4
+ --n-paper: #f4efe3;
5
+ --n-panel: #fffaf0;
6
+ --n-line: #d5ccba;
7
+ --n-asphalt: #202827;
8
+ --n-asphalt-soft: #36403e;
9
+ --n-cream: #f7e8b4;
10
+ --n-amber: #f2a93b;
11
+ --n-red: #d44b3f;
12
+ --n-green: #3c8b68;
13
+ --n-blue: #87b9c1;
14
+ --n-shadow: #16211f24;
15
+ --v-stage: #eef3ef;
16
+ --v-sky-soft: #dbe6df;
17
+ --v-road: #4d5956;
18
+ --v-card: #fffaf0;
19
+ --v-ink: #17211f;
20
+ --v-muted: #5d6d68;
21
+ --v-lane: #fff1bd;
22
+ --v-lab: #fffffff2;
23
+ --bdc-reaction-share: 43.4%;
24
+ --bdc-braking-share: 56.6%;
25
+ --bdc-stop-share: 89%;
26
+ --bdc-gap-share: 46%;
27
+
28
+ container-type: inline-size;
29
+ color: var(--n-ink);
30
+ }
31
+
32
+ .theme-dark .bdc-shell {
33
+ --n-ink: #edf2e9;
34
+ --n-muted: #aebbb5;
35
+ --n-paper: #121817;
36
+ --n-panel: #1b2422;
37
+ --n-line: #394541;
38
+ --n-asphalt: #080c0c;
39
+ --n-asphalt-soft: #242e2c;
40
+ --n-cream: #f7df96;
41
+ --n-shadow: #0008;
42
+ --v-stage: #202827;
43
+ --v-sky-soft: #36403e;
44
+ --v-road: #303a38;
45
+ --v-card: #202827;
46
+ --v-ink: #f7df96;
47
+ --v-muted: #aebbb5;
48
+ --v-lane: #f7df96;
49
+ --v-lab: #28322fcc;
50
+ }
51
+
52
+ .bdc-dashboard {
53
+ display: grid;
54
+ grid-template-columns: minmax(17rem, 0.75fr) minmax(0, 1.45fr);
55
+ overflow: hidden;
56
+ border: 1px solid var(--n-line);
57
+ border-radius: 1.5rem;
58
+ background: var(--n-paper);
59
+ box-shadow: 0 1.5rem 4rem var(--n-shadow);
60
+ }
61
+
62
+ .bdc-controls {
63
+ display: grid;
64
+ gap: 1.35rem;
65
+ padding: clamp(1.25rem, 3vw, 2rem);
66
+ background: var(--n-panel);
67
+ border-right: 1px solid var(--n-line);
68
+ }
69
+
70
+ .bdc-toggle-row,
71
+ .bdc-choice,
72
+ .bdc-slider,
73
+ .bdc-surfaces {
74
+ display: grid;
75
+ gap: 0.65rem;
76
+ }
77
+
78
+ .bdc-toggle-row {
79
+ grid-template-columns: 1fr 1fr;
80
+ }
81
+
82
+ .bdc-choice {
83
+ grid-template-columns: repeat(2, 1fr);
84
+ }
85
+
86
+ .bdc-choice > span,
87
+ .bdc-surfaces > span {
88
+ grid-column: 1 / -1;
89
+ color: var(--n-muted);
90
+ font-size: 0.72rem;
91
+ font-weight: 800;
92
+ letter-spacing: 0.08em;
93
+ text-transform: uppercase;
94
+ }
95
+
96
+ .bdc-choice button,
97
+ .bdc-surface-grid button {
98
+ min-height: 2.6rem;
99
+ border: 1px solid var(--n-line);
100
+ border-radius: 0.7rem;
101
+ color: var(--n-ink);
102
+ background: var(--n-paper);
103
+ font: inherit;
104
+ font-size: 0.82rem;
105
+ font-weight: 750;
106
+ cursor: pointer;
107
+ }
108
+
109
+ .bdc-choice button[aria-pressed="true"],
110
+ .bdc-surface-grid button[aria-pressed="true"] {
111
+ border-color: var(--n-ink);
112
+ color: var(--n-panel);
113
+ background: var(--n-ink);
114
+ }
115
+
116
+ .bdc-slider {
117
+ grid-template-columns: 1fr auto;
118
+ align-items: end;
119
+ color: var(--n-muted);
120
+ font-size: 0.85rem;
121
+ }
122
+
123
+ .bdc-slider strong {
124
+ color: var(--n-ink);
125
+ font-size: 1rem;
126
+ }
127
+
128
+ .bdc-slider input {
129
+ grid-column: 1 / -1;
130
+ width: 100%;
131
+ accent-color: var(--n-amber);
132
+ }
133
+
134
+ .bdc-surface-grid {
135
+ display: grid;
136
+ grid-template-columns: repeat(2, 1fr);
137
+ gap: 0.55rem;
138
+ }
139
+
140
+ .bdc-deceleration {
141
+ margin-top: 1rem;
142
+ }
143
+
144
+ .bdc-advanced {
145
+ padding-top: 0.85rem;
146
+ border-top: 1px dashed var(--n-line);
147
+ }
148
+
149
+ .bdc-advanced summary {
150
+ color: var(--n-muted);
151
+ font-size: 0.76rem;
152
+ font-weight: 800;
153
+ letter-spacing: 0.04em;
154
+ cursor: pointer;
155
+ }
156
+
157
+ .bdc-visual {
158
+ position: relative;
159
+ display: grid;
160
+ grid-template-rows: auto auto auto auto auto;
161
+ min-height: 38rem;
162
+ overflow: hidden;
163
+ background: var(--v-stage);
164
+ color: var(--v-ink);
165
+ }
166
+
167
+ .bdc-sky {
168
+ position: relative;
169
+ min-height: 13rem;
170
+ background: linear-gradient(180deg, var(--v-stage), var(--v-sky-soft));
171
+ }
172
+
173
+ .bdc-moon {
174
+ position: absolute;
175
+ top: 2rem;
176
+ right: 8%;
177
+ width: 4.5rem;
178
+ aspect-ratio: 1;
179
+ border-radius: 50%;
180
+ background: var(--n-amber);
181
+ box-shadow: 0 0 3rem color-mix(in srgb, var(--n-amber) 55%, transparent);
182
+ opacity: 0.88;
183
+ }
184
+
185
+ .bdc-status-lamp {
186
+ position: absolute;
187
+ left: clamp(1rem, 4vw, 3rem);
188
+ top: 2.2rem;
189
+ display: flex;
190
+ align-items: flex-start;
191
+ gap: 0.75rem;
192
+ max-width: 26rem;
193
+ }
194
+
195
+ .bdc-status-lamp span {
196
+ flex: 0 0 auto;
197
+ width: 0.8rem;
198
+ height: 0.8rem;
199
+ margin-top: 0.28rem;
200
+ border-radius: 50%;
201
+ background: var(--n-amber);
202
+ box-shadow: 0 0 1rem var(--n-amber);
203
+ }
204
+
205
+ .bdc-shell[data-status="normal"] .bdc-status-lamp span {
206
+ background: var(--n-green);
207
+ box-shadow: 0 0 1rem var(--n-green);
208
+ }
209
+
210
+ .bdc-shell[data-status="critical"] .bdc-status-lamp span {
211
+ background: var(--n-red);
212
+ box-shadow: 0 0 1rem var(--n-red);
213
+ }
214
+
215
+ .bdc-status-lamp p {
216
+ margin: 0;
217
+ color: var(--v-ink);
218
+ font-size: clamp(1.05rem, 2vw, 1.35rem);
219
+ line-height: 1.35;
220
+ }
221
+
222
+ .bdc-road {
223
+ position: relative;
224
+ min-height: 12rem;
225
+ margin: 0 clamp(1rem, 4vw, 3rem);
226
+ border-top: 0.28rem solid var(--v-lane);
227
+ border-bottom: 0.28rem solid var(--v-lane);
228
+ background: repeating-linear-gradient(90deg, transparent 0 8%, var(--v-lane) 8% 12%, transparent 12% 20%) center / 100% 0.24rem no-repeat, var(--v-road);
229
+ }
230
+
231
+ .bdc-road::before {
232
+ content: '';
233
+ position: absolute;
234
+ inset: -6rem 0 auto;
235
+ height: 6rem;
236
+ background: linear-gradient(90deg, transparent, var(--n-amber), transparent);
237
+ clip-path: polygon(0 100%, 50% 0, 100% 100%);
238
+ opacity: 0.13;
239
+ }
240
+
241
+ .bdc-segment {
242
+ position: absolute;
243
+ inset-block: 0;
244
+ display: flex;
245
+ flex-direction: column;
246
+ justify-content: flex-end;
247
+ gap: 0.25rem;
248
+ min-width: 4rem;
249
+ padding: 1rem;
250
+ }
251
+
252
+ .bdc-reaction {
253
+ left: 0;
254
+ width: var(--bdc-reaction-share);
255
+ background: linear-gradient(90deg, transparent, var(--n-blue));
256
+ }
257
+
258
+ .bdc-braking {
259
+ left: var(--bdc-reaction-share);
260
+ width: var(--bdc-braking-share);
261
+ background: linear-gradient(90deg, var(--n-amber), transparent);
262
+ }
263
+
264
+ .bdc-segment span,
265
+ .bdc-road-label,
266
+ .bdc-brake-marker span,
267
+ .bdc-stop-marker span {
268
+ font-size: 0.68rem;
269
+ font-weight: 800;
270
+ letter-spacing: 0.06em;
271
+ text-transform: uppercase;
272
+ }
273
+
274
+ .bdc-segment strong {
275
+ font-size: clamp(1.1rem, 2.5vw, 1.8rem);
276
+ }
277
+
278
+ .bdc-brake-marker,
279
+ .bdc-stop-marker {
280
+ position: absolute;
281
+ inset-block: 0;
282
+ z-index: 2;
283
+ width: 0.2rem;
284
+ background: var(--n-red);
285
+ }
286
+
287
+ .bdc-brake-marker {
288
+ left: var(--bdc-reaction-share);
289
+ }
290
+
291
+ .bdc-stop-marker {
292
+ left: var(--bdc-stop-share);
293
+ }
294
+
295
+ .bdc-brake-marker span,
296
+ .bdc-stop-marker span {
297
+ position: absolute;
298
+ bottom: calc(100% + 0.6rem);
299
+ left: 50%;
300
+ color: var(--v-ink);
301
+ white-space: nowrap;
302
+ transform: translateX(-50%);
303
+ }
304
+
305
+ .bdc-start {
306
+ position: absolute;
307
+ bottom: calc(100% + 0.6rem);
308
+ left: 0;
309
+ color: var(--v-ink);
310
+ }
311
+
312
+ .bdc-vehicle {
313
+ position: absolute;
314
+ left: 1rem;
315
+ top: 35%;
316
+ width: 4.4rem;
317
+ height: 2rem;
318
+ border-radius: 1.2rem 1.6rem 0.4rem 0.4rem;
319
+ background: var(--n-red);
320
+ box-shadow: 0.7rem 0 1.2rem var(--n-amber);
321
+ }
322
+
323
+ .bdc-lead-vehicle {
324
+ position: absolute;
325
+ z-index: 4;
326
+ left: var(--bdc-gap-share);
327
+ top: 18%;
328
+ width: 4rem;
329
+ height: 1.85rem;
330
+ border-radius: 1.2rem 1.6rem 0.4rem 0.4rem;
331
+ background: var(--n-cream);
332
+ box-shadow: 0 0 1.2rem #f7e8b466;
333
+ transform: translateX(-50%);
334
+ }
335
+
336
+ .bdc-lead-vehicle::before,
337
+ .bdc-lead-vehicle::after {
338
+ content: '';
339
+ position: absolute;
340
+ bottom: -0.5rem;
341
+ width: 0.95rem;
342
+ aspect-ratio: 1;
343
+ border: 0.18rem solid var(--n-line);
344
+ border-radius: 50%;
345
+ background: var(--n-asphalt);
346
+ }
347
+
348
+ .bdc-lead-vehicle::before {
349
+ left: 0.5rem;
350
+ }
351
+
352
+ .bdc-lead-vehicle::after {
353
+ right: 0.5rem;
354
+ }
355
+
356
+ .bdc-lead-vehicle span {
357
+ position: absolute;
358
+ top: calc(100% + 0.65rem);
359
+ left: 50%;
360
+ color: var(--v-lane);
361
+ font-size: 0.62rem;
362
+ font-weight: 800;
363
+ letter-spacing: 0.05em;
364
+ text-transform: uppercase;
365
+ white-space: nowrap;
366
+ transform: translateX(-50%);
367
+ }
368
+
369
+ @container (max-width: 56rem) {
370
+ .bdc-dashboard {
371
+ grid-template-columns: 1fr;
372
+ }
373
+
374
+ .bdc-controls {
375
+ border-right: 0;
376
+ border-bottom: 1px solid var(--n-line);
377
+ }
378
+ }
379
+
380
+ .bdc-shell[data-simulating="true"] .bdc-vehicle {
381
+ animation: bdc-stop-run 2.8s cubic-bezier(0.2, 0.75, 0.25, 1) forwards;
382
+ }
383
+
384
+ .bdc-shell[data-simulating="complete"] .bdc-vehicle {
385
+ left: var(--bdc-stop-share);
386
+ transform: translateX(-50%);
387
+ }
388
+
389
+ .bdc-shell:has(.bdc-gap-lab[data-gap-status="insufficient"])[data-simulating="complete"] .bdc-lead-vehicle {
390
+ background: var(--n-red);
391
+ box-shadow: 0 0 2rem var(--n-red);
392
+ }
393
+
394
+ @keyframes bdc-stop-run {
395
+ from {
396
+ left: 1rem;
397
+ transform: translateX(0);
398
+ }
399
+
400
+ to {
401
+ left: var(--bdc-stop-share);
402
+ transform: translateX(-50%);
403
+ }
404
+ }
405
+
406
+ .bdc-gap-lab {
407
+ display: grid;
408
+ gap: 0.85rem;
409
+ margin: 1.1rem clamp(1rem, 4vw, 3rem) 0;
410
+ padding: 1rem;
411
+ border: 1px solid color-mix(in srgb, var(--n-amber) 45%, var(--n-line));
412
+ background: var(--v-lab);
413
+ }
414
+
415
+ .bdc-gap-heading {
416
+ display: flex;
417
+ align-items: center;
418
+ justify-content: space-between;
419
+ gap: 1rem;
420
+ }
421
+
422
+ .bdc-gap-heading > div > span {
423
+ font-size: 0.72rem;
424
+ font-weight: 850;
425
+ letter-spacing: 0.07em;
426
+ text-transform: uppercase;
427
+ }
428
+
429
+ .bdc-gap-heading p {
430
+ max-width: 30rem;
431
+ margin: 0.3rem 0 0;
432
+ color: var(--v-ink);
433
+ font-size: 0.82rem;
434
+ line-height: 1.35;
435
+ }
436
+
437
+ .bdc-gap-heading button {
438
+ flex: 0 0 auto;
439
+ min-height: 2.6rem;
440
+ padding: 0.65rem 1rem;
441
+ border: 1px solid var(--v-ink);
442
+ border-radius: 999px;
443
+ color: var(--v-stage);
444
+ background: var(--v-ink);
445
+ font: inherit;
446
+ font-size: 0.78rem;
447
+ font-weight: 850;
448
+ cursor: pointer;
449
+ }
450
+
451
+ .bdc-gap-slider {
452
+ color: var(--v-ink);
453
+ }
454
+
455
+ .bdc-gap-slider strong {
456
+ color: var(--v-ink);
457
+ }
458
+
459
+ .bdc-gap-values {
460
+ display: grid;
461
+ grid-template-columns: 1fr 1fr;
462
+ gap: 0.65rem;
463
+ }
464
+
465
+ .bdc-gap-values div {
466
+ display: flex;
467
+ align-items: baseline;
468
+ justify-content: space-between;
469
+ gap: 1rem;
470
+ padding: 0.65rem 0.75rem;
471
+ background: color-mix(in srgb, var(--v-stage) 72%, transparent);
472
+ }
473
+
474
+ .bdc-gap-values span {
475
+ color: var(--v-muted);
476
+ font-size: 0.7rem;
477
+ font-weight: 800;
478
+ text-transform: uppercase;
479
+ }
480
+
481
+ .bdc-gap-values strong {
482
+ color: var(--v-ink);
483
+ }
484
+
485
+ .bdc-gap-lab[data-gap-status="enough"] {
486
+ border-color: var(--n-green);
487
+ }
488
+
489
+ .bdc-gap-lab[data-gap-status="enough"] .bdc-gap-values div:last-child strong {
490
+ color: #81d6aa;
491
+ }
492
+
493
+ .bdc-gap-lab[data-gap-status="insufficient"] {
494
+ border-color: var(--n-red);
495
+ }
496
+
497
+ .bdc-gap-lab[data-gap-status="insufficient"] .bdc-gap-values div:last-child strong {
498
+ color: #ff8f84;
499
+ }
500
+
501
+ .bdc-gap-hint {
502
+ margin: 0;
503
+ color: var(--v-muted);
504
+ font-size: 0.7rem;
505
+ line-height: 1.35;
506
+ }
507
+
508
+ .bdc-vehicle::before,
509
+ .bdc-vehicle::after {
510
+ content: '';
511
+ position: absolute;
512
+ bottom: -0.55rem;
513
+ width: 1.05rem;
514
+ aspect-ratio: 1;
515
+ border-radius: 50%;
516
+ background: var(--n-asphalt);
517
+ border: 0.2rem solid var(--n-line);
518
+ }
519
+
520
+ .bdc-vehicle::before {
521
+ left: 0.55rem;
522
+ }
523
+
524
+ .bdc-vehicle::after {
525
+ right: 0.55rem;
526
+ }
527
+
528
+ .bdc-result-ribbon {
529
+ display: grid;
530
+ grid-template-columns: 1fr 1fr;
531
+ gap: 1px;
532
+ margin: clamp(1rem, 4vw, 3rem);
533
+ background: var(--n-line);
534
+ border: 1px solid var(--n-line);
535
+ }
536
+
537
+ .bdc-result-ribbon div {
538
+ display: grid;
539
+ gap: 0.2rem;
540
+ padding: 1rem;
541
+ background: var(--v-card);
542
+ }
543
+
544
+ .bdc-result-ribbon span {
545
+ color: var(--v-muted);
546
+ font-size: 0.72rem;
547
+ font-weight: 800;
548
+ text-transform: uppercase;
549
+ }
550
+
551
+ .bdc-result-ribbon strong {
552
+ color: var(--v-ink);
553
+ font-size: clamp(1.6rem, 4vw, 2.7rem);
554
+ }
555
+
556
+ .bdc-vehicle-note,
557
+ .bdc-notice {
558
+ margin: 0 clamp(1rem, 4vw, 3rem) 0.75rem;
559
+ color: var(--v-muted);
560
+ font-size: 0.78rem;
561
+ line-height: 1.45;
562
+ }
563
+
564
+ .bdc-notice {
565
+ margin-bottom: 1.5rem;
566
+ color: var(--v-ink);
567
+ }
568
+
569
+ @media (max-width: 800px) {
570
+ .bdc-dashboard {
571
+ grid-template-columns: 1fr;
572
+ }
573
+
574
+ .bdc-controls {
575
+ border-right: 0;
576
+ border-bottom: 1px solid var(--n-line);
577
+ }
578
+
579
+ .bdc-visual {
580
+ min-height: 34rem;
581
+ }
582
+
583
+ .bdc-road {
584
+ margin-inline: 1rem;
585
+ }
586
+
587
+ .bdc-gap-heading {
588
+ align-items: stretch;
589
+ flex-direction: column;
590
+ }
591
+
592
+ .bdc-gap-values {
593
+ grid-template-columns: 1fr;
594
+ }
595
+ }
596
+
597
+ @media (prefers-reduced-motion: reduce) {
598
+ .bdc-shell[data-simulating="true"] .bdc-vehicle {
599
+ animation-duration: 0.01ms;
600
+ }
601
+ }
@@ -0,0 +1,117 @@
1
+ ---
2
+ import './braking-distance-calculator.css';
3
+
4
+ interface Props {
5
+ ui: Record<string, string>;
6
+ }
7
+
8
+ const { ui } = Astro.props;
9
+ ---
10
+
11
+ <div class="bdc-shell" data-braking-distance-calculator data-status="warning">
12
+ <div class="bdc-dashboard">
13
+ <section class="bdc-controls" aria-label={ui.estimateNotice}>
14
+ <div class="bdc-toggle-row">
15
+ <div class="bdc-choice" role="group" aria-label={ui.unitLabel}>
16
+ <span>{ui.unitLabel}</span>
17
+ <button type="button" data-unit-button data-value="kmh" aria-pressed="true">{ui.metricUnit}</button>
18
+ <button type="button" data-unit-button data-value="mph" aria-pressed="false">{ui.imperialUnit}</button>
19
+ </div>
20
+ <div class="bdc-choice" role="group" aria-label={ui.vehicleLabel}>
21
+ <span>{ui.vehicleLabel}</span>
22
+ <button type="button" data-vehicle-button data-value="car" aria-pressed="true">{ui.carLabel}</button>
23
+ <button type="button" data-vehicle-button data-value="motorcycle" aria-pressed="false">{ui.motorcycleLabel}</button>
24
+ </div>
25
+ </div>
26
+
27
+ <label class="bdc-slider" for="bdc-speed">
28
+ <span>{ui.speedLabel}</span>
29
+ <strong><output data-output="bdc-speed">80</output></strong>
30
+ <input id="bdc-speed" type="range" min="10" max="200" step="5" value="80" aria-label={ui.speedLabel} />
31
+ </label>
32
+
33
+ <label class="bdc-slider" for="bdc-reaction-time">
34
+ <span>{ui.reactionTimeLabel}</span>
35
+ <strong><output data-output="bdc-reaction-time">2.5</output> s</strong>
36
+ <input id="bdc-reaction-time" type="range" min="0.5" max="4" step="0.1" value="2.5" aria-label={ui.reactionTimeLabel} />
37
+ </label>
38
+
39
+ <label class="bdc-slider" for="bdc-grade">
40
+ <span>{ui.gradeLabel}</span>
41
+ <strong><output data-output="bdc-grade">0</output>%</strong>
42
+ <input id="bdc-grade" type="range" min="-12" max="12" step="1" value="0" aria-label={ui.gradeLabel} />
43
+ </label>
44
+
45
+ <div class="bdc-surfaces" role="group" aria-label={ui.roadConditionLabel}>
46
+ <span>{ui.roadConditionLabel}</span>
47
+ <div class="bdc-surface-grid">
48
+ <button type="button" data-surface-button data-deceleration="3.4" aria-pressed="true">{ui.dryRoadLabel}</button>
49
+ <button type="button" data-surface-button data-deceleration="1.7" aria-pressed="false">{ui.wetRoadLabel}</button>
50
+ <button type="button" data-surface-button data-deceleration="0.85" aria-pressed="false">{ui.snowRoadLabel}</button>
51
+ <button type="button" data-surface-button data-deceleration="0.34" aria-pressed="false">{ui.iceRoadLabel}</button>
52
+ </div>
53
+ </div>
54
+
55
+ <details class="bdc-advanced">
56
+ <summary>{ui.advancedLabel}</summary>
57
+ <label class="bdc-slider bdc-deceleration" for="bdc-deceleration">
58
+ <span>{ui.decelerationLabel}</span>
59
+ <strong><output data-output="bdc-deceleration">3.4</output> m/s2</strong>
60
+ <input id="bdc-deceleration" type="range" min="0.2" max="8" step="0.1" value="3.4" aria-label={ui.decelerationLabel} />
61
+ </label>
62
+ </details>
63
+ </section>
64
+
65
+ <section class="bdc-visual" aria-live="polite">
66
+ <div class="bdc-sky">
67
+ <div class="bdc-moon"></div>
68
+ <div class="bdc-status-lamp"><span></span><p data-bdc-status>{ui.warningMessage}</p></div>
69
+ </div>
70
+ <div class="bdc-road">
71
+ <div class="bdc-road-label bdc-start">{ui.roadStartLabel}</div>
72
+ <div class="bdc-segment bdc-reaction">
73
+ <span>{ui.reactionDistanceLabel}</span>
74
+ <strong data-bdc-reaction>55.6 m</strong>
75
+ </div>
76
+ <div class="bdc-brake-marker"><span>{ui.brakePointLabel}</span></div>
77
+ <div class="bdc-segment bdc-braking">
78
+ <span>{ui.brakingDistanceLabel}</span>
79
+ <strong data-bdc-braking>72.6 m</strong>
80
+ </div>
81
+ <div class="bdc-stop-marker"><span>{ui.stopPointLabel}</span></div>
82
+ <div class="bdc-lead-vehicle" aria-hidden="true"><i></i><b></b><span>{ui.leadVehicleLabel}</span></div>
83
+ <div class="bdc-vehicle" aria-hidden="true"><i></i><b></b></div>
84
+ </div>
85
+ <section class="bdc-gap-lab" data-gap-status="insufficient" aria-label={ui.safetyExperimentLabel}>
86
+ <div class="bdc-gap-heading">
87
+ <div><span>{ui.safetyExperimentLabel}</span><p data-bdc-gap-message>{ui.insufficientGapMessage}</p></div>
88
+ <button type="button" data-bdc-simulate>{ui.simulateLabel}</button>
89
+ </div>
90
+ <label class="bdc-slider bdc-gap-slider" for="bdc-gap-seconds">
91
+ <span>{ui.gapTimeLabel}</span>
92
+ <strong><output data-output="bdc-gap-seconds">3.0</output> s</strong>
93
+ <input id="bdc-gap-seconds" type="range" min="0.5" max="8" step="0.1" value="3" aria-label={ui.gapTimeLabel} />
94
+ </label>
95
+ <div class="bdc-gap-values">
96
+ <div><span>{ui.availableDistanceLabel}</span><strong data-bdc-gap-distance>66.7 m</strong></div>
97
+ <div><span data-bdc-margin-label>{ui.shortfallLabel}</span><strong data-bdc-margin>61.5 m</strong></div>
98
+ </div>
99
+ <p class="bdc-gap-hint">{ui.safetyExperimentHint}</p>
100
+ </section>
101
+ <div class="bdc-result-ribbon">
102
+ <div><span>{ui.totalDistanceLabel}</span><strong data-bdc-total>128.2 m</strong></div>
103
+ <div><span>{ui.totalTimeLabel}</span><strong data-bdc-time>9.0 s</strong></div>
104
+ </div>
105
+ <p class="bdc-vehicle-note" data-bdc-vehicle-note>{ui.carNote}</p>
106
+ <p class="bdc-notice">{ui.estimateNotice}</p>
107
+ </section>
108
+ </div>
109
+ </div>
110
+
111
+ <script is:inline id="bdc-copy" type="application/json" set:html={JSON.stringify(ui)}></script>
112
+ <script>
113
+ import { initBrakingDistanceCalculator } from './controller';
114
+
115
+ const copy = document.querySelector<HTMLScriptElement>('#bdc-copy');
116
+ if (copy?.textContent) initBrakingDistanceCalculator(JSON.parse(copy.textContent));
117
+ </script>