@inventure71/paddockjs 0.3.0 → 1.0.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 (54) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/INSTALL_AND_UPDATE.md +3 -1
  3. package/README.md +142 -9
  4. package/docs/architecture.md +49 -15
  5. package/docs/concepts.md +20 -3
  6. package/docs/data_contract.md +185 -27
  7. package/docs/data_model.md +1 -1
  8. package/docs/learnings.md +1 -1
  9. package/docs/rules.md +140 -19
  10. package/docs/system_specs.md +39 -16
  11. package/docs/training.md +103 -1
  12. package/examples/train-basic-policy.mjs +5 -6
  13. package/examples/trainingData.mjs +133 -0
  14. package/package.json +5 -2
  15. package/src/api/PaddockSimulatorController.js +32 -2
  16. package/src/app/F1SimulatorApp.js +1101 -128
  17. package/src/app/domBindings.js +10 -1
  18. package/src/config/defaultOptions.js +4 -4
  19. package/src/environment/actions.js +25 -2
  20. package/src/environment/evaluation.js +184 -0
  21. package/src/environment/index.d.ts +281 -1
  22. package/src/environment/index.js +13 -0
  23. package/src/environment/observationOptions.js +12 -0
  24. package/src/environment/observations.js +189 -17
  25. package/src/environment/options.js +7 -0
  26. package/src/environment/recorder.js +28 -0
  27. package/src/environment/runtime.js +41 -6
  28. package/src/environment/scenarios.js +220 -0
  29. package/src/environment/sensors.js +145 -35
  30. package/src/environment/specs.js +92 -8
  31. package/src/environment/workerProtocol.js +52 -0
  32. package/src/index.d.ts +366 -3
  33. package/src/index.js +21 -0
  34. package/src/rendering/proceduralTrackAsset.js +336 -23
  35. package/src/simulation/collisionGeometry.js +214 -0
  36. package/src/simulation/driverController.js +431 -65
  37. package/src/simulation/raceSimulation.js +2132 -171
  38. package/src/simulation/rules/collisionSteward.js +69 -0
  39. package/src/simulation/rules/penaltyLedger.js +153 -0
  40. package/src/simulation/rules/pitLaneSpeedingSteward.js +71 -0
  41. package/src/simulation/rules/tireRequirementSteward.js +29 -0
  42. package/src/simulation/rules/trackLimitsSteward.js +85 -0
  43. package/src/simulation/rulesConfig.js +352 -0
  44. package/src/simulation/trackModel.js +1154 -76
  45. package/src/simulation/units.js +6 -4
  46. package/src/simulation/vehicleGeometry.js +247 -0
  47. package/src/simulation/vehiclePhysics.js +82 -50
  48. package/src/simulation/wheelSurface.js +338 -0
  49. package/src/styles.css +300 -42
  50. package/src/ui/bannerTemplates.js +69 -0
  51. package/src/ui/componentTemplates.js +36 -96
  52. package/src/ui/raceControlStatusBanner.js +39 -0
  53. package/src/ui/shellTemplate.js +1 -1
  54. package/src/ui/templateUtils.js +23 -0
package/src/styles.css CHANGED
@@ -10,6 +10,7 @@
10
10
  --text: #f4f7fb;
11
11
  --muted: #8d97a7;
12
12
  --red: var(--paddock-accent-color, #e10600);
13
+ --race-control-red: #e10600;
13
14
  --green: var(--paddock-green-color, #14c784);
14
15
  --yellow: var(--paddock-yellow-color, #ffd166);
15
16
  --blue: #39a7ff;
@@ -68,6 +69,8 @@
68
69
 
69
70
  .sim-backlink,
70
71
  .sim-control {
72
+ min-width: 0;
73
+ overflow: hidden;
71
74
  border: 1px solid var(--line-strong);
72
75
  color: var(--text);
73
76
  background: rgba(255, 255, 255, 0.05);
@@ -77,6 +80,8 @@
77
80
  font-size: 0.76rem;
78
81
  font-weight: 800;
79
82
  padding: 0.75rem 0.9rem;
83
+ text-overflow: ellipsis;
84
+ white-space: nowrap;
80
85
  cursor: pointer;
81
86
  }
82
87
 
@@ -92,7 +97,8 @@
92
97
  border-color: var(--yellow);
93
98
  }
94
99
 
95
- .camera-controls button.is-active {
100
+ .camera-controls button.is-active,
101
+ .camera-controls button[aria-pressed='true'] {
96
102
  color: #08090b;
97
103
  background: var(--text);
98
104
  border-color: var(--text);
@@ -121,6 +127,7 @@
121
127
  }
122
128
 
123
129
  .sim-controls {
130
+ min-width: 0;
124
131
  display: flex;
125
132
  gap: 0.6rem;
126
133
  justify-content: flex-end;
@@ -128,7 +135,7 @@
128
135
 
129
136
  .sim-external-camera-controls {
130
137
  display: flex;
131
- justify-content: flex-end;
138
+ justify-content: flex-start;
132
139
  }
133
140
 
134
141
  .sim-grid {
@@ -136,7 +143,7 @@
136
143
  min-height: 0;
137
144
  position: relative;
138
145
  display: grid;
139
- grid-template-columns: minmax(255px, 0.78fr) minmax(500px, 3fr) minmax(270px, 0.9fr);
146
+ grid-template-columns: minmax(0, 0.78fr) minmax(0, 3fr) minmax(0, 0.9fr);
140
147
  gap: 0.9rem;
141
148
  }
142
149
 
@@ -229,12 +236,12 @@
229
236
  }
230
237
 
231
238
  .sim-shell--left-tower-overlay .race-data-panel {
232
- z-index: 8;
239
+ z-index: 12;
233
240
  grid-template-columns: clamp(4.6rem, 7.5cqw, 7rem) minmax(0, 1fr) auto;
234
241
  }
235
242
 
236
243
  .sim-canvas-panel--with-timing-tower .race-data-panel {
237
- z-index: 8;
244
+ z-index: 12;
238
245
  grid-template-columns: clamp(4.6rem, 7.5cqw, 7rem) minmax(0, 1fr) auto;
239
246
  }
240
247
 
@@ -296,8 +303,13 @@
296
303
 
297
304
  .telemetry-stack {
298
305
  display: grid;
306
+ grid-auto-rows: max-content;
307
+ align-content: start;
299
308
  gap: 0.72rem;
300
309
  min-height: 0;
310
+ max-height: 100%;
311
+ overflow-y: auto;
312
+ scrollbar-width: thin;
301
313
  }
302
314
 
303
315
  .telemetry-stack > .sim-telemetry {
@@ -360,7 +372,7 @@
360
372
 
361
373
  .broadcast-brand,
362
374
  .broadcast-lap,
363
- .broadcast-safety-banner,
375
+ .broadcast-race-control-banner,
364
376
  .broadcast-column-head,
365
377
  .timing-list {
366
378
  position: relative;
@@ -409,21 +421,31 @@
409
421
  letter-spacing: -0.04em;
410
422
  }
411
423
 
412
- .broadcast-safety-banner {
424
+ .broadcast-race-control-banner {
413
425
  display: grid;
414
426
  grid-template-columns: 3.4rem minmax(0, 1fr);
415
427
  align-items: center;
416
428
  margin: 0;
429
+ background: rgba(255, 255, 255, 0.16);
430
+ color: #fff;
431
+ text-transform: uppercase;
432
+ }
433
+
434
+ .broadcast-race-control-banner.is-safety-car {
417
435
  background: #e0ce00;
418
436
  color: #0a0b0d;
419
- text-transform: uppercase;
420
437
  }
421
438
 
422
- .broadcast-safety-banner[hidden] {
439
+ .broadcast-race-control-banner.is-red-flag {
440
+ background: var(--race-control-red);
441
+ color: #fff;
442
+ }
443
+
444
+ .broadcast-race-control-banner[hidden] {
423
445
  display: none;
424
446
  }
425
447
 
426
- .broadcast-safety-banner span {
448
+ .broadcast-race-control-banner span {
427
449
  display: grid;
428
450
  place-items: center;
429
451
  min-height: 2.65rem;
@@ -434,7 +456,7 @@
434
456
  letter-spacing: -0.06em;
435
457
  }
436
458
 
437
- .broadcast-safety-banner strong {
459
+ .broadcast-race-control-banner strong {
438
460
  padding: 0 0.65rem;
439
461
  font-size: 1.1rem;
440
462
  font-weight: 900;
@@ -562,6 +584,11 @@
562
584
  scrollbar-width: thin;
563
585
  }
564
586
 
587
+ .timing-list > li {
588
+ min-width: 0;
589
+ overflow: hidden;
590
+ }
591
+
565
592
  .sim-shell--left-tower-overlay .timing-list {
566
593
  overflow-x: hidden;
567
594
  overflow-y: auto;
@@ -603,6 +630,10 @@
603
630
  min-width: 0;
604
631
  }
605
632
 
633
+ .timing-row > * {
634
+ min-width: 0;
635
+ }
636
+
606
637
  .sim-shell--left-tower-overlay .timing-row {
607
638
  min-width: 0;
608
639
  }
@@ -668,7 +699,9 @@
668
699
 
669
700
  .timing-name {
670
701
  min-width: 0;
671
- display: block;
702
+ display: flex;
703
+ align-items: center;
704
+ gap: 0.26rem;
672
705
  overflow: hidden;
673
706
  padding-inline: 0.45rem;
674
707
  font-size: 0.76rem;
@@ -680,11 +713,37 @@
680
713
  text-transform: uppercase;
681
714
  }
682
715
 
716
+ .timing-name > span:first-child {
717
+ min-width: 0;
718
+ overflow: hidden;
719
+ text-overflow: ellipsis;
720
+ }
721
+
722
+ .timing-penalty-badge {
723
+ flex: 0 0 auto;
724
+ display: grid;
725
+ place-items: center;
726
+ width: 1rem;
727
+ height: 1rem;
728
+ color: #fff;
729
+ background: #e10600;
730
+ border: 1px solid rgba(255, 220, 220, 0.78);
731
+ border-radius: 0.18rem;
732
+ font-family: var(--font-mono);
733
+ font-size: 0.66rem;
734
+ font-weight: 900;
735
+ line-height: 1;
736
+ box-shadow: 0 0 14px rgba(225, 6, 0, 0.42);
737
+ }
738
+
683
739
  .timing-gap {
684
740
  color: rgba(244, 247, 251, 0.76);
685
741
  font-size: 0.66rem;
742
+ overflow: hidden;
686
743
  padding-right: 0.25rem;
687
744
  text-align: right;
745
+ text-overflow: ellipsis;
746
+ white-space: nowrap;
688
747
  }
689
748
 
690
749
  .timing-row.is-selected .timing-gap {
@@ -728,6 +787,11 @@
728
787
  .track-canvas {
729
788
  position: absolute;
730
789
  inset: 0;
790
+ z-index: 0;
791
+ background: #2e7d32;
792
+ cursor: default;
793
+ user-select: none;
794
+ touch-action: auto;
731
795
  }
732
796
 
733
797
  .track-canvas canvas {
@@ -738,19 +802,41 @@
738
802
 
739
803
  .race-telemetry-drawer {
740
804
  --telemetry-drawer-width: min(390px, max(300px, 30cqw));
805
+ --race-toolbar-height: 3.4rem;
741
806
  position: relative;
742
- display: block;
807
+ display: flex;
808
+ flex-direction: column;
743
809
  container-type: inline-size;
744
810
  inline-size: 100%;
745
- min-height: var(--paddock-race-view-min-height, 620px);
811
+ max-inline-size: 100%;
812
+ height: var(--paddock-race-view-height, var(--paddock-race-view-min-height, 620px));
813
+ min-height: min(100%, var(--paddock-race-view-min-height, 620px));
746
814
  overflow: hidden;
747
815
  background: #000;
748
816
  }
749
817
 
818
+ .race-telemetry-drawer__toolbar {
819
+ position: relative;
820
+ z-index: 13;
821
+ display: flex;
822
+ align-items: center;
823
+ justify-content: space-between;
824
+ gap: 0.7rem;
825
+ min-height: var(--race-toolbar-height);
826
+ padding: 0.45rem;
827
+ background: #090b0d;
828
+ border-bottom: 1px solid var(--line);
829
+ }
830
+
831
+ .race-telemetry-drawer__toolbar .camera-controls--external {
832
+ flex: 1 1 auto;
833
+ }
834
+
750
835
  .race-telemetry-drawer__race {
751
836
  min-width: 0;
752
837
  display: flex;
753
- min-height: inherit;
838
+ flex: 1 1 auto;
839
+ min-height: 0;
754
840
  margin-right: 0;
755
841
  transition: margin-right 320ms cubic-bezier(0.2, 0.8, 0.2, 1);
756
842
  }
@@ -761,15 +847,12 @@
761
847
 
762
848
  .race-telemetry-drawer__race > .sim-canvas-panel {
763
849
  flex: 1 1 auto;
764
- min-height: inherit;
765
- height: auto;
850
+ min-height: 0;
851
+ height: 100%;
766
852
  }
767
853
 
768
854
  .race-telemetry-drawer__controls {
769
- position: absolute;
770
- z-index: 12;
771
- top: 1rem;
772
- right: 1rem;
855
+ min-width: 0;
773
856
  display: flex;
774
857
  flex-wrap: wrap;
775
858
  justify-content: flex-end;
@@ -780,7 +863,6 @@
780
863
  .race-telemetry-drawer__controls .telemetry-drawer-toggle {
781
864
  min-height: 2.45rem;
782
865
  padding: 0.62rem 0.72rem;
783
- box-shadow: 0 14px 34px rgba(0, 0, 0, 0.3);
784
866
  }
785
867
 
786
868
  .telemetry-drawer-toggle {
@@ -798,47 +880,46 @@
798
880
  .telemetry-drawer {
799
881
  position: absolute;
800
882
  z-index: 11;
801
- top: 0;
883
+ top: var(--race-toolbar-height);
802
884
  right: 0;
803
885
  bottom: 0;
804
886
  width: var(--telemetry-drawer-width);
805
887
  min-width: 0;
806
- height: 100%;
888
+ height: calc(100% - var(--race-toolbar-height));
807
889
  overflow: hidden;
808
890
  color: var(--text);
809
891
  background:
810
892
  linear-gradient(180deg, rgba(255, 255, 255, 0.075), transparent 22%),
811
893
  rgba(8, 9, 11, 0.97);
812
894
  border-left: 1px solid var(--line);
813
- transform: translate3d(100%, 0, 0);
895
+ clip-path: inset(0 0 0 100%);
896
+ transform: translate3d(0, 0, 0);
814
897
  opacity: 0;
815
898
  pointer-events: none;
816
899
  will-change: transform, opacity;
817
900
  transition:
818
- transform 320ms cubic-bezier(0.2, 0.8, 0.2, 1),
901
+ clip-path 320ms cubic-bezier(0.2, 0.8, 0.2, 1),
819
902
  opacity 180ms ease;
820
903
  }
821
904
 
822
905
  .race-telemetry-drawer.is-telemetry-open .telemetry-drawer {
823
- transform: translate3d(0, 0, 0);
906
+ clip-path: inset(0);
824
907
  opacity: 1;
825
908
  pointer-events: auto;
826
909
  }
827
910
 
828
- .telemetry-drawer__header {
829
- min-height: 3.4rem;
830
- border-bottom: 1px solid var(--line);
831
- }
832
-
833
911
  .telemetry-drawer__content {
834
- display: grid;
835
- gap: 0.72rem;
836
- height: calc(100% - 3.4rem);
912
+ height: 100%;
837
913
  padding: 0.72rem;
838
- overflow-y: auto;
914
+ min-height: 0;
915
+ overflow: hidden;
839
916
  scrollbar-width: thin;
840
917
  }
841
918
 
919
+ .telemetry-drawer__content > .telemetry-stack {
920
+ height: 100%;
921
+ }
922
+
842
923
  .camera-controls {
843
924
  position: absolute;
844
925
  top: 1rem;
@@ -855,6 +936,8 @@
855
936
 
856
937
  .camera-controls--external {
857
938
  position: relative;
939
+ top: auto;
940
+ left: auto;
858
941
  max-width: 100%;
859
942
  justify-content: flex-end;
860
943
  background: rgba(8, 9, 11, 0.84);
@@ -877,6 +960,89 @@
877
960
  border-color: var(--text);
878
961
  }
879
962
 
963
+ .steward-message {
964
+ --steward-panel-bg: rgba(8, 9, 11, 0.92);
965
+ --steward-chip-bg: color-mix(in srgb, var(--steward-color, var(--yellow)) 18%, rgba(8, 9, 11, 0.94));
966
+ --steward-chip-color: var(--steward-color, var(--yellow));
967
+ position: absolute;
968
+ top: 1rem;
969
+ left: 50%;
970
+ z-index: 8;
971
+ display: grid;
972
+ grid-template-columns: auto minmax(0, 1fr);
973
+ gap: 0.16rem 0.7rem;
974
+ min-width: min(28rem, calc(100% - 2rem));
975
+ max-width: min(34rem, calc(100% - 2rem));
976
+ padding: 0.62rem 0.75rem;
977
+ color: var(--text);
978
+ background: var(--steward-panel-bg);
979
+ border: 1px solid color-mix(in srgb, var(--steward-color, var(--yellow)) 70%, transparent);
980
+ box-shadow:
981
+ inset 0 3px 0 var(--steward-color, var(--yellow)),
982
+ 0 16px 40px rgba(0, 0, 0, 0.34);
983
+ transform: translate(-50%, 0);
984
+ opacity: 1;
985
+ pointer-events: none;
986
+ transition:
987
+ opacity 160ms ease,
988
+ transform 160ms ease;
989
+ }
990
+
991
+ .steward-message.is-penalty {
992
+ --steward-panel-bg: linear-gradient(110deg, rgba(68, 6, 8, 0.96), rgba(8, 9, 11, 0.94) 58%);
993
+ --steward-chip-bg: #e10600;
994
+ --steward-chip-color: #fff;
995
+ }
996
+
997
+ .steward-message.is-warning {
998
+ --steward-panel-bg: linear-gradient(110deg, rgba(70, 56, 8, 0.96), rgba(8, 9, 11, 0.94) 58%);
999
+ --steward-chip-bg: var(--yellow);
1000
+ --steward-chip-color: #111318;
1001
+ }
1002
+
1003
+ .steward-message.is-hidden {
1004
+ opacity: 0;
1005
+ transform: translate(-50%, -0.55rem);
1006
+ }
1007
+
1008
+ .steward-message__kicker {
1009
+ grid-row: span 2;
1010
+ align-self: center;
1011
+ min-width: 4.9rem;
1012
+ padding: 0.48rem 0.62rem;
1013
+ color: var(--steward-chip-color);
1014
+ background: var(--steward-chip-bg);
1015
+ border: 1px solid color-mix(in srgb, var(--steward-chip-color) 34%, transparent);
1016
+ font-family: var(--font-mono);
1017
+ font-size: 1.05rem;
1018
+ font-weight: 900;
1019
+ letter-spacing: 0.02em;
1020
+ line-height: 1;
1021
+ text-align: center;
1022
+ text-transform: uppercase;
1023
+ }
1024
+
1025
+ .steward-message strong {
1026
+ min-width: 0;
1027
+ overflow-wrap: anywhere;
1028
+ overflow: hidden;
1029
+ color: var(--text);
1030
+ font-size: 0.92rem;
1031
+ line-height: 1.08;
1032
+ text-transform: uppercase;
1033
+ white-space: normal;
1034
+ }
1035
+
1036
+ .steward-message span:last-child {
1037
+ min-width: 0;
1038
+ overflow-wrap: anywhere;
1039
+ overflow: hidden;
1040
+ color: var(--muted);
1041
+ font-size: 0.7rem;
1042
+ text-overflow: ellipsis;
1043
+ white-space: normal;
1044
+ }
1045
+
880
1046
  .fps-counter {
881
1047
  position: absolute;
882
1048
  top: 1rem;
@@ -1133,11 +1299,13 @@
1133
1299
  left: var(--race-data-banner-left);
1134
1300
  bottom: 1.2rem;
1135
1301
  transform: translateX(var(--race-data-banner-translate-x));
1136
- z-index: 8;
1302
+ z-index: 12;
1137
1303
  display: grid;
1138
- grid-template-columns: clamp(6.5rem, 12vw, 9.6rem) minmax(280px, 1fr) auto;
1304
+ grid-template-columns: clamp(6.5rem, 12vw, 9.6rem) minmax(0, 1fr) auto;
1139
1305
  align-items: center;
1140
1306
  width: var(--race-data-banner-width);
1307
+ max-width: calc(100% - 1.5rem);
1308
+ min-width: 0;
1141
1309
  min-height: 104px;
1142
1310
  color: var(--text);
1143
1311
  background:
@@ -1187,15 +1355,78 @@
1187
1355
 
1188
1356
  .race-data-copy,
1189
1357
  .race-data-number,
1190
- .race-data-link {
1358
+ .race-data-link,
1359
+ .race-data-dismiss {
1191
1360
  position: relative;
1192
1361
  z-index: 1;
1193
1362
  }
1194
1363
 
1364
+ .race-data-dismiss {
1365
+ --race-data-dismiss-icon-color: rgba(244, 247, 251, 0.9);
1366
+ position: absolute;
1367
+ top: 0.44rem;
1368
+ right: 0.48rem;
1369
+ display: grid;
1370
+ place-items: center;
1371
+ width: 0.95rem;
1372
+ height: 0.95rem;
1373
+ padding: 0;
1374
+ overflow: hidden;
1375
+ color: transparent;
1376
+ background: rgba(8, 10, 16, 0.62);
1377
+ border: 1px solid rgba(244, 247, 251, 0.24);
1378
+ border-radius: 999px;
1379
+ font-family: var(--font-mono);
1380
+ font-size: 0;
1381
+ font-weight: 900;
1382
+ line-height: 0;
1383
+ text-transform: none;
1384
+ white-space: nowrap;
1385
+ z-index: 4;
1386
+ }
1387
+
1388
+ .race-data-dismiss::before,
1389
+ .race-data-dismiss::after {
1390
+ content: "";
1391
+ position: absolute;
1392
+ left: 50%;
1393
+ top: 50%;
1394
+ width: 0.48rem;
1395
+ height: 0.1rem;
1396
+ background: var(--race-data-dismiss-icon-color);
1397
+ border-radius: 999px;
1398
+ }
1399
+
1400
+ .race-data-dismiss::before {
1401
+ transform: translate(-50%, -50%) rotate(45deg);
1402
+ }
1403
+
1404
+ .race-data-dismiss::after {
1405
+ transform: translate(-50%, -50%) rotate(-45deg);
1406
+ }
1407
+
1408
+ .race-data-dismiss:hover {
1409
+ --race-data-dismiss-icon-color: #fff;
1410
+ color: transparent;
1411
+ border-color: color-mix(in srgb, var(--driver-color, var(--red)) 70%, #ffffff);
1412
+ background: color-mix(in srgb, var(--driver-color, var(--red)) 52%, rgba(8, 10, 16, 0.72));
1413
+ }
1414
+
1415
+ .race-data-dismiss:focus-visible {
1416
+ outline: 2px solid var(--driver-color, var(--red));
1417
+ outline-offset: 2px;
1418
+ }
1419
+
1420
+ .race-data-panel--with-telemetry .race-data-dismiss,
1421
+ .race-data-panel.is-radio-mode .race-data-dismiss {
1422
+ z-index: 4;
1423
+ }
1424
+
1195
1425
  .race-data-copy {
1196
1426
  grid-column: 2;
1197
1427
  grid-row: 1;
1198
1428
  min-width: 0;
1429
+ overflow: hidden;
1199
1430
  padding: 0.92rem 1.45rem 0.98rem 0.55rem;
1200
1431
  }
1201
1432
 
@@ -1223,6 +1454,9 @@
1223
1454
 
1224
1455
  .race-data-subtitle {
1225
1456
  display: block;
1457
+ overflow: hidden;
1458
+ text-overflow: ellipsis;
1459
+ white-space: nowrap;
1226
1460
  margin-top: 0.28rem;
1227
1461
  color: rgba(244, 247, 251, 0.72);
1228
1462
  font-family: var(--font-mono);
@@ -1259,7 +1493,7 @@
1259
1493
  }
1260
1494
 
1261
1495
  .race-data-panel--with-telemetry {
1262
- grid-template-columns: clamp(6.5rem, 11vw, 8.7rem) minmax(220px, 1fr) minmax(190px, 0.48fr) auto;
1496
+ grid-template-columns: clamp(6.5rem, 11vw, 8.7rem) minmax(0, 1fr) minmax(150px, 0.48fr) auto;
1263
1497
  min-height: 112px;
1264
1498
  }
1265
1499
 
@@ -1308,7 +1542,7 @@
1308
1542
  }
1309
1543
 
1310
1544
  .race-data-panel.is-radio-mode {
1311
- grid-template-columns: minmax(280px, 1fr);
1545
+ grid-template-columns: minmax(0, 1fr);
1312
1546
  }
1313
1547
 
1314
1548
  .race-data-panel.is-radio-mode .race-data-copy {
@@ -1360,9 +1594,13 @@
1360
1594
  place-items: center;
1361
1595
  align-self: center;
1362
1596
  min-width: clamp(132px, 13vw, 176px);
1597
+ max-width: calc(100% - 0.85rem);
1363
1598
  min-height: 3.1rem;
1364
1599
  margin-right: 0.85rem;
1365
1600
  padding: 0 1.05rem;
1601
+ overflow: hidden;
1602
+ text-overflow: ellipsis;
1603
+ white-space: nowrap;
1366
1604
  color: #f6f8fb;
1367
1605
  background: color-mix(in srgb, var(--driver-color, var(--red)) 88%, #111318);
1368
1606
  border: 1px solid color-mix(in srgb, var(--driver-color, var(--red)) 72%, #ffffff);
@@ -1382,6 +1620,9 @@
1382
1620
 
1383
1621
  .telemetry-grid dt {
1384
1622
  display: block;
1623
+ min-width: 0;
1624
+ overflow: hidden;
1625
+ text-overflow: ellipsis;
1385
1626
  margin-bottom: 0.22rem;
1386
1627
  color: var(--muted);
1387
1628
  font-family: var(--font-mono);
@@ -1392,6 +1633,10 @@
1392
1633
  }
1393
1634
 
1394
1635
  .telemetry-grid dd {
1636
+ min-width: 0;
1637
+ overflow: hidden;
1638
+ text-overflow: ellipsis;
1639
+ white-space: nowrap;
1395
1640
  margin: 0;
1396
1641
  font-family: var(--font-mono);
1397
1642
  font-size: clamp(0.86rem, 1.6vw, 1.15rem);
@@ -1417,9 +1662,10 @@
1417
1662
  }
1418
1663
 
1419
1664
  .telemetry-grid > div {
1665
+ min-width: 0;
1420
1666
  display: flex;
1421
1667
  justify-content: space-between;
1422
- gap: 1rem;
1668
+ gap: 0.55rem;
1423
1669
  padding: 0.82rem 0.7rem;
1424
1670
  background: var(--surface-panel);
1425
1671
  }
@@ -1621,6 +1867,8 @@
1621
1867
  .telemetry-lap-table,
1622
1868
  .telemetry-sector-table {
1623
1869
  width: 100%;
1870
+ min-width: 0;
1871
+ table-layout: fixed;
1624
1872
  border-collapse: collapse;
1625
1873
  overflow: hidden;
1626
1874
  background: rgba(255, 255, 255, 0.045);
@@ -1641,10 +1889,13 @@
1641
1889
  .telemetry-lap-table td,
1642
1890
  .telemetry-sector-table th,
1643
1891
  .telemetry-sector-table td {
1892
+ overflow: hidden;
1644
1893
  padding: 0.55rem 0.5rem;
1645
1894
  font-size: 0.68rem;
1646
1895
  line-height: 1;
1647
1896
  text-align: right;
1897
+ text-overflow: ellipsis;
1898
+ white-space: nowrap;
1648
1899
  }
1649
1900
 
1650
1901
  .telemetry-lap-table th,
@@ -2202,6 +2453,13 @@
2202
2453
  min-height: 48px;
2203
2454
  }
2204
2455
 
2456
+ .race-data-dismiss {
2457
+ top: 0.38rem;
2458
+ right: 0.42rem;
2459
+ width: 0.95rem;
2460
+ height: 0.95rem;
2461
+ }
2462
+
2205
2463
  .sim-telemetry {
2206
2464
  display: block;
2207
2465
  }
@@ -0,0 +1,69 @@
1
+ import { createLoadingMarkup } from './templateUtils.js';
2
+
3
+ export function createStewardMessageMarkup() {
4
+ return `
5
+ <div class="steward-message is-hidden" data-paddock-component="steward-message" data-steward-message aria-live="polite">
6
+ <span class="steward-message__kicker" data-steward-message-kicker>Race control</span>
7
+ <strong data-steward-message-title>--</strong>
8
+ <span data-steward-message-detail>--</span>
9
+ </div>
10
+ `;
11
+ }
12
+
13
+ export function createRaceDataPanelMarkup({ ui = {} } = {}) {
14
+ const sizeMode = ui.raceDataBannerSize === 'auto' ? 'auto' : 'custom';
15
+ const telemetryDetail = Boolean(ui.raceDataTelemetryDetail);
16
+ const classNames = ['race-data-panel', `race-data-panel--${sizeMode}`];
17
+ if (telemetryDetail) classNames.push('race-data-panel--with-telemetry');
18
+ return `
19
+ <div class="${classNames.join(' ')}" data-paddock-component="race-data-panel" data-race-data-panel aria-live="polite">
20
+ <button class="race-data-dismiss" type="button" data-race-data-dismiss aria-label="Close race data pill">x</button>
21
+ <div class="race-data-copy">
22
+ <span class="race-data-kicker" data-race-data-kicker>Project</span>
23
+ <strong data-race-data-title>Select driver</strong>
24
+ <span class="race-data-subtitle" data-race-data-subtitle>Race entry</span>
25
+ </div>
26
+ ${telemetryDetail ? createRaceDataTelemetryMarkup() : ''}
27
+ <strong class="race-data-number" data-race-data-number>--</strong>
28
+ <button class="race-data-link" type="button" data-race-data-open>Open project</button>
29
+ ${createLoadingMarkup('Race data')}
30
+ </div>
31
+ `;
32
+ }
33
+
34
+ function createRaceDataTelemetryMarkup() {
35
+ return `
36
+ <div class="race-data-telemetry" data-race-data-telemetry aria-label="Project sector telemetry">
37
+ <span class="race-data-telemetry__label">Sectors</span>
38
+ <div class="race-data-telemetry__bars">
39
+ ${[1, 2, 3].map((sector) => `
40
+ <div class="telemetry-sector-bar race-data-sector-bar" data-telemetry-sector-bar="${sector}" style="--sector-fill: 0%">
41
+ <span>S${sector}</span>
42
+ <strong data-telemetry-sector-time="${sector}">--</strong>
43
+ </div>
44
+ `).join('')}
45
+ </div>
46
+ </div>
47
+ `;
48
+ }
49
+
50
+ export function createTelemetrySectorBannerMarkup() {
51
+ return `
52
+ <section class="telemetry-sector-banner" data-paddock-component="telemetry-sector-banner" data-telemetry-sector-banner aria-label="Broadcast sector telemetry">
53
+ <div class="telemetry-sector-banner__copy">
54
+ <span><b data-selected-code>--</b> sector telemetry</span>
55
+ <strong data-selected-name>Select driver</strong>
56
+ <em data-telemetry-current-sector>S1</em>
57
+ </div>
58
+ <div class="telemetry-sector-banner__bars">
59
+ ${[1, 2, 3].map((sector) => `
60
+ <div class="telemetry-sector-bar" data-telemetry-sector-bar="${sector}" style="--sector-fill: 0%">
61
+ <span>S${sector}</span>
62
+ <strong data-telemetry-sector-time="${sector}">--</strong>
63
+ </div>
64
+ `).join('')}
65
+ </div>
66
+ ${createLoadingMarkup('Sector banner')}
67
+ </section>
68
+ `;
69
+ }