@inventure71/paddockjs 0.1.0 → 0.2.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 +10 -0
- package/README.md +20 -4
- package/docs/architecture.md +15 -6
- package/docs/concepts.md +5 -1
- package/docs/data_contract.md +85 -3
- package/docs/rules.md +2 -0
- package/docs/system_specs.md +29 -5
- package/package.json +1 -1
- package/src/api/PaddockSimulatorController.js +57 -1
- package/src/app/F1SimulatorApp.js +160 -22
- package/src/app/domBindings.js +36 -11
- package/src/config/defaultOptions.js +25 -0
- package/src/index.d.ts +57 -0
- package/src/index.js +6 -0
- package/src/simulation/raceSimulation.js +205 -0
- package/src/simulation/trackModel.js +21 -0
- package/src/styles.css +451 -2
- package/src/ui/componentTemplates.js +176 -3
package/src/styles.css
CHANGED
|
@@ -294,6 +294,20 @@
|
|
|
294
294
|
overflow: hidden;
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
.telemetry-stack {
|
|
298
|
+
display: grid;
|
|
299
|
+
gap: 0.72rem;
|
|
300
|
+
min-height: 0;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.telemetry-stack > .sim-telemetry {
|
|
304
|
+
min-height: auto;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.telemetry-component {
|
|
308
|
+
width: 100%;
|
|
309
|
+
}
|
|
310
|
+
|
|
297
311
|
.sim-timing.broadcast-tower {
|
|
298
312
|
position: relative;
|
|
299
313
|
width: min(100%, var(--timing-board-max-width));
|
|
@@ -722,6 +736,134 @@
|
|
|
722
736
|
display: block;
|
|
723
737
|
}
|
|
724
738
|
|
|
739
|
+
.race-telemetry-drawer {
|
|
740
|
+
--telemetry-drawer-width: min(390px, max(300px, 30cqw));
|
|
741
|
+
position: relative;
|
|
742
|
+
display: block;
|
|
743
|
+
container-type: inline-size;
|
|
744
|
+
min-height: var(--paddock-race-view-min-height, 620px);
|
|
745
|
+
overflow: hidden;
|
|
746
|
+
background: #000;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
.race-telemetry-drawer__race {
|
|
750
|
+
min-width: 0;
|
|
751
|
+
display: flex;
|
|
752
|
+
min-height: inherit;
|
|
753
|
+
margin-right: 0;
|
|
754
|
+
transition: margin-right 320ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
.race-telemetry-drawer.is-telemetry-open .race-telemetry-drawer__race {
|
|
758
|
+
margin-right: var(--telemetry-drawer-width);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
.race-telemetry-drawer__race > .sim-canvas-panel {
|
|
762
|
+
flex: 1 1 auto;
|
|
763
|
+
min-height: inherit;
|
|
764
|
+
height: auto;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
.race-telemetry-drawer__controls {
|
|
768
|
+
position: absolute;
|
|
769
|
+
z-index: 12;
|
|
770
|
+
top: 1rem;
|
|
771
|
+
right: 1rem;
|
|
772
|
+
display: flex;
|
|
773
|
+
flex-wrap: wrap;
|
|
774
|
+
justify-content: flex-end;
|
|
775
|
+
gap: 0.45rem;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
.race-telemetry-drawer__controls .sim-control,
|
|
779
|
+
.race-telemetry-drawer__controls .telemetry-drawer-toggle {
|
|
780
|
+
min-height: 2.45rem;
|
|
781
|
+
padding: 0.62rem 0.72rem;
|
|
782
|
+
box-shadow: 0 14px 34px rgba(0, 0, 0, 0.3);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
.telemetry-drawer-toggle {
|
|
786
|
+
border: 1px solid rgba(255, 255, 255, 0.26);
|
|
787
|
+
color: #08090b;
|
|
788
|
+
background: var(--green);
|
|
789
|
+
font-family: var(--font-mono);
|
|
790
|
+
font-size: 0.66rem;
|
|
791
|
+
font-weight: 900;
|
|
792
|
+
letter-spacing: 0.12em;
|
|
793
|
+
text-transform: uppercase;
|
|
794
|
+
cursor: pointer;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
.telemetry-drawer {
|
|
798
|
+
position: absolute;
|
|
799
|
+
z-index: 11;
|
|
800
|
+
top: 0;
|
|
801
|
+
right: 0;
|
|
802
|
+
bottom: 0;
|
|
803
|
+
width: var(--telemetry-drawer-width);
|
|
804
|
+
min-width: 0;
|
|
805
|
+
height: 100%;
|
|
806
|
+
overflow: hidden;
|
|
807
|
+
color: var(--text);
|
|
808
|
+
background:
|
|
809
|
+
linear-gradient(180deg, rgba(255, 255, 255, 0.075), transparent 22%),
|
|
810
|
+
rgba(8, 9, 11, 0.97);
|
|
811
|
+
border-left: 1px solid var(--line);
|
|
812
|
+
transform: translate3d(100%, 0, 0);
|
|
813
|
+
opacity: 0;
|
|
814
|
+
pointer-events: none;
|
|
815
|
+
will-change: transform, opacity;
|
|
816
|
+
transition:
|
|
817
|
+
transform 320ms cubic-bezier(0.2, 0.8, 0.2, 1),
|
|
818
|
+
opacity 180ms ease;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.race-telemetry-drawer.is-telemetry-open .telemetry-drawer {
|
|
822
|
+
transform: translate3d(0, 0, 0);
|
|
823
|
+
opacity: 1;
|
|
824
|
+
pointer-events: auto;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
.telemetry-drawer__header {
|
|
828
|
+
display: flex;
|
|
829
|
+
align-items: center;
|
|
830
|
+
justify-content: space-between;
|
|
831
|
+
gap: 0.75rem;
|
|
832
|
+
padding: 0.8rem 0.85rem;
|
|
833
|
+
border-bottom: 1px solid var(--line);
|
|
834
|
+
text-transform: uppercase;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
.telemetry-drawer__header span {
|
|
838
|
+
color: var(--green);
|
|
839
|
+
font-family: var(--font-mono);
|
|
840
|
+
font-size: 0.72rem;
|
|
841
|
+
font-weight: 900;
|
|
842
|
+
letter-spacing: 0.16em;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
.telemetry-drawer__header button {
|
|
846
|
+
border: 1px solid rgba(255, 255, 255, 0.18);
|
|
847
|
+
color: var(--text);
|
|
848
|
+
background: rgba(255, 255, 255, 0.06);
|
|
849
|
+
font-family: var(--font-mono);
|
|
850
|
+
font-size: 0.62rem;
|
|
851
|
+
font-weight: 900;
|
|
852
|
+
letter-spacing: 0.1em;
|
|
853
|
+
text-transform: uppercase;
|
|
854
|
+
padding: 0.45rem 0.54rem;
|
|
855
|
+
cursor: pointer;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
.telemetry-drawer__content {
|
|
859
|
+
display: grid;
|
|
860
|
+
gap: 0.72rem;
|
|
861
|
+
height: calc(100% - 3.4rem);
|
|
862
|
+
padding: 0.72rem;
|
|
863
|
+
overflow-y: auto;
|
|
864
|
+
scrollbar-width: thin;
|
|
865
|
+
}
|
|
866
|
+
|
|
725
867
|
.camera-controls {
|
|
726
868
|
position: absolute;
|
|
727
869
|
top: 1rem;
|
|
@@ -1263,6 +1405,239 @@
|
|
|
1263
1405
|
text-align: right;
|
|
1264
1406
|
}
|
|
1265
1407
|
|
|
1408
|
+
.telemetry-module-header {
|
|
1409
|
+
display: flex;
|
|
1410
|
+
align-items: baseline;
|
|
1411
|
+
justify-content: space-between;
|
|
1412
|
+
gap: 0.75rem;
|
|
1413
|
+
margin-bottom: 0.65rem;
|
|
1414
|
+
text-transform: uppercase;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
.telemetry-module-header span {
|
|
1418
|
+
color: var(--muted);
|
|
1419
|
+
font-family: var(--font-mono);
|
|
1420
|
+
font-size: 0.66rem;
|
|
1421
|
+
font-weight: 800;
|
|
1422
|
+
letter-spacing: 0.14em;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
.telemetry-module-header strong {
|
|
1426
|
+
color: var(--green);
|
|
1427
|
+
font-family: var(--font-mono);
|
|
1428
|
+
font-size: 0.7rem;
|
|
1429
|
+
font-weight: 900;
|
|
1430
|
+
letter-spacing: 0.08em;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
.telemetry-sector-bars {
|
|
1434
|
+
display: grid;
|
|
1435
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
1436
|
+
gap: 0.35rem;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
.telemetry-sector-bar {
|
|
1440
|
+
--sector-status-color: var(--green);
|
|
1441
|
+
--sector-status-bg: color-mix(in srgb, var(--sector-status-color) 10%, rgba(8, 10, 14, 0.94));
|
|
1442
|
+
position: relative;
|
|
1443
|
+
isolation: isolate;
|
|
1444
|
+
min-width: 0;
|
|
1445
|
+
overflow: hidden;
|
|
1446
|
+
padding: 0.55rem 0.46rem 0.48rem;
|
|
1447
|
+
color: rgba(244, 247, 251, 0.84);
|
|
1448
|
+
background:
|
|
1449
|
+
linear-gradient(90deg, color-mix(in srgb, var(--sector-status-color) 72%, transparent) var(--sector-fill, 0%), rgba(255, 255, 255, 0.055) 0),
|
|
1450
|
+
var(--sector-status-bg);
|
|
1451
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
1452
|
+
box-shadow: inset 0 -2px 0 rgba(255, 255, 255, 0.05);
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
.telemetry-sector-bar.is-active {
|
|
1456
|
+
border-color: color-mix(in srgb, var(--green) 65%, rgba(255, 255, 255, 0.22));
|
|
1457
|
+
box-shadow:
|
|
1458
|
+
inset 0 -2px 0 var(--green),
|
|
1459
|
+
0 0 24px rgba(20, 199, 132, 0.12);
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
.telemetry-sector-bar.is-complete {
|
|
1463
|
+
color: #08090b;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
.telemetry-sector-bar.is-overall-best,
|
|
1467
|
+
.telemetry-sector-table td.is-overall-best {
|
|
1468
|
+
--sector-status-color: #c77dff;
|
|
1469
|
+
--sector-status-bg: color-mix(in srgb, #c77dff 23%, rgba(8, 10, 14, 0.94));
|
|
1470
|
+
color: #fff;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
.telemetry-sector-bar.is-personal-best,
|
|
1474
|
+
.telemetry-sector-table td.is-personal-best {
|
|
1475
|
+
--sector-status-color: var(--green);
|
|
1476
|
+
--sector-status-bg: color-mix(in srgb, var(--green) 20%, rgba(8, 10, 14, 0.94));
|
|
1477
|
+
color: #ecfff7;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
.telemetry-sector-bar.is-slower,
|
|
1481
|
+
.telemetry-sector-table td.is-slower {
|
|
1482
|
+
--sector-status-color: var(--yellow);
|
|
1483
|
+
--sector-status-bg: color-mix(in srgb, var(--yellow) 16%, rgba(8, 10, 14, 0.94));
|
|
1484
|
+
color: #fff2b8;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
.telemetry-sector-table td.is-overall-best,
|
|
1488
|
+
.telemetry-sector-table td.is-personal-best,
|
|
1489
|
+
.telemetry-sector-table td.is-slower {
|
|
1490
|
+
background:
|
|
1491
|
+
linear-gradient(90deg, color-mix(in srgb, var(--sector-status-color) 28%, transparent), transparent 72%),
|
|
1492
|
+
var(--sector-status-bg);
|
|
1493
|
+
box-shadow: inset 3px 0 0 var(--sector-status-color);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
.telemetry-sector-bar span,
|
|
1497
|
+
.telemetry-sector-bar strong {
|
|
1498
|
+
position: relative;
|
|
1499
|
+
z-index: 1;
|
|
1500
|
+
display: block;
|
|
1501
|
+
font-family: var(--font-mono);
|
|
1502
|
+
line-height: 1;
|
|
1503
|
+
text-shadow: 0 1px 8px rgba(0, 0, 0, 0.32);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
.telemetry-sector-bar span {
|
|
1507
|
+
margin-bottom: 0.4rem;
|
|
1508
|
+
font-size: 0.58rem;
|
|
1509
|
+
font-weight: 900;
|
|
1510
|
+
letter-spacing: 0.16em;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
.telemetry-sector-bar strong {
|
|
1514
|
+
overflow: hidden;
|
|
1515
|
+
font-size: 0.72rem;
|
|
1516
|
+
font-weight: 900;
|
|
1517
|
+
text-overflow: ellipsis;
|
|
1518
|
+
white-space: nowrap;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
.telemetry-sector-banner {
|
|
1522
|
+
--sector-status-color: var(--green);
|
|
1523
|
+
--sector-banner-color: var(--driver-color, var(--green));
|
|
1524
|
+
position: absolute;
|
|
1525
|
+
z-index: 9;
|
|
1526
|
+
right: 1rem;
|
|
1527
|
+
bottom: 1rem;
|
|
1528
|
+
display: grid;
|
|
1529
|
+
grid-template-columns: minmax(9rem, 0.8fr) minmax(14rem, 1.2fr);
|
|
1530
|
+
gap: 0.65rem;
|
|
1531
|
+
align-items: stretch;
|
|
1532
|
+
width: min(560px, calc(100% - 2rem));
|
|
1533
|
+
padding: 0.65rem;
|
|
1534
|
+
color: var(--text);
|
|
1535
|
+
background:
|
|
1536
|
+
linear-gradient(110deg, rgba(8, 10, 16, 0.98), rgba(24, 29, 40, 0.92)),
|
|
1537
|
+
repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.05) 0 1px, transparent 1px 58px);
|
|
1538
|
+
border-left: 4px solid var(--sector-banner-color);
|
|
1539
|
+
border-bottom: 2px solid var(--sector-banner-color);
|
|
1540
|
+
clip-path: polygon(0 0, calc(100% - 14px) 0, 100% 14px, 100% 100%, 14px 100%, 0 calc(100% - 14px));
|
|
1541
|
+
box-shadow: 0 16px 42px rgba(0, 0, 0, 0.42), inset 0 1px rgba(255, 255, 255, 0.12);
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
.sim-canvas-panel:has(.race-data-panel:not(.is-hidden)) .telemetry-sector-banner {
|
|
1545
|
+
bottom: 8.3rem;
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
.telemetry-sector-banner__copy {
|
|
1549
|
+
min-width: 0;
|
|
1550
|
+
padding: 0.28rem 0.35rem;
|
|
1551
|
+
text-transform: uppercase;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
.telemetry-sector-banner__copy span {
|
|
1555
|
+
display: block;
|
|
1556
|
+
color: var(--sector-banner-color);
|
|
1557
|
+
font-family: var(--font-mono);
|
|
1558
|
+
font-size: 0.62rem;
|
|
1559
|
+
font-weight: 900;
|
|
1560
|
+
letter-spacing: 0.14em;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
.telemetry-sector-banner__copy b {
|
|
1564
|
+
color: inherit;
|
|
1565
|
+
font: inherit;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
.telemetry-sector-banner__copy strong {
|
|
1569
|
+
display: block;
|
|
1570
|
+
margin-top: 0.2rem;
|
|
1571
|
+
overflow: hidden;
|
|
1572
|
+
font-size: clamp(1.15rem, 2vw, 1.65rem);
|
|
1573
|
+
font-weight: 900;
|
|
1574
|
+
line-height: 0.92;
|
|
1575
|
+
text-overflow: ellipsis;
|
|
1576
|
+
white-space: nowrap;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
.telemetry-sector-banner__copy em {
|
|
1580
|
+
display: block;
|
|
1581
|
+
margin-top: 0.28rem;
|
|
1582
|
+
color: rgba(244, 247, 251, 0.68);
|
|
1583
|
+
font-family: var(--font-mono);
|
|
1584
|
+
font-size: 0.66rem;
|
|
1585
|
+
font-style: normal;
|
|
1586
|
+
font-weight: 900;
|
|
1587
|
+
letter-spacing: 0.12em;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
.telemetry-sector-banner__bars {
|
|
1591
|
+
align-self: stretch;
|
|
1592
|
+
display: grid;
|
|
1593
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
1594
|
+
gap: 0.32rem;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
.telemetry-lap-table,
|
|
1598
|
+
.telemetry-sector-table {
|
|
1599
|
+
width: 100%;
|
|
1600
|
+
border-collapse: collapse;
|
|
1601
|
+
overflow: hidden;
|
|
1602
|
+
background: rgba(255, 255, 255, 0.045);
|
|
1603
|
+
font-family: var(--font-mono);
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
.telemetry-lap-table tr,
|
|
1607
|
+
.telemetry-sector-table tr {
|
|
1608
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.075);
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
.telemetry-lap-table tr:last-child,
|
|
1612
|
+
.telemetry-sector-table tr:last-child {
|
|
1613
|
+
border-bottom: 0;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
.telemetry-lap-table th,
|
|
1617
|
+
.telemetry-lap-table td,
|
|
1618
|
+
.telemetry-sector-table th,
|
|
1619
|
+
.telemetry-sector-table td {
|
|
1620
|
+
padding: 0.55rem 0.5rem;
|
|
1621
|
+
font-size: 0.68rem;
|
|
1622
|
+
line-height: 1;
|
|
1623
|
+
text-align: right;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
.telemetry-lap-table th,
|
|
1627
|
+
.telemetry-sector-table th {
|
|
1628
|
+
color: var(--muted);
|
|
1629
|
+
font-weight: 800;
|
|
1630
|
+
letter-spacing: 0.09em;
|
|
1631
|
+
text-align: left;
|
|
1632
|
+
text-transform: uppercase;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
.telemetry-lap-table td,
|
|
1636
|
+
.telemetry-sector-table td {
|
|
1637
|
+
color: var(--text);
|
|
1638
|
+
font-weight: 900;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1266
1641
|
.car-overview {
|
|
1267
1642
|
position: relative;
|
|
1268
1643
|
margin-top: 1rem;
|
|
@@ -1676,7 +2051,12 @@
|
|
|
1676
2051
|
|
|
1677
2052
|
.sim-shell--left-tower-overlay .sim-timing,
|
|
1678
2053
|
.sim-canvas-panel--with-timing-tower > .sim-timing {
|
|
1679
|
-
width: min(
|
|
2054
|
+
width: min(100%, var(--timing-board-max-width));
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
.sim-shell--left-tower-overlay .sim-timing {
|
|
2058
|
+
width: 100%;
|
|
2059
|
+
max-width: 100%;
|
|
1680
2060
|
}
|
|
1681
2061
|
|
|
1682
2062
|
.sim-topbar {
|
|
@@ -1703,8 +2083,33 @@
|
|
|
1703
2083
|
min-height: 58svh;
|
|
1704
2084
|
}
|
|
1705
2085
|
|
|
2086
|
+
.race-telemetry-drawer {
|
|
2087
|
+
--telemetry-drawer-width: 100%;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
.race-telemetry-drawer__race,
|
|
2091
|
+
.race-telemetry-drawer.is-telemetry-open .race-telemetry-drawer__race {
|
|
2092
|
+
margin-right: 0;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
.race-telemetry-drawer.is-telemetry-open .race-telemetry-drawer__race {
|
|
2096
|
+
margin-bottom: min(360px, 46svh);
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
.telemetry-drawer {
|
|
2100
|
+
top: auto;
|
|
2101
|
+
height: min(360px, 46svh);
|
|
2102
|
+
border-left: 0;
|
|
2103
|
+
border-top: 1px solid var(--line);
|
|
2104
|
+
transform: translate3d(0, 100%, 0);
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
.race-telemetry-drawer.is-telemetry-open .telemetry-drawer {
|
|
2108
|
+
transform: translate3d(0, 0, 0);
|
|
2109
|
+
}
|
|
2110
|
+
|
|
1706
2111
|
.camera-controls {
|
|
1707
|
-
max-width: calc(100% -
|
|
2112
|
+
max-width: calc(100% - 1.5rem);
|
|
1708
2113
|
}
|
|
1709
2114
|
|
|
1710
2115
|
.race-data-panel {
|
|
@@ -1715,6 +2120,17 @@
|
|
|
1715
2120
|
min-height: 88px;
|
|
1716
2121
|
}
|
|
1717
2122
|
|
|
2123
|
+
.telemetry-sector-banner {
|
|
2124
|
+
right: 0.75rem;
|
|
2125
|
+
bottom: 0.75rem;
|
|
2126
|
+
grid-template-columns: 1fr;
|
|
2127
|
+
width: calc(100% - 1.5rem);
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
.sim-canvas-panel:has(.race-data-panel:not(.is-hidden)) .telemetry-sector-banner {
|
|
2131
|
+
bottom: 8rem;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
1718
2134
|
.race-data-panel.is-radio-mode {
|
|
1719
2135
|
grid-template-columns: minmax(0, 1fr);
|
|
1720
2136
|
}
|
|
@@ -1816,3 +2232,36 @@
|
|
|
1816
2232
|
display: none;
|
|
1817
2233
|
}
|
|
1818
2234
|
}
|
|
2235
|
+
|
|
2236
|
+
@media (max-width: 520px) {
|
|
2237
|
+
.sim-canvas-panel--with-timing-tower {
|
|
2238
|
+
min-height: max(58svh, calc(min(46svh, 420px) + 17rem));
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
.sim-canvas-panel--with-timing-tower > .sim-timing {
|
|
2242
|
+
position: static;
|
|
2243
|
+
inset: auto;
|
|
2244
|
+
width: 100%;
|
|
2245
|
+
max-width: 100%;
|
|
2246
|
+
height: min(46svh, 420px);
|
|
2247
|
+
clip-path: none;
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
.sim-canvas-panel--with-timing-tower > .sim-timing.broadcast-tower {
|
|
2251
|
+
position: static;
|
|
2252
|
+
width: 100%;
|
|
2253
|
+
max-width: 100%;
|
|
2254
|
+
height: min(46svh, 420px);
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
.sim-canvas-panel--with-timing-tower > .camera-controls {
|
|
2258
|
+
left: 0.75rem;
|
|
2259
|
+
top: calc(min(46svh, 420px) + 1.25rem);
|
|
2260
|
+
max-width: calc(100% - 1.5rem);
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
.sim-canvas-panel--with-timing-tower > .start-lights {
|
|
2264
|
+
top: calc(min(46svh, 420px) + 4.2rem);
|
|
2265
|
+
left: 50%;
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
@@ -2,6 +2,13 @@ function buttonHiddenAttribute(isVisible) {
|
|
|
2
2
|
return isVisible ? '' : ' hidden';
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
let telemetryDrawerIdSequence = 0;
|
|
6
|
+
|
|
7
|
+
function createTelemetryDrawerId() {
|
|
8
|
+
telemetryDrawerIdSequence += 1;
|
|
9
|
+
return `paddock-telemetry-drawer-${telemetryDrawerIdSequence}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
function createLoadingMarkup(label = 'Loading') {
|
|
6
13
|
return `
|
|
7
14
|
<div class="paddock-loading" data-paddock-loading aria-label="${escapeHtml(label)} loading">
|
|
@@ -110,6 +117,7 @@ export function createCameraControlsMarkup({ embedded = false } = {}) {
|
|
|
110
117
|
export function createRaceCanvasMarkup({
|
|
111
118
|
includeRaceDataPanel = false,
|
|
112
119
|
includeTimingTower = false,
|
|
120
|
+
includeTelemetrySectorBanner = false,
|
|
113
121
|
timingTowerVerticalFit,
|
|
114
122
|
assets,
|
|
115
123
|
totalLaps,
|
|
@@ -147,6 +155,7 @@ export function createRaceCanvasMarkup({
|
|
|
147
155
|
</div>
|
|
148
156
|
${showEmbeddedCameraControls ? createCameraControlsMarkup({ embedded: true }) : ''}
|
|
149
157
|
${includeRaceDataPanel ? createRaceDataPanelMarkup({ assets, ui }) : ''}
|
|
158
|
+
${includeTelemetrySectorBanner ? createTelemetrySectorBannerMarkup({ ui }) : ''}
|
|
150
159
|
<div class="race-finish-panel" data-race-finish-panel hidden aria-live="polite">
|
|
151
160
|
<span>Race winner</span>
|
|
152
161
|
<strong data-race-finish-winner>--</strong>
|
|
@@ -205,9 +214,33 @@ export function createCarDriverOverviewMarkup({ assets }) {
|
|
|
205
214
|
`;
|
|
206
215
|
}
|
|
207
216
|
|
|
208
|
-
|
|
217
|
+
function getTelemetryModules(ui = {}) {
|
|
218
|
+
const defaults = {
|
|
219
|
+
core: true,
|
|
220
|
+
sectors: true,
|
|
221
|
+
lapTimes: true,
|
|
222
|
+
sectorTimes: true,
|
|
223
|
+
};
|
|
224
|
+
const modules = ui.telemetryModules;
|
|
225
|
+
if (modules === false) return Object.fromEntries(Object.keys(defaults).map((name) => [name, false]));
|
|
226
|
+
if (Array.isArray(modules)) {
|
|
227
|
+
const requested = new Set(modules);
|
|
228
|
+
return Object.fromEntries(Object.keys(defaults).map((name) => [name, requested.has(name)]));
|
|
229
|
+
}
|
|
230
|
+
if (!modules || typeof modules !== 'object') return defaults;
|
|
231
|
+
return Object.fromEntries(Object.entries(defaults).map(([name, fallback]) => [
|
|
232
|
+
name,
|
|
233
|
+
modules[name] == null ? fallback : Boolean(modules[name]),
|
|
234
|
+
]));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function getTelemetryModuleClass(componentName) {
|
|
238
|
+
return `sim-telemetry telemetry-component telemetry-component--${componentName}`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function createTelemetryCoreMarkup() {
|
|
209
242
|
return `
|
|
210
|
-
<
|
|
243
|
+
<section class="${getTelemetryModuleClass('core')}" data-paddock-component="telemetry-core" aria-label="Selected car core telemetry">
|
|
211
244
|
<div class="telemetry-header">
|
|
212
245
|
<span data-selected-code>---</span>
|
|
213
246
|
<strong data-selected-name>Select car</strong>
|
|
@@ -222,8 +255,148 @@ export function createTelemetryPanelMarkup(options, { includeOverview = options.
|
|
|
222
255
|
<div><dt>Interval</dt><dd data-telemetry-gap>--</dd></div>
|
|
223
256
|
<div><dt>Leader</dt><dd data-telemetry-leader-gap>--</dd></div>
|
|
224
257
|
</dl>
|
|
258
|
+
${createLoadingMarkup('Core telemetry')}
|
|
259
|
+
</section>
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function createTelemetrySectorsMarkup() {
|
|
264
|
+
return `
|
|
265
|
+
<section class="${getTelemetryModuleClass('sectors')} telemetry-sector-strip" data-paddock-component="telemetry-sectors" data-telemetry-sector-strip aria-label="Sector progress">
|
|
266
|
+
<div class="telemetry-module-header">
|
|
267
|
+
<span>Sector map</span>
|
|
268
|
+
<strong data-telemetry-current-sector>S1</strong>
|
|
269
|
+
</div>
|
|
270
|
+
<div class="telemetry-sector-bars">
|
|
271
|
+
${[1, 2, 3].map((sector) => `
|
|
272
|
+
<div class="telemetry-sector-bar" data-telemetry-sector-bar="${sector}" style="--sector-fill: 0%">
|
|
273
|
+
<span>S${sector}</span>
|
|
274
|
+
<strong data-telemetry-sector-time="${sector}">--</strong>
|
|
275
|
+
</div>
|
|
276
|
+
`).join('')}
|
|
277
|
+
</div>
|
|
278
|
+
${createLoadingMarkup('Sector telemetry')}
|
|
279
|
+
</section>
|
|
280
|
+
`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function createTelemetrySectorBannerMarkup() {
|
|
284
|
+
return `
|
|
285
|
+
<section class="telemetry-sector-banner" data-paddock-component="telemetry-sector-banner" data-telemetry-sector-banner aria-label="Broadcast sector telemetry">
|
|
286
|
+
<div class="telemetry-sector-banner__copy">
|
|
287
|
+
<span><b data-selected-code>--</b> sector telemetry</span>
|
|
288
|
+
<strong data-selected-name>Select driver</strong>
|
|
289
|
+
<em data-telemetry-current-sector>S1</em>
|
|
290
|
+
</div>
|
|
291
|
+
<div class="telemetry-sector-banner__bars">
|
|
292
|
+
${[1, 2, 3].map((sector) => `
|
|
293
|
+
<div class="telemetry-sector-bar" data-telemetry-sector-bar="${sector}" style="--sector-fill: 0%">
|
|
294
|
+
<span>S${sector}</span>
|
|
295
|
+
<strong data-telemetry-sector-time="${sector}">--</strong>
|
|
296
|
+
</div>
|
|
297
|
+
`).join('')}
|
|
298
|
+
</div>
|
|
299
|
+
${createLoadingMarkup('Sector banner')}
|
|
300
|
+
</section>
|
|
301
|
+
`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function createTelemetryLapTimesMarkup() {
|
|
305
|
+
return `
|
|
306
|
+
<section class="${getTelemetryModuleClass('lap-times')} telemetry-lap-module" data-paddock-component="telemetry-lap-times" aria-label="Lap timing">
|
|
307
|
+
<div class="telemetry-module-header">
|
|
308
|
+
<span>Lap timing</span>
|
|
309
|
+
<strong data-telemetry-completed-laps>0 laps</strong>
|
|
310
|
+
</div>
|
|
311
|
+
<table class="telemetry-lap-table" data-telemetry-lap-table>
|
|
312
|
+
<tbody>
|
|
313
|
+
<tr><th scope="row">Current</th><td data-telemetry-current-lap-time>--</td></tr>
|
|
314
|
+
<tr><th scope="row">Last</th><td data-telemetry-last-lap-time>--</td></tr>
|
|
315
|
+
<tr><th scope="row">Best</th><td data-telemetry-best-lap-time>--</td></tr>
|
|
316
|
+
</tbody>
|
|
317
|
+
</table>
|
|
318
|
+
${createLoadingMarkup('Lap telemetry')}
|
|
319
|
+
</section>
|
|
320
|
+
`;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function createTelemetrySectorTimesMarkup() {
|
|
324
|
+
return `
|
|
325
|
+
<section class="${getTelemetryModuleClass('sector-times')} telemetry-sector-table-module" data-paddock-component="telemetry-sector-times" aria-label="Sector timing table">
|
|
326
|
+
<div class="telemetry-module-header">
|
|
327
|
+
<span>Sector timing</span>
|
|
328
|
+
<strong>Last / Best</strong>
|
|
329
|
+
</div>
|
|
330
|
+
<table class="telemetry-sector-table" data-telemetry-sector-table>
|
|
331
|
+
<thead>
|
|
332
|
+
<tr><th scope="col">Sector</th><th scope="col">Last</th><th scope="col">Best</th></tr>
|
|
333
|
+
</thead>
|
|
334
|
+
<tbody>
|
|
335
|
+
${[1, 2, 3].map((sector) => `
|
|
336
|
+
<tr data-telemetry-sector-row="${sector}">
|
|
337
|
+
<th scope="row">S${sector}</th>
|
|
338
|
+
<td data-telemetry-sector-last="${sector}">--</td>
|
|
339
|
+
<td data-telemetry-sector-best="${sector}">--</td>
|
|
340
|
+
</tr>
|
|
341
|
+
`).join('')}
|
|
342
|
+
</tbody>
|
|
343
|
+
</table>
|
|
344
|
+
${createLoadingMarkup('Sector table')}
|
|
345
|
+
</section>
|
|
346
|
+
`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function createTelemetryComponentMarkup(options, modules = getTelemetryModules(options.ui)) {
|
|
350
|
+
return `
|
|
351
|
+
${modules.core ? createTelemetryCoreMarkup(options) : ''}
|
|
352
|
+
${modules.sectors ? createTelemetrySectorsMarkup(options) : ''}
|
|
353
|
+
${modules.lapTimes ? createTelemetryLapTimesMarkup(options) : ''}
|
|
354
|
+
${modules.sectorTimes ? createTelemetrySectorTimesMarkup(options) : ''}
|
|
355
|
+
`;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function createTelemetryPanelMarkup(options, { includeOverview = options.ui?.telemetryIncludesOverview !== false } = {}) {
|
|
359
|
+
const modules = getTelemetryModules(options.ui);
|
|
360
|
+
return `
|
|
361
|
+
<aside class="telemetry-stack" data-paddock-component="telemetry-stack" aria-label="Selected car telemetry stack">
|
|
362
|
+
${createTelemetryComponentMarkup(options, modules)}
|
|
225
363
|
${includeOverview ? createCarDriverOverviewMarkup(options) : ''}
|
|
226
|
-
${createLoadingMarkup('Telemetry')}
|
|
227
364
|
</aside>
|
|
228
365
|
`;
|
|
229
366
|
}
|
|
367
|
+
|
|
368
|
+
export function createRaceTelemetryDrawerMarkup(options, {
|
|
369
|
+
timingTowerVerticalFit,
|
|
370
|
+
drawerInitiallyOpen = false,
|
|
371
|
+
} = {}) {
|
|
372
|
+
const openClass = drawerInitiallyOpen ? ' is-telemetry-open' : '';
|
|
373
|
+
const drawerId = createTelemetryDrawerId();
|
|
374
|
+
return `
|
|
375
|
+
<section class="race-telemetry-drawer${openClass}" data-paddock-component="race-telemetry-drawer" data-race-telemetry-drawer aria-label="Race view with telemetry drawer">
|
|
376
|
+
<div class="race-telemetry-drawer__race">
|
|
377
|
+
${createRaceCanvasMarkup({
|
|
378
|
+
...options,
|
|
379
|
+
includeRaceDataPanel: true,
|
|
380
|
+
includeTimingTower: true,
|
|
381
|
+
includeTelemetrySectorBanner: true,
|
|
382
|
+
timingTowerVerticalFit,
|
|
383
|
+
})}
|
|
384
|
+
</div>
|
|
385
|
+
<div class="race-telemetry-drawer__controls" aria-label="Race workbench controls">
|
|
386
|
+
${createSafetyCarControlMarkup({ compact: true })}
|
|
387
|
+
<button class="telemetry-drawer-toggle" type="button" data-telemetry-drawer-toggle aria-expanded="${drawerInitiallyOpen ? 'true' : 'false'}" aria-controls="${drawerId}">
|
|
388
|
+
${drawerInitiallyOpen ? 'Close telemetry' : 'Telemetry'}
|
|
389
|
+
</button>
|
|
390
|
+
</div>
|
|
391
|
+
<aside id="${drawerId}" class="telemetry-drawer" data-telemetry-drawer aria-label="Telemetry drawer" aria-hidden="${drawerInitiallyOpen ? 'false' : 'true'}"${drawerInitiallyOpen ? '' : ' inert'}>
|
|
392
|
+
<div class="telemetry-drawer__header">
|
|
393
|
+
<span>Live telemetry</span>
|
|
394
|
+
<button type="button" data-telemetry-drawer-close>Close</button>
|
|
395
|
+
</div>
|
|
396
|
+
<div class="telemetry-drawer__content">
|
|
397
|
+
${createTelemetryComponentMarkup(options)}
|
|
398
|
+
</div>
|
|
399
|
+
</aside>
|
|
400
|
+
</section>
|
|
401
|
+
`;
|
|
402
|
+
}
|