@inventure71/paddockjs 4.2.0 → 4.3.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/CHANGELOG.md +34 -0
- package/INSTALL_AND_UPDATE.md +23 -4
- package/README.md +31 -2
- package/docs/advanced_ai_pace.md +131 -0
- package/docs/advanced_physics_rebuild.md +166 -0
- package/docs/architecture.md +109 -30
- package/docs/cleanup_coverage.md +284 -0
- package/docs/component_inventory.md +31 -15
- package/docs/composable-layouts.md +2 -0
- package/docs/concepts.md +12 -1
- package/docs/custom_model_controller.md +1 -1
- package/docs/data_contract.md +33 -10
- package/docs/data_model.md +4 -2
- package/docs/efficiency_cleanup_2026-10-13.md +80 -0
- package/docs/grid_walk.md +80 -0
- package/docs/index.md +1 -0
- package/docs/keyboard_driving_fix.md +118 -0
- package/docs/limit_driving_2026-10-14.md +88 -0
- package/docs/maintainability_audit_2026-09-08.md +146 -0
- package/docs/racing_ai_improvements.md +145 -0
- package/docs/rules.md +43 -13
- package/docs/sense_contract.md +4 -1
- package/docs/system_specs.md +36 -11
- package/docs/testing-reliability-guide.md +5 -0
- package/docs/v11_improvements.md +108 -0
- package/package.json +23 -13
- package/src/api/MountedSimulatorComponents.js +101 -0
- package/src/api/PaddockSimulatorController.js +56 -81
- package/src/app/BrowserExpertAdapter.js +0 -6
- package/src/app/BrowserExpertExternalRenderer.js +72 -50
- package/src/app/ExternalTrackSnapshot.js +50 -0
- package/src/app/F1SimulatorApp.js +242 -297
- package/src/app/HostLifecycleEmitter.js +93 -0
- package/src/app/banners/raceDataBannerController.js +9 -4
- package/src/app/camera/cameraConstants.js +1 -1
- package/src/app/camera/cameraController.js +10 -13
- package/src/app/camera/cameraGeometry.js +1 -1
- package/src/app/controls/SimulatorViewControls.js +45 -0
- package/src/app/controls/workbenchBindings.js +64 -0
- package/src/app/controls/workbenchDisclosure.js +41 -0
- package/src/app/domBindings.js +21 -13
- package/src/app/gridWalk/GridWalkController.js +180 -0
- package/src/app/gridWalk/gridWalkCamera.js +88 -0
- package/src/app/gridWalk/gridWalkControls.js +24 -0
- package/src/app/gridWalk/gridWalkCrew.js +246 -0
- package/src/app/gridWalk/gridWalkLabels.js +104 -0
- package/src/app/gridWalk/gridWalkLayout.js +46 -0
- package/src/app/gridWalk/gridWalkNavigation.js +99 -0
- package/src/app/gridWalk/gridWalkProjectBanner.js +43 -0
- package/src/app/gridWalk/gridWalkSession.js +50 -0
- package/src/app/layoutSupport.js +2 -0
- package/src/app/raceOverlayClearanceSupport.js +21 -7
- package/src/app/readouts/carOverviewRenderer.js +9 -4
- package/src/app/readouts/readoutFormatters.js +1 -8
- package/src/app/readouts/telemetryRenderer.js +1 -3
- package/src/app/readouts/timingTowerRenderer.js +48 -50
- package/src/app/rendering/appAssets.js +8 -10
- package/src/app/rendering/carRenderer.js +27 -79
- package/src/app/rendering/displayUtils.js +0 -6
- package/src/app/rendering/expertSensorRenderer.js +1 -1
- package/src/app/rendering/replayGhostRenderer.js +4 -1
- package/src/app/rendering/trackRenderer.js +2 -2
- package/src/app/runtime/frameLoop.js +18 -17
- package/src/app/runtime/pixiStartupLifecycle.js +94 -0
- package/src/app/runtime/runtimeTiming.js +4 -1
- package/src/app/runtime/runtimeVisibility.js +7 -2
- package/src/config/cssValues.js +1 -1
- package/src/config/defaultOptions.js +12 -173
- package/src/config/restartOptions.js +1 -0
- package/src/config/runtimeSeed.js +10 -0
- package/src/config/simulatorDefaults.js +106 -0
- package/src/config/telemetryModules.js +20 -0
- package/src/config/theme/cssTokens.js +85 -0
- package/src/config/theme/themeCss.js +72 -0
- package/src/config/theme/themeInputs.js +155 -0
- package/src/config/theme/themeResolver.js +172 -0
- package/src/config/theme/themeSelectors.js +69 -0
- package/src/config/theme/themeTokens.js +257 -0
- package/src/config/theme/themeUtils.js +15 -0
- package/src/config/themeOptions.js +14 -830
- package/src/config/timingGapMode.js +0 -2
- package/src/config/uiOptions.js +53 -0
- package/src/data/championship.js +16 -158
- package/src/data/championshipDefaults.js +87 -0
- package/src/data/driverData.js +10 -20
- package/src/data/entryIdentity.js +67 -0
- package/src/data/entryPresentation.js +23 -0
- package/src/data/entryTypes.d.ts +160 -0
- package/src/data/index.d.ts +13 -259
- package/src/data/ratingScale.js +14 -0
- package/src/data/teamTypes.d.ts +23 -0
- package/src/data/trackTypes.d.ts +101 -0
- package/src/data/vehicleData.js +12 -23
- package/src/environment/actions.js +3 -2
- package/src/environment/controllerLoop.js +57 -44
- package/src/environment/controllerScheduler.js +30 -0
- package/src/environment/driverObservations.js +260 -0
- package/src/environment/driverReset.js +51 -0
- package/src/environment/episode.js +0 -1
- package/src/environment/evaluation.js +6 -90
- package/src/environment/evaluationTracker.js +100 -0
- package/src/environment/events.js +23 -0
- package/src/environment/index.d.ts +21 -140
- package/src/environment/metrics.js +3 -17
- package/src/environment/observationSchema.js +104 -0
- package/src/environment/observationVector.js +16 -115
- package/src/environment/observations.js +16 -316
- package/src/environment/options.js +5 -24
- package/src/environment/renderFrames.js +21 -0
- package/src/environment/resetOptions.js +41 -0
- package/src/environment/results.js +125 -0
- package/src/environment/rewards.js +10 -16
- package/src/environment/runtime.js +17 -300
- package/src/environment/scenarioOptions.js +90 -0
- package/src/environment/scenarioPlacements.js +70 -0
- package/src/environment/scenarioPresets.js +78 -0
- package/src/environment/scenarios.js +5 -236
- package/src/environment/scratch.js +18 -0
- package/src/environment/sensorOptions.js +20 -0
- package/src/environment/sensors/index.js +2 -556
- package/src/environment/sensors/indexedRayBands.js +0 -18
- package/src/environment/sensors/nearbyCars.js +18 -15
- package/src/environment/sensors/opponentRadar.js +3 -2
- package/src/environment/sensors/pitConnectorProximity.js +20 -0
- package/src/environment/sensors/rayBandTrace.js +1 -37
- package/src/environment/sensors/rayBatch.js +81 -0
- package/src/environment/sensors/rayConfig.js +26 -0
- package/src/environment/sensors/rayGeometry.js +0 -8
- package/src/environment/sensors/rayGuards.js +0 -6
- package/src/environment/sensors/rayOutputs.js +75 -0
- package/src/environment/sensors/rayQuery.js +110 -0
- package/src/environment/sensors/rayScratch.js +146 -0
- package/src/environment/sensors/sensorTargets.js +12 -4
- package/src/environment/sensors/surfaceRays.js +6 -61
- package/src/environment/sensors/trackRays.js +7 -80
- package/src/environment/simulationSetup.js +30 -0
- package/src/environment/specs.js +15 -109
- package/src/index.d.ts +34 -259
- package/src/index.js +28 -6
- package/src/placeholder/index.js +1 -8
- package/src/rendering/displayLifecycle.js +6 -0
- package/src/rendering/proceduralTrackAsset.js +1 -1
- package/src/rendering/renderSnapshot.js +3 -0
- package/src/rendering/track/finishLineRenderer.js +1 -1
- package/src/rendering/track/gridRenderer.js +1 -1
- package/src/rendering/track/offsetStrokeSafety.js +2 -2
- package/src/rendering/track/pitLaneRenderer.js +1 -1
- package/src/rendering/track/trackMaterialRenderer.js +4 -4
- package/src/rendering/track/trackRenderConstants.js +0 -1
- package/src/rendering/track/trackRenderGeometry.js +1 -7
- package/src/simulation/collisionGeometry.js +5 -11
- package/src/simulation/driver/advancedPathControls.js +53 -0
- package/src/simulation/driver/advancedRacePace.js +112 -0
- package/src/simulation/driver/advancedRacingControls.js +61 -0
- package/src/simulation/driver/advancedRacingEnvelope.js +255 -0
- package/src/simulation/driver/advancedRacingSteering.js +76 -0
- package/src/simulation/driver/advancedRacingTiming.js +16 -0
- package/src/simulation/driver/arcadeRacePace.js +83 -0
- package/src/simulation/driver/arcadeRacingControls.js +111 -0
- package/src/simulation/driver/arcadeRacingEnvelope.js +63 -0
- package/src/simulation/driver/driverControlConstants.js +0 -2
- package/src/simulation/driver/driverController.js +14 -3
- package/src/simulation/driver/edgeRecovery.js +0 -1
- package/src/simulation/driver/racingControls.js +10 -406
- package/src/simulation/driver/racingDriverProfile.js +48 -0
- package/src/simulation/driver/racingLinePlan.js +48 -15
- package/src/simulation/driver/rejoinControls.js +58 -111
- package/src/simulation/driver/safetyCarControls.js +12 -0
- package/src/simulation/driver/trafficScan.js +89 -6
- package/src/simulation/pit/pitCarPlacement.js +29 -0
- package/src/simulation/pit/pitFlow.js +8 -30
- package/src/simulation/pit/pitIntent.js +4 -3
- package/src/simulation/pit/pitOccupancy.js +2 -2
- package/src/simulation/pit/pitQueue.js +2 -18
- package/src/simulation/pit/pitRouteMovement.js +1 -36
- package/src/simulation/pit/pitRouting.js +5 -81
- package/src/simulation/pit/pitService.js +3 -32
- package/src/simulation/pit/pitServiceConstants.js +1 -0
- package/src/simulation/pit/pitServiceProgress.js +34 -0
- package/src/simulation/pit/pitSnapshots.js +8 -4
- package/src/simulation/pit/pitState.js +1 -5
- package/src/simulation/race/classification.js +1 -8
- package/src/simulation/race/raceFinish.js +3 -8
- package/src/simulation/race/raceOrder.js +2 -13
- package/src/simulation/race/raceProgress.js +6 -20
- package/src/simulation/race/raceSetup.js +1 -1
- package/src/simulation/race/raceStep.js +1 -1
- package/src/simulation/race/redFlag.js +2 -4
- package/src/simulation/race/safetyCar.js +3 -8
- package/src/simulation/race/stalledDnf.js +2 -4
- package/src/simulation/raceSimulation.js +6 -38
- package/src/simulation/replay/replayGhosts.js +44 -32
- package/src/simulation/rules/collisionSteward.js +0 -14
- package/src/simulation/rules/penaltyActions.js +37 -0
- package/src/simulation/rules/penaltyLedger.js +8 -1
- package/src/simulation/rules/rulesReview.js +1 -7
- package/src/simulation/simMath.js +4 -0
- package/src/simulation/snapshots/raceSnapshots.js +30 -11
- package/src/simulation/timing/drsState.js +8 -0
- package/src/simulation/timing/drsTiming.js +2 -4
- package/src/simulation/timing/lapTelemetry.js +7 -4
- package/src/simulation/timing/sectorPerformance.js +3 -3
- package/src/simulation/timing/timingLines.js +12 -6
- package/src/simulation/track/pitLaneAccess.js +2 -2
- package/src/simulation/track/pitLaneLayout.js +4 -135
- package/src/simulation/track/pitLaneModel.js +70 -0
- package/src/simulation/track/pitLaneState.js +0 -1
- package/src/simulation/track/pitServiceLayout.js +121 -0
- package/src/simulation/track/proceduralTrack.js +77 -0
- package/src/simulation/track/spatialQueries.js +1 -25
- package/src/simulation/track/trackBuilder.js +54 -0
- package/src/simulation/track/trackConstants.js +0 -22
- package/src/simulation/track/trackMath.js +4 -29
- package/src/simulation/track/trackModel.js +4 -241
- package/src/simulation/track/trackModelCache.js +105 -0
- package/src/simulation/track/trackNearestQueries.js +1218 -0
- package/src/simulation/track/trackQueryConstants.js +3 -0
- package/src/simulation/track/trackQueryConstruction.js +368 -0
- package/src/simulation/track/trackQueryIndex.js +12 -1933
- package/src/simulation/track/trackQuerySegments.js +174 -0
- package/src/simulation/track/trackQueryStats.js +12 -0
- package/src/simulation/track/trackRayQueries.js +165 -0
- package/src/simulation/vehicle/advancedContactResponse.js +89 -0
- package/src/simulation/vehicle/advancedTireForces.js +92 -0
- package/src/simulation/vehicle/advancedVehicleModel.js +65 -0
- package/src/simulation/vehicle/advancedVehiclePhysics.js +127 -0
- package/src/simulation/vehicle/contactResolution.js +20 -6
- package/src/simulation/vehicle/mainTrackWheelSurface.js +2 -13
- package/src/simulation/vehicle/pitWheelSurface.js +0 -12
- package/src/simulation/vehicle/runoffResponse.js +2 -4
- package/src/simulation/vehicle/sampledWheelSurface.js +266 -0
- package/src/simulation/vehicle/vehicleGeometry.js +0 -56
- package/src/simulation/vehicle/vehicleKinematics.js +10 -0
- package/src/simulation/vehicle/vehiclePhysics.js +35 -194
- package/src/simulation/vehicle/vehicleSnapshots.js +13 -4
- package/src/simulation/vehicle/vehicleState.js +2 -1
- package/src/simulation/vehicle/wheelSurface.js +13 -432
- package/src/simulation/vehicle/wheelSurfaceCache.js +55 -0
- package/src/simulation/vehicle/wheelSurfaceSummary.js +101 -0
- package/src/styles.css +173 -19
- package/src/ui/bannerTemplates.js +4 -2
- package/src/ui/cameraControlsTemplate.js +4 -2
- package/src/ui/gridWalkTemplate.js +29 -0
- package/src/ui/htmlEscaping.js +8 -0
- package/src/ui/shellTemplate.js +3 -0
- package/src/ui/telemetryDrawerTemplate.js +4 -2
- package/src/ui/telemetryTemplates.js +3 -22
- package/src/ui/templateUtils.js +2 -8
- package/src/ui/viewControlsTemplate.js +21 -0
package/src/styles.css
CHANGED
|
@@ -552,7 +552,7 @@
|
|
|
552
552
|
.f1-sim-component h1 {
|
|
553
553
|
margin: 0;
|
|
554
554
|
font-size: clamp(1.8rem, 4vw, 4.6rem);
|
|
555
|
-
line-height:
|
|
555
|
+
line-height: 1.15;
|
|
556
556
|
text-transform: uppercase;
|
|
557
557
|
letter-spacing: 0;
|
|
558
558
|
}
|
|
@@ -580,11 +580,6 @@
|
|
|
580
580
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
581
581
|
}
|
|
582
582
|
|
|
583
|
-
:where(.f1-sim-component, .f1-sim-component *).sim-topbar h1 {
|
|
584
|
-
line-height: 0.92;
|
|
585
|
-
min-height: 1.08em;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
583
|
:where(.f1-sim-component, .f1-sim-component *).sim-control,
|
|
589
584
|
:where(.f1-sim-component, .f1-sim-component *).sim-backlink {
|
|
590
585
|
text-align: center;
|
|
@@ -1426,6 +1421,7 @@
|
|
|
1426
1421
|
width: 100% !important;
|
|
1427
1422
|
height: 100% !important;
|
|
1428
1423
|
display: block;
|
|
1424
|
+
touch-action: auto;
|
|
1429
1425
|
}
|
|
1430
1426
|
|
|
1431
1427
|
:where(.f1-sim-component, .f1-sim-component *).physics-mode-indicator {
|
|
@@ -1532,7 +1528,7 @@
|
|
|
1532
1528
|
position: relative;
|
|
1533
1529
|
z-index: 13;
|
|
1534
1530
|
display: flex;
|
|
1535
|
-
align-items:
|
|
1531
|
+
align-items: stretch;
|
|
1536
1532
|
justify-content: space-between;
|
|
1537
1533
|
gap: 0.7rem;
|
|
1538
1534
|
min-height: var(--race-toolbar-height);
|
|
@@ -1545,6 +1541,10 @@
|
|
|
1545
1541
|
flex: 1 1 auto;
|
|
1546
1542
|
}
|
|
1547
1543
|
|
|
1544
|
+
:where(.f1-sim-component, .f1-sim-component *).race-telemetry-drawer__toolbar [data-paddock-component='camera-controls'] button {
|
|
1545
|
+
flex: 1 1 auto;
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
1548
|
:where(.f1-sim-component, .f1-sim-component *).race-telemetry-drawer__race {
|
|
1549
1549
|
position: relative;
|
|
1550
1550
|
min-width: 0;
|
|
@@ -1569,8 +1569,9 @@
|
|
|
1569
1569
|
|
|
1570
1570
|
:where(.f1-sim-component, .f1-sim-component *).race-telemetry-drawer__controls {
|
|
1571
1571
|
min-width: 0;
|
|
1572
|
-
|
|
1573
|
-
|
|
1572
|
+
flex: 0 0 auto;
|
|
1573
|
+
display: grid;
|
|
1574
|
+
grid-template-columns: repeat(2, max-content);
|
|
1574
1575
|
justify-content: flex-end;
|
|
1575
1576
|
gap: 0.45rem;
|
|
1576
1577
|
}
|
|
@@ -1680,6 +1681,14 @@
|
|
|
1680
1681
|
border-color: var(--text);
|
|
1681
1682
|
}
|
|
1682
1683
|
|
|
1684
|
+
:where(.f1-sim-component, .f1-sim-component *).camera-controls__zoom {
|
|
1685
|
+
display: flex;
|
|
1686
|
+
gap: 0.35rem;
|
|
1687
|
+
flex: 2 1 auto;
|
|
1688
|
+
min-width: calc(88px + 0.35rem);
|
|
1689
|
+
}
|
|
1690
|
+
:where(.f1-sim-component, .f1-sim-component *).camera-controls__zoom button { flex: 1 1 44px; }
|
|
1691
|
+
|
|
1683
1692
|
:where(.f1-sim-component, .f1-sim-component *).steward-message {
|
|
1684
1693
|
--steward-panel-bg: rgba(8, 9, 11, 0.92);
|
|
1685
1694
|
--steward-chip-bg: color-mix(in srgb, var(--steward-color, var(--yellow)) 18%, rgba(8, 9, 11, 0.94));
|
|
@@ -3592,7 +3601,7 @@
|
|
|
3592
3601
|
}
|
|
3593
3602
|
}
|
|
3594
3603
|
|
|
3595
|
-
:where(.f1-sim-component, .f1-sim-component *).race-data-panel--standalone {
|
|
3604
|
+
:where(.f1-sim-component, .f1-sim-component *).race-data-panel.race-data-panel--standalone {
|
|
3596
3605
|
position: relative;
|
|
3597
3606
|
left: auto;
|
|
3598
3607
|
right: auto;
|
|
@@ -3604,29 +3613,29 @@
|
|
|
3604
3613
|
min-height: 0;
|
|
3605
3614
|
}
|
|
3606
3615
|
|
|
3607
|
-
:where(.f1-sim-component, .f1-sim-component *).race-data-panel--standalone .race-data-copy {
|
|
3616
|
+
:where(.f1-sim-component, .f1-sim-component *).race-data-panel.race-data-panel--standalone .race-data-copy {
|
|
3608
3617
|
grid-column: 2;
|
|
3609
3618
|
grid-row: 1;
|
|
3610
3619
|
min-width: 0;
|
|
3611
3620
|
padding: 0.75rem 0.68rem 0.55rem;
|
|
3612
3621
|
}
|
|
3613
3622
|
|
|
3614
|
-
:where(.f1-sim-component, .f1-sim-component *).race-data-panel--standalone .race-data-kicker {
|
|
3623
|
+
:where(.f1-sim-component, .f1-sim-component *).race-data-panel.race-data-panel--standalone .race-data-kicker {
|
|
3615
3624
|
font-size: 0.54rem;
|
|
3616
3625
|
letter-spacing: 0.12em;
|
|
3617
3626
|
}
|
|
3618
3627
|
|
|
3619
|
-
:where(.f1-sim-component, .f1-sim-component *).race-data-panel--standalone .race-data-copy strong {
|
|
3628
|
+
:where(.f1-sim-component, .f1-sim-component *).race-data-panel.race-data-panel--standalone .race-data-copy strong {
|
|
3620
3629
|
font-size: clamp(1.05rem, 3vw, 1.45rem);
|
|
3621
3630
|
line-height: 0.98;
|
|
3622
3631
|
}
|
|
3623
3632
|
|
|
3624
|
-
:where(.f1-sim-component, .f1-sim-component *).race-data-panel--standalone .race-data-subtitle {
|
|
3633
|
+
:where(.f1-sim-component, .f1-sim-component *).race-data-panel.race-data-panel--standalone .race-data-subtitle {
|
|
3625
3634
|
font-size: 0.55rem;
|
|
3626
3635
|
letter-spacing: 0.06em;
|
|
3627
3636
|
}
|
|
3628
3637
|
|
|
3629
|
-
:where(.f1-sim-component, .f1-sim-component *).race-data-panel--standalone .race-data-number {
|
|
3638
|
+
:where(.f1-sim-component, .f1-sim-component *).race-data-panel.race-data-panel--standalone .race-data-number {
|
|
3630
3639
|
grid-column: 1;
|
|
3631
3640
|
grid-row: 1;
|
|
3632
3641
|
min-width: 0;
|
|
@@ -3634,7 +3643,7 @@
|
|
|
3634
3643
|
font-size: clamp(2.3rem, 5vw, 3.9rem);
|
|
3635
3644
|
}
|
|
3636
3645
|
|
|
3637
|
-
:where(.f1-sim-component, .f1-sim-component *).race-data-panel--standalone .race-data-link {
|
|
3646
|
+
:where(.f1-sim-component, .f1-sim-component *).race-data-panel.race-data-panel--standalone .race-data-link {
|
|
3638
3647
|
grid-column: 1 / -1;
|
|
3639
3648
|
grid-row: 2;
|
|
3640
3649
|
margin: 0;
|
|
@@ -3643,18 +3652,163 @@
|
|
|
3643
3652
|
font-size: 0.62rem;
|
|
3644
3653
|
}
|
|
3645
3654
|
|
|
3646
|
-
:where(.f1-sim-component, .f1-sim-component *).race-data-panel--standalone.race-data-panel--with-telemetry {
|
|
3655
|
+
:where(.f1-sim-component, .f1-sim-component *).race-data-panel.race-data-panel--standalone.race-data-panel--with-telemetry {
|
|
3647
3656
|
grid-template-columns: minmax(4.6rem, 0.35fr) minmax(0, 1fr);
|
|
3648
3657
|
}
|
|
3649
3658
|
|
|
3650
|
-
:where(.f1-sim-component, .f1-sim-component *).race-data-panel--standalone.race-data-panel--with-telemetry .race-data-copy {
|
|
3659
|
+
:where(.f1-sim-component, .f1-sim-component *).race-data-panel.race-data-panel--standalone.race-data-panel--with-telemetry .race-data-copy {
|
|
3651
3660
|
grid-column: 2;
|
|
3652
3661
|
padding-bottom: 0.35rem;
|
|
3653
3662
|
}
|
|
3654
3663
|
|
|
3655
|
-
:where(.f1-sim-component, .f1-sim-component *).race-data-panel--standalone .race-data-telemetry {
|
|
3664
|
+
:where(.f1-sim-component, .f1-sim-component *).race-data-panel.race-data-panel--standalone .race-data-telemetry {
|
|
3656
3665
|
grid-column: 1 / -1;
|
|
3657
3666
|
grid-row: auto;
|
|
3658
3667
|
padding: 0 0.68rem 0.65rem;
|
|
3659
3668
|
border-left: 0;
|
|
3660
3669
|
}
|
|
3670
|
+
|
|
3671
|
+
/* Grid Walk positions shared simulator controls over the existing world. */
|
|
3672
|
+
:where(.f1-sim-component, .f1-sim-component *) .grid-walk {
|
|
3673
|
+
position: absolute;
|
|
3674
|
+
inset: 0;
|
|
3675
|
+
z-index: 24;
|
|
3676
|
+
overflow: hidden;
|
|
3677
|
+
pointer-events: none;
|
|
3678
|
+
color: var(--text);
|
|
3679
|
+
}
|
|
3680
|
+
.grid-walk [hidden] { display: none !important; }
|
|
3681
|
+
.is-grid-walk-covered { visibility: hidden !important; pointer-events: none !important; }
|
|
3682
|
+
.grid-walk button, .grid-walk select, .grid-walk a { pointer-events: auto; touch-action: manipulation; }
|
|
3683
|
+
.grid-walk :is(button, select, a, [data-grid-heading]):focus-visible { outline: 3px solid var(--blue); outline-offset: 3px; }
|
|
3684
|
+
.grid-walk .grid-walk__toolbar {
|
|
3685
|
+
position: absolute;
|
|
3686
|
+
top: 12px;
|
|
3687
|
+
left: 12px;
|
|
3688
|
+
right: 12px;
|
|
3689
|
+
display: flex;
|
|
3690
|
+
flex-wrap: wrap;
|
|
3691
|
+
align-items: flex-start;
|
|
3692
|
+
justify-content: center;
|
|
3693
|
+
gap: 8px;
|
|
3694
|
+
}
|
|
3695
|
+
.grid-walk .grid-walk__toolbar > .camera-controls {
|
|
3696
|
+
position: static;
|
|
3697
|
+
max-width: 100%;
|
|
3698
|
+
box-sizing: border-box;
|
|
3699
|
+
background: var(--paddock-camera-controls-background, var(--surface-panel));
|
|
3700
|
+
}
|
|
3701
|
+
:where(.f1-sim-component, .f1-sim-component *).paddock-view-controls[hidden] { display: none !important; }
|
|
3702
|
+
.paddock-view-controls [hidden] { display: none !important; }
|
|
3703
|
+
:where(.f1-sim-component, .f1-sim-component *).paddock-view-controls { flex: 0 0 auto; }
|
|
3704
|
+
.race-telemetry-drawer__toolbar .paddock-view-controls { flex: 0 0 auto; }
|
|
3705
|
+
.paddock-view-controls button:focus-visible { outline: 3px solid var(--blue); outline-offset: 3px; }
|
|
3706
|
+
.paddock-view-controls.camera-controls button[data-grid-action='start'] {
|
|
3707
|
+
color: #08090b;
|
|
3708
|
+
background: var(--green);
|
|
3709
|
+
border-color: var(--green);
|
|
3710
|
+
}
|
|
3711
|
+
.paddock-view-controls.camera-controls button[data-grid-action='stop'] {
|
|
3712
|
+
color: #ffffff;
|
|
3713
|
+
background: var(--race-control-red);
|
|
3714
|
+
border-color: var(--race-control-red);
|
|
3715
|
+
}
|
|
3716
|
+
/* Intrinsic sizing reserves the longest action label in both states. */
|
|
3717
|
+
:where(.f1-sim-component, .f1-sim-component *) button[data-size-label] { display: grid; place-items: center; }
|
|
3718
|
+
:where(.f1-sim-component, .f1-sim-component *) button[data-size-label]::after { content: attr(data-size-label); visibility: hidden; grid-area: 1 / 1; }
|
|
3719
|
+
:where(.f1-sim-component, .f1-sim-component *) button[data-size-label] > [data-control-label] { grid-area: 1 / 1; }
|
|
3720
|
+
.grid-walk .grid-walk__entry {
|
|
3721
|
+
position: absolute;
|
|
3722
|
+
top: 0;
|
|
3723
|
+
left: 0;
|
|
3724
|
+
width: 180px;
|
|
3725
|
+
min-height: 44px;
|
|
3726
|
+
padding: 8px 10px;
|
|
3727
|
+
box-sizing: border-box;
|
|
3728
|
+
text-align: left;
|
|
3729
|
+
white-space: normal;
|
|
3730
|
+
font-family: var(--font-display);
|
|
3731
|
+
color: var(--text);
|
|
3732
|
+
background: var(--surface-panel);
|
|
3733
|
+
border: 1px solid var(--line);
|
|
3734
|
+
border-inline-start: 3px solid var(--driver-color, var(--red));
|
|
3735
|
+
box-shadow: 0 3px 12px #0003;
|
|
3736
|
+
cursor: pointer;
|
|
3737
|
+
}
|
|
3738
|
+
.grid-walk .grid-walk__entry:hover { background: var(--surface-raised); border-color: var(--text); }
|
|
3739
|
+
.grid-walk .grid-walk__entry { touch-action: none; }
|
|
3740
|
+
.grid-walk .grid-walk__entry[aria-pressed='true'] {
|
|
3741
|
+
border-color: var(--driver-color, var(--red));
|
|
3742
|
+
box-shadow: inset 0 0 0 1px var(--driver-color, var(--red)), 0 3px 12px #0003;
|
|
3743
|
+
}
|
|
3744
|
+
.grid-walk__entry strong {
|
|
3745
|
+
display: -webkit-box;
|
|
3746
|
+
-webkit-box-orient: vertical;
|
|
3747
|
+
-webkit-line-clamp: 3;
|
|
3748
|
+
overflow: hidden;
|
|
3749
|
+
font-size: .78rem;
|
|
3750
|
+
line-height: 1.25;
|
|
3751
|
+
overflow-wrap: anywhere;
|
|
3752
|
+
}
|
|
3753
|
+
.grid-walk__position { display: block; font: 10px var(--font-mono); color: var(--muted); }
|
|
3754
|
+
.grid-walk[data-compact-labels='true'] .grid-walk__entry { padding: 5px 8px; }
|
|
3755
|
+
.grid-walk[data-compact-labels='true'] .grid-walk__entry strong { -webkit-line-clamp: 1; }
|
|
3756
|
+
.grid-walk__connectors { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; stroke: var(--muted); stroke-width: 1; }
|
|
3757
|
+
.grid-walk .grid-walk__toolbar > .grid-walk__navigation {
|
|
3758
|
+
width: 360px;
|
|
3759
|
+
min-width: min(240px, 100%);
|
|
3760
|
+
max-width: min(360px, 100%);
|
|
3761
|
+
flex: 0 1 360px;
|
|
3762
|
+
flex-wrap: nowrap;
|
|
3763
|
+
align-items: center;
|
|
3764
|
+
}
|
|
3765
|
+
.grid-walk[data-compact-controls='true'] .grid-walk__toolbar > .grid-walk__navigation { min-width: min(180px, 100%); flex: 1 1 180px; }
|
|
3766
|
+
.grid-walk__navigation button { flex: 0 0 44px; }
|
|
3767
|
+
.grid-walk__navigation label { min-width: 0; flex: 1; }
|
|
3768
|
+
.grid-walk .grid-walk__navigation select { display: block; width: 100%; min-height: 44px; font-family: var(--font-mono); padding: 6px; background: var(--surface-panel); text-align: center; text-align-last: center; }
|
|
3769
|
+
.grid-walk[data-view] [data-grid-project-banner] {
|
|
3770
|
+
position: absolute;
|
|
3771
|
+
left: 50%;
|
|
3772
|
+
bottom: 12px;
|
|
3773
|
+
width: calc(100% - 24px);
|
|
3774
|
+
max-width: 1080px;
|
|
3775
|
+
transform: translateX(-50%);
|
|
3776
|
+
pointer-events: auto;
|
|
3777
|
+
min-height: 0;
|
|
3778
|
+
overflow: auto;
|
|
3779
|
+
}
|
|
3780
|
+
.grid-walk__status, .grid-walk__sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
|
|
3781
|
+
.sim-canvas-panel[data-grid-walk-active='true'] [data-track-canvas] canvas { touch-action: none; cursor: grab !important; }
|
|
3782
|
+
.sim-canvas-panel[data-grid-walk-active='true'] [data-grid-dragging='true'] canvas { cursor: grabbing !important; }
|
|
3783
|
+
.sim-canvas-panel[data-grid-walk-active='true'] > :is(.sim-timing, .timing-panel-toggle, .fps-counter, .start-lights, .steward-message, .camera-controls, .race-data-panel, .telemetry-sector-banner, .race-finish-panel) { display: none !important; }
|
|
3784
|
+
@media (prefers-reduced-motion: reduce) { .grid-walk .race-data-panel { transition: none; } }
|
|
3785
|
+
|
|
3786
|
+
/* Opt-in shell chrome uses its container, including embeds on wide pages.
|
|
3787
|
+
This reuses the shell's stacked composition without changing board sizing. */
|
|
3788
|
+
@container (max-width: 900px) {
|
|
3789
|
+
.sim-shell:has(.grid-walk) .sim-grid { grid-template-columns: minmax(0, 1fr); }
|
|
3790
|
+
.sim-shell:has(.grid-walk) .sim-canvas-panel {
|
|
3791
|
+
grid-column: 1;
|
|
3792
|
+
grid-row: 1;
|
|
3793
|
+
min-height: var(--paddock-race-view-min-height, 620px);
|
|
3794
|
+
}
|
|
3795
|
+
.sim-shell:has(.grid-walk) .sim-grid > .sim-timing {
|
|
3796
|
+
position: relative;
|
|
3797
|
+
inset: auto;
|
|
3798
|
+
grid-column: 1;
|
|
3799
|
+
grid-row: 2;
|
|
3800
|
+
width: min(100%, var(--timing-board-max-width));
|
|
3801
|
+
max-width: var(--timing-board-max-width);
|
|
3802
|
+
height: auto;
|
|
3803
|
+
clip-path: none;
|
|
3804
|
+
}
|
|
3805
|
+
.sim-shell:has(.grid-walk) .telemetry-stack { grid-column: 1; grid-row: 3; max-height: none; }
|
|
3806
|
+
.sim-shell:has(.grid-walk) .sim-canvas-panel > .camera-controls { left: 1rem; max-width: calc(100% - 2rem); }
|
|
3807
|
+
.sim-shell:has(.grid-walk) .start-lights { left: 50%; }
|
|
3808
|
+
.sim-shell:has(.grid-walk) .race-data-panel {
|
|
3809
|
+
--race-data-safe-left: 0px;
|
|
3810
|
+
--race-data-banner-left: 50%;
|
|
3811
|
+
--race-data-banner-width: calc(100% - 1.5rem);
|
|
3812
|
+
left: 50%;
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
@@ -13,7 +13,7 @@ export function createStewardMessageMarkup() {
|
|
|
13
13
|
`;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export function createRaceDataPanelMarkup({ ui = {}, standalone = false } = {}) {
|
|
16
|
+
export function createRaceDataPanelMarkup({ ui = {}, standalone = false, navigationLink = false } = {}) {
|
|
17
17
|
const sizeMode = ui.raceDataBannerSize === 'auto' ? 'auto' : 'custom';
|
|
18
18
|
const telemetryDetail = Boolean(ui.raceDataTelemetryDetail);
|
|
19
19
|
const classNames = ['race-data-panel', `race-data-panel--${sizeMode}`];
|
|
@@ -28,7 +28,9 @@ export function createRaceDataPanelMarkup({ ui = {}, standalone = false } = {})
|
|
|
28
28
|
</div>
|
|
29
29
|
${telemetryDetail ? createRaceDataTelemetryMarkup() : ''}
|
|
30
30
|
<strong class="race-data-number" data-race-data-number>--</strong>
|
|
31
|
-
|
|
31
|
+
${navigationLink
|
|
32
|
+
? '<a class="race-data-link" data-race-data-open>Open project</a>'
|
|
33
|
+
: '<button class="race-data-link" type="button" data-race-data-open>Open project</button>'}
|
|
32
34
|
`;
|
|
33
35
|
|
|
34
36
|
return createComponentSurfaceMarkup({
|
|
@@ -13,8 +13,10 @@ export function createCameraControlsMarkup({ embedded = false, showSimulationSpe
|
|
|
13
13
|
${driverCameraControl}
|
|
14
14
|
<button type="button" data-camera-mode="show-all" aria-pressed="false">Show all</button>
|
|
15
15
|
<button type="button" data-camera-mode="pit" aria-pressed="false">Pits</button>
|
|
16
|
-
<
|
|
17
|
-
|
|
16
|
+
<div class="camera-controls__zoom" role="group" aria-label="Zoom">
|
|
17
|
+
<button type="button" data-zoom-out aria-label="Zoom out">-</button>
|
|
18
|
+
<button type="button" data-zoom-in aria-label="Zoom in">+</button>
|
|
19
|
+
</div>
|
|
18
20
|
${showSpeedControl ? '<button type="button" data-simulation-speed aria-label="Simulation speed">1x</button>' : ''}
|
|
19
21
|
<button type="button" data-race-data-banners-muted aria-pressed="false">Mute banners</button>
|
|
20
22
|
`;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createRaceDataPanelMarkup } from './bannerTemplates.js';
|
|
2
|
+
import { escapeHtml as html } from './htmlEscaping.js';
|
|
3
|
+
|
|
4
|
+
export function createGridWalkMarkup(drivers, cars, ui = {}) {
|
|
5
|
+
const byId = new Map(drivers.map((driver) => [driver.id, driver]));
|
|
6
|
+
return `<div class="grid-walk" data-grid-walk>
|
|
7
|
+
<div class="grid-walk__toolbar" data-grid-header>
|
|
8
|
+
<div class="camera-controls grid-walk__navigation" data-grid-navigation>
|
|
9
|
+
<button type="button" data-grid-previous aria-label="Previous grid entry">←</button>
|
|
10
|
+
<label><span class="grid-walk__sr-only">Explore the grid</span><select class="sim-control" data-grid-jump aria-label="Jump to grid entry">
|
|
11
|
+
${cars.map((car, index) => `<option value="${html(car.id)}">${index + 1}. ${html(byId.get(car.id).presentation.name)}</option>`).join('')}
|
|
12
|
+
</select></label>
|
|
13
|
+
<button type="button" data-grid-next aria-label="Next grid entry">→</button>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="grid-walk__labels" data-grid-labels role="group" aria-label="Starting grid">
|
|
17
|
+
<svg class="grid-walk__connectors" aria-hidden="true">${cars.map((car) => `<line data-grid-connector="${html(car.id)}" />`).join('')}</svg>
|
|
18
|
+
${cars.map((car, index) => {
|
|
19
|
+
const entry = byId.get(car.id).presentation;
|
|
20
|
+
return `<button type="button" class="grid-walk__entry" data-grid-entry="${html(car.id)}" title="${html(entry.name)}" style="--driver-color: ${html(byId.get(car.id).color)}" aria-pressed="false">
|
|
21
|
+
<span class="grid-walk__position">${String(index + 1).padStart(2, '0')}</span>
|
|
22
|
+
<strong>${html(entry.name)}</strong>
|
|
23
|
+
</button>`;
|
|
24
|
+
}).join('')}
|
|
25
|
+
</div>
|
|
26
|
+
${createRaceDataPanelMarkup({ ui: { ...ui, raceDataTelemetryDetail: false }, navigationLink: true })}
|
|
27
|
+
<span class="grid-walk__status" data-grid-status role="status"></span>
|
|
28
|
+
</div>`;
|
|
29
|
+
}
|
package/src/ui/shellTemplate.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
createTelemetryPanelMarkup,
|
|
6
6
|
createTimingTowerMarkup,
|
|
7
7
|
} from './componentTemplates.js';
|
|
8
|
+
import { createViewControlsMarkup } from './viewControlsTemplate.js';
|
|
8
9
|
import { createUnsupportedSizeMarkup } from './templateUtils.js';
|
|
9
10
|
|
|
10
11
|
export function createF1SimulatorShell({
|
|
@@ -17,6 +18,7 @@ export function createF1SimulatorShell({
|
|
|
17
18
|
physicsMode,
|
|
18
19
|
assets,
|
|
19
20
|
ui = {},
|
|
21
|
+
gridWalk = {},
|
|
20
22
|
}) {
|
|
21
23
|
const layoutPreset = ui.layoutPreset === 'left-tower-overlay' ? 'left-tower-overlay' : 'standard';
|
|
22
24
|
const timingFit = ui.timingTowerVerticalFit === 'scroll' ? 'scroll' : 'expand-race-view';
|
|
@@ -34,6 +36,7 @@ export function createF1SimulatorShell({
|
|
|
34
36
|
<section class="sim-workspace" aria-label="F1 race simulator">
|
|
35
37
|
${createRaceControlsMarkup({ title, kicker, backLinkHref, backLinkLabel, showBackLink })}
|
|
36
38
|
${cameraControls}
|
|
39
|
+
${createViewControlsMarkup({ gridWalk, ui })}
|
|
37
40
|
<div class="sim-grid">
|
|
38
41
|
${createTimingTowerMarkup({ totalLaps, assets, ui })}
|
|
39
42
|
${createRaceCanvasMarkup({ includeRaceDataPanel: ui.showRaceDataPanel !== false, assets, totalLaps, physicsMode, ui })}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createViewControlsMarkup } from './viewControlsTemplate.js';
|
|
1
2
|
import { createCameraControlsMarkup } from './cameraControlsTemplate.js';
|
|
2
3
|
import { createRaceCanvasMarkup } from './raceCanvasTemplate.js';
|
|
3
4
|
import { createSafetyCarControlMarkup } from './raceControlsTemplate.js';
|
|
@@ -33,11 +34,12 @@ export function createRaceTelemetryDrawerMarkup(options, {
|
|
|
33
34
|
const showCameraControls = options.ui?.cameraControls !== false;
|
|
34
35
|
const body = `
|
|
35
36
|
<div class="race-telemetry-drawer__toolbar" aria-label="Race workbench controls">
|
|
37
|
+
${createViewControlsMarkup(options)}
|
|
36
38
|
${showCameraControls ? createCameraControlsMarkup({ showSimulationSpeed: true, ui: options.ui }) : ''}
|
|
37
39
|
<div class="race-telemetry-drawer__controls">
|
|
38
40
|
${createSafetyCarControlMarkup({ compact: true })}
|
|
39
|
-
<button class="telemetry-drawer-toggle" type="button" data-telemetry-drawer-toggle aria-expanded="${drawerInitiallyOpen ? 'true' : 'false'}" aria-controls="${drawerId}" aria-label="${drawerInitiallyOpen ? 'Close telemetry' : 'Open telemetry'}">
|
|
40
|
-
|
|
41
|
+
<button class="telemetry-drawer-toggle" type="button" data-telemetry-drawer-toggle data-size-label="Telemetry" aria-expanded="${drawerInitiallyOpen ? 'true' : 'false'}" aria-controls="${drawerId}" aria-label="${drawerInitiallyOpen ? 'Close telemetry' : 'Open telemetry'}">
|
|
42
|
+
<span data-control-label>${drawerInitiallyOpen ? 'Close' : 'Telemetry'}</span>
|
|
41
43
|
</button>
|
|
42
44
|
</div>
|
|
43
45
|
</div>
|
|
@@ -1,29 +1,10 @@
|
|
|
1
|
+
import { normalizeTelemetryModules } from '../config/telemetryModules.js';
|
|
1
2
|
import { createCarDriverOverviewMarkup } from './carOverviewTemplate.js';
|
|
2
3
|
import {
|
|
3
4
|
createComponentSurfaceMarkup,
|
|
4
5
|
createTelemetrySectorBarsMarkup,
|
|
5
6
|
} from './templateUtils.js';
|
|
6
7
|
|
|
7
|
-
function getTelemetryModules(ui = {}) {
|
|
8
|
-
const defaults = {
|
|
9
|
-
core: true,
|
|
10
|
-
sectors: true,
|
|
11
|
-
lapTimes: true,
|
|
12
|
-
sectorTimes: true,
|
|
13
|
-
};
|
|
14
|
-
const modules = ui.telemetryModules;
|
|
15
|
-
if (modules === false) return Object.fromEntries(Object.keys(defaults).map((name) => [name, false]));
|
|
16
|
-
if (Array.isArray(modules)) {
|
|
17
|
-
const requested = new Set(modules);
|
|
18
|
-
return Object.fromEntries(Object.keys(defaults).map((name) => [name, requested.has(name)]));
|
|
19
|
-
}
|
|
20
|
-
if (!modules || typeof modules !== 'object') return defaults;
|
|
21
|
-
return Object.fromEntries(Object.entries(defaults).map(([name, fallback]) => [
|
|
22
|
-
name,
|
|
23
|
-
modules[name] == null ? fallback : Boolean(modules[name]),
|
|
24
|
-
]));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
8
|
function getTelemetryModuleClass(componentName) {
|
|
28
9
|
return `sim-telemetry telemetry-component telemetry-component--${componentName}`;
|
|
29
10
|
}
|
|
@@ -135,7 +116,7 @@ export function createTelemetrySectorTimesMarkup() {
|
|
|
135
116
|
});
|
|
136
117
|
}
|
|
137
118
|
|
|
138
|
-
function createTelemetryComponentMarkup(options, modules =
|
|
119
|
+
function createTelemetryComponentMarkup(options, modules = normalizeTelemetryModules(options.ui?.telemetryModules)) {
|
|
139
120
|
return `
|
|
140
121
|
${modules.core ? createTelemetryCoreMarkup(options) : ''}
|
|
141
122
|
${modules.sectors ? createTelemetrySectorsMarkup(options) : ''}
|
|
@@ -145,7 +126,7 @@ function createTelemetryComponentMarkup(options, modules = getTelemetryModules(o
|
|
|
145
126
|
}
|
|
146
127
|
|
|
147
128
|
export function createTelemetryPanelMarkup(options, { includeOverview = options.ui?.telemetryIncludesOverview !== false } = {}) {
|
|
148
|
-
const modules =
|
|
129
|
+
const modules = normalizeTelemetryModules(options.ui?.telemetryModules);
|
|
149
130
|
const body = `
|
|
150
131
|
${createTelemetryComponentMarkup(options, modules)}
|
|
151
132
|
${includeOverview ? createCarDriverOverviewMarkup(options) : ''}
|
package/src/ui/templateUtils.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
.replaceAll('&', '&')
|
|
4
|
-
.replaceAll('<', '<')
|
|
5
|
-
.replaceAll('>', '>')
|
|
6
|
-
.replaceAll('"', '"')
|
|
7
|
-
.replaceAll("'", ''');
|
|
8
|
-
}
|
|
1
|
+
import { escapeHtml } from './htmlEscaping.js';
|
|
2
|
+
export { escapeHtml } from './htmlEscaping.js';
|
|
9
3
|
|
|
10
4
|
export function createLoadingMarkup(label = 'Loading') {
|
|
11
5
|
return `
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createComponentSurfaceMarkup } from './templateUtils.js';
|
|
2
|
+
|
|
3
|
+
export function createViewControlsMarkup({ gridWalk = {}, ui = {} } = {}) {
|
|
4
|
+
const enabled = gridWalk.enabled === true;
|
|
5
|
+
const view = enabled && gridWalk.initialView === 'grid-walk' ? 'grid-walk' : 'race';
|
|
6
|
+
const toggle = ui.viewControls === 'toggle';
|
|
7
|
+
return createComponentSurfaceMarkup({
|
|
8
|
+
tagName: 'div',
|
|
9
|
+
className: 'camera-controls camera-controls--external paddock-view-controls',
|
|
10
|
+
componentName: 'view-controls',
|
|
11
|
+
ariaLabel: 'Simulator view',
|
|
12
|
+
attributes: `role="group"${enabled ? '' : ' hidden'}`,
|
|
13
|
+
body: `
|
|
14
|
+
<button type="button" data-grid-view="grid-walk" aria-pressed="${view === 'grid-walk'}" disabled${toggle ? ' hidden' : ''}>Grid Walk</button>
|
|
15
|
+
<button type="button" data-grid-view="race" aria-pressed="${view === 'race'}" disabled${toggle ? ' hidden' : ''}>Race</button>
|
|
16
|
+
<button type="button" data-grid-action="${view === 'grid-walk' ? 'start' : 'stop'}" data-grid-toggle data-size-label="Start race" disabled${toggle ? '' : ' hidden'}><span data-control-label>${view === 'grid-walk' ? 'Start race' : 'Stop race'}</span></button>
|
|
17
|
+
`,
|
|
18
|
+
unsupportedLabel: 'Simulator view',
|
|
19
|
+
loadingLabel: 'Simulator view',
|
|
20
|
+
});
|
|
21
|
+
}
|