Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Mobile optimizations and Safran logo
Browse files- Reduce timeline padding on mobile
- Hide timeline tooltips on mobile
- Move event card to top of map on mobile
- Zoom out to show all France during playback on mobile
- Add embedded Safran Aircraft Engines logo to header
- index.html +46 -15
- src/components/Map.js +8 -0
- src/main.js +8 -0
- src/styles/main.css +24 -9
- src/styles/timeline.css +4 -3
index.html
CHANGED
|
@@ -1,31 +1,62 @@
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="fr">
|
| 3 |
<head>
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
</head>
|
| 11 |
<body>
|
| 12 |
-
|
| 13 |
<header class="header">
|
| 14 |
-
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
</header>
|
| 17 |
|
| 18 |
<main class="main">
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
</main>
|
| 23 |
|
| 24 |
<footer class="footer">
|
| 25 |
-
|
| 26 |
</footer>
|
| 27 |
-
|
| 28 |
|
| 29 |
-
|
| 30 |
</body>
|
| 31 |
</html>
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="fr">
|
| 3 |
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Patrouille de France - Airshow Tracker</title>
|
| 7 |
+
<link href="https://api.mapbox.com/mapbox-gl-js/v3.9.4/mapbox-gl.css" rel="stylesheet">
|
| 8 |
+
<link rel="stylesheet" href="/src/styles/main.css">
|
| 9 |
+
<link rel="stylesheet" href="/src/styles/timeline.css">
|
| 10 |
</head>
|
| 11 |
<body>
|
| 12 |
+
<div id="app">
|
| 13 |
<header class="header">
|
| 14 |
+
<h1>Patrouille de France</h1>
|
| 15 |
+
<span class="subtitle">Airshow Tracker</span>
|
| 16 |
+
<span class="header-spacer"></span>
|
| 17 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="header-logo"
|
| 18 |
+
x="0px" y="0px" viewBox="0 0 922 200">
|
| 19 |
+
<g>
|
| 20 |
+
<path class="st0" d="M488.8,70.9v25l56.7,0l-3.4,16.2h-53.2v35.8H463V54.6h91.3l-3.5,16.3H488.8z M803,147.9h-29.7l-8.9-18.9h-49.8
|
| 21 |
+
l-8.6,18.9h-18.7l43.9-93.3c0,0,11.7,0,17.5,0c6.6,0,9.1,2.7,12.5,9.7C764.4,70.9,803,147.9,803,147.9z M756.7,112.8l-17.6-37.2
|
| 22 |
+
l-17,37.2H756.7z M450.2,147.9h-29.7l-8.9-18.9h-49.8l-8.6,18.9h-18.7l44-93.3c0,0,11.7,0,17.5,0c6.6,0,9.1,2.7,12.5,9.7
|
| 23 |
+
C411.5,70.9,450.2,147.9,450.2,147.9z M403.8,112.8l-17.6-37.2l-17,37.2H403.8z M280,150.9c33.4,0,48.8-12.2,48.8-30
|
| 24 |
+
c0-15.5-14-23.4-44.7-31.8c-18.3-5-29.7-6-29.7-12.2c0-5.9,10.7-9.1,27.1-8.3c20.6,1,33.7,9.7,33.7,9.7l8.1-12.9
|
| 25 |
+
c0,0-18.5-13.7-47.9-13.7c-32.1,0-49,9.8-49,27.8c0,17.8,19.2,23.9,47.2,31.9c17.2,4.9,27.2,5.8,27.2,12.4
|
| 26 |
+
c0,5.1-5.2,11.4-28.1,10.3c-22-1.1-41.1-13.8-41.1-13.8l-9.8,11.9C221.9,132.1,242.8,150.9,280,150.9z M902.5,54.6v55.7
|
| 27 |
+
c0,0-54.7-43.6-59.2-47.2c-8.1-6.4-11.8-8.5-20.8-8.5c-3.8,0-6.7,0-6.7,0v93.3H835V93.1c0,0,54.2,43.3,58.7,46.8
|
| 28 |
+
c8.1,6.4,12.2,8,21.2,8c3.8,0,6.7,0,6.7,0V54.6H902.5z M677.4,147.9c0,0-6.7,0-17.5,0c-10.4,0-16.7-7.2-20.5-12.2
|
| 29 |
+
c-4.2-5.5-14.7-19.4-14.7-19.4s-1.2,0-1.8,0c-7,0-26.3,0-26.3,0h-0.3v31.6h-25.9V54.6h26.2c0,0,19.2,0,26.3,0
|
| 30 |
+
c20.1,0,49,2.1,49,30.9v0c0,16.7-9.8,24.4-21.9,27.9L677.4,147.9z M643.8,85.5L643.8,85.5c0-9.8-6.2-14.6-19.9-14.6h-27.3l-0.3-0.2
|
| 31 |
+
v29.6l0.3-0.2h27.3C637.6,100.1,643.8,95.3,643.8,85.5z">
|
| 32 |
+
</path>
|
| 33 |
+
<path class="st0" d="M175,141.5h-2.1c-19.2,26.3-50.2,43.4-85.2,43.4c-35,0-66.1-17.1-85.2-43.4H0.4c15.5,32.6,48.8,55.2,87.3,55.2
|
| 34 |
+
C126.2,196.6,159.5,174.1,175,141.5z">
|
| 35 |
+
</path>
|
| 36 |
+
<path id="_x3C_Tracé_x3E__7_" class="st0" d="M156.9,151.9c4.7-5.2,8.9-12.4,9.8-17.6c4.5-23.8-17.6-35.9-43.7-45
|
| 37 |
+
c-20.4-7.1-37.2-12-51.5-16.4c-22.7-7-26.6-13.7-26.4-20.9c0.2-8.4,9.1-19,19.2-25.3c0,0-19-0.1-38.3,14.3
|
| 38 |
+
C14.7,49.5,10,58.3,8.3,66.3c-8.1,39.8,70.5,55.6,100.7,65.4c16.5,5.4,18.7,12.6,18.6,18.3c-0.3,13.3-19.1,25.3-19.2,25.3
|
| 39 |
+
C123.7,175,144.4,165.9,156.9,151.9">
|
| 40 |
+
</path>
|
| 41 |
+
<path class="st0" d="M175,58.5h-2.1c-19.2-26.3-50.2-43.4-85.2-43.4c-35,0-66.1,17.1-85.2,43.4H0.4C15.9,25.9,49.2,3.4,87.7,3.4
|
| 42 |
+
C126.2,3.4,159.5,25.9,175,58.5z">
|
| 43 |
+
</path>
|
| 44 |
+
</g>
|
| 45 |
+
</svg>
|
| 46 |
+
|
| 47 |
</header>
|
| 48 |
|
| 49 |
<main class="main">
|
| 50 |
+
<div id="map" class="map"></div>
|
| 51 |
+
<div id="countdown" class="countdown-container"></div>
|
| 52 |
+
<div id="event-card" class="event-card hidden"></div>
|
| 53 |
</main>
|
| 54 |
|
| 55 |
<footer class="footer">
|
| 56 |
+
<div id="timeline" class="timeline"></div>
|
| 57 |
</footer>
|
| 58 |
+
</div>
|
| 59 |
|
| 60 |
+
<script type="module" src="/src/main.js"></script>
|
| 61 |
</body>
|
| 62 |
</html>
|
src/components/Map.js
CHANGED
|
@@ -136,6 +136,14 @@ export class MapView {
|
|
| 136 |
this.hideFlightPath();
|
| 137 |
}
|
| 138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
getMap() {
|
| 140 |
return this.map;
|
| 141 |
}
|
|
|
|
| 136 |
this.hideFlightPath();
|
| 137 |
}
|
| 138 |
|
| 139 |
+
zoomToFrance() {
|
| 140 |
+
this.map.flyTo({
|
| 141 |
+
center: MAP_CONFIG.center,
|
| 142 |
+
zoom: 4.3,
|
| 143 |
+
duration: 800
|
| 144 |
+
});
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
getMap() {
|
| 148 |
return this.map;
|
| 149 |
}
|
src/main.js
CHANGED
|
@@ -6,6 +6,9 @@ import { HOME_BASE } from './config.js';
|
|
| 6 |
import airshowData from './data/airshows.json';
|
| 7 |
|
| 8 |
const FLIGHT_ANIMATION_DURATION = 2000; // ms
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
class App {
|
| 11 |
constructor() {
|
|
@@ -103,6 +106,11 @@ class App {
|
|
| 103 |
this.isPlaying = playing;
|
| 104 |
|
| 105 |
if (playing) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
// Start playback
|
| 107 |
if (this.timeline.selectedIndex < 0) {
|
| 108 |
// No event selected, start from first
|
|
|
|
| 6 |
import airshowData from './data/airshows.json';
|
| 7 |
|
| 8 |
const FLIGHT_ANIMATION_DURATION = 2000; // ms
|
| 9 |
+
const MOBILE_BREAKPOINT = 768;
|
| 10 |
+
|
| 11 |
+
const isMobile = () => window.innerWidth <= MOBILE_BREAKPOINT;
|
| 12 |
|
| 13 |
class App {
|
| 14 |
constructor() {
|
|
|
|
| 106 |
this.isPlaying = playing;
|
| 107 |
|
| 108 |
if (playing) {
|
| 109 |
+
// On mobile, zoom out to show all of France during playback
|
| 110 |
+
if (isMobile()) {
|
| 111 |
+
this.map.zoomToFrance();
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
// Start playback
|
| 115 |
if (this.timeline.selectedIndex < 0) {
|
| 116 |
// No event selected, start from first
|
src/styles/main.css
CHANGED
|
@@ -67,6 +67,16 @@ body {
|
|
| 67 |
color: var(--color-text-muted);
|
| 68 |
}
|
| 69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
/* Main content */
|
| 71 |
.main {
|
| 72 |
flex: 1;
|
|
@@ -340,20 +350,21 @@ body {
|
|
| 340 |
display: none;
|
| 341 |
}
|
| 342 |
|
| 343 |
-
/* Event card -
|
| 344 |
.event-card {
|
| 345 |
-
top:
|
| 346 |
-
bottom:
|
| 347 |
-
left:
|
| 348 |
-
right:
|
| 349 |
-
width:
|
| 350 |
-
border-radius:
|
| 351 |
-
padding:
|
| 352 |
transform: translateY(0);
|
| 353 |
}
|
| 354 |
|
| 355 |
.event-card.hidden {
|
| 356 |
-
transform: translateY(100%);
|
|
|
|
| 357 |
}
|
| 358 |
|
| 359 |
.event-card h2 {
|
|
@@ -381,4 +392,8 @@ body {
|
|
| 381 |
.header .subtitle {
|
| 382 |
display: none;
|
| 383 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
}
|
|
|
|
| 67 |
color: var(--color-text-muted);
|
| 68 |
}
|
| 69 |
|
| 70 |
+
.header-spacer {
|
| 71 |
+
flex: 1;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
.header-logo {
|
| 75 |
+
height: 24px;
|
| 76 |
+
opacity: 0.6;
|
| 77 |
+
filter: brightness(0) invert(1);
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
/* Main content */
|
| 81 |
.main {
|
| 82 |
flex: 1;
|
|
|
|
| 350 |
display: none;
|
| 351 |
}
|
| 352 |
|
| 353 |
+
/* Event card - top position on mobile */
|
| 354 |
.event-card {
|
| 355 |
+
top: 8px;
|
| 356 |
+
bottom: auto;
|
| 357 |
+
left: 8px;
|
| 358 |
+
right: 8px;
|
| 359 |
+
width: auto;
|
| 360 |
+
border-radius: 8px;
|
| 361 |
+
padding: 12px;
|
| 362 |
transform: translateY(0);
|
| 363 |
}
|
| 364 |
|
| 365 |
.event-card.hidden {
|
| 366 |
+
transform: translateY(-100%);
|
| 367 |
+
opacity: 0;
|
| 368 |
}
|
| 369 |
|
| 370 |
.event-card h2 {
|
|
|
|
| 392 |
.header .subtitle {
|
| 393 |
display: none;
|
| 394 |
}
|
| 395 |
+
|
| 396 |
+
.header-logo {
|
| 397 |
+
height: 18px;
|
| 398 |
+
}
|
| 399 |
}
|
src/styles/timeline.css
CHANGED
|
@@ -198,7 +198,7 @@
|
|
| 198 |
/* Mobile: horizontal scrolling timeline */
|
| 199 |
@media (max-width: 768px) {
|
| 200 |
.timeline {
|
| 201 |
-
padding:
|
| 202 |
gap: 12px;
|
| 203 |
}
|
| 204 |
|
|
@@ -214,7 +214,7 @@
|
|
| 214 |
|
| 215 |
.timeline-months {
|
| 216 |
gap: 16px;
|
| 217 |
-
padding:
|
| 218 |
}
|
| 219 |
|
| 220 |
.timeline-year {
|
|
@@ -239,7 +239,8 @@
|
|
| 239 |
height: 14px;
|
| 240 |
}
|
| 241 |
|
|
|
|
| 242 |
.timeline-event-tooltip {
|
| 243 |
-
|
| 244 |
}
|
| 245 |
}
|
|
|
|
| 198 |
/* Mobile: horizontal scrolling timeline */
|
| 199 |
@media (max-width: 768px) {
|
| 200 |
.timeline {
|
| 201 |
+
padding: 4px 16px;
|
| 202 |
gap: 12px;
|
| 203 |
}
|
| 204 |
|
|
|
|
| 214 |
|
| 215 |
.timeline-months {
|
| 216 |
gap: 16px;
|
| 217 |
+
padding: 4px 16px 4px 0;
|
| 218 |
}
|
| 219 |
|
| 220 |
.timeline-year {
|
|
|
|
| 239 |
height: 14px;
|
| 240 |
}
|
| 241 |
|
| 242 |
+
/* Hide tooltip on mobile */
|
| 243 |
.timeline-event-tooltip {
|
| 244 |
+
display: none;
|
| 245 |
}
|
| 246 |
}
|