@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/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(72vw, var(--timing-board-max-width));
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% - 5.4rem);
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
- export function createTelemetryPanelMarkup(options, { includeOverview = options.ui?.telemetryIncludesOverview !== false } = {}) {
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
- <aside class="sim-telemetry" data-paddock-component="telemetry-panel" aria-label="Selected car telemetry">
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
+ }