SPB Git forge

spb/voyage2026

Public
1commits 1branches 0releases
32.0 KBsize
maindefault branch
19 days agolast push
HTML 95.5% JavaScript 4.5%

chore(retrait cluster): synchro copie du nœud M3U96b + README « URL live hors fonction » (2026-09-04)

Simon-Pierre Boucher committed 19 days ago (Sep 4, 2026)

4 changed files +728 −0

added .gitignore +10 −0
@@ -0,0 +1,10 @@
1 +node_modules/
2 +.next/
3 +dist/
4 +.env
5 +.env.*
6 +!.env.example
7 +*.db*
8 +*.log
9 +.DS_Store
10 +data/models/
added README.md +4 −0
@@ -0,0 +1,4 @@
1 +> ⚠️ **URL live hors fonction pour le moment.** L'application a été retirée du cluster MacLustr le 2026-09-04 (processus arrêtés, copie du nœud supprimée). Ce dépôt spbgit est désormais la seule source de vérité du projet. Ancienne URL : https://www.voyage2026toronto.com
2 +
3 +# voyage2026
4 +
added itineraire-toronto-2026.html +662 −0
@@ -0,0 +1,662 @@
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>Erika ♥ SP · Road Trip Québec → Toronto · Ed Sheeran LOOP Tour · 17–24 août 2026</title>
7 +<meta name="description" content="Itinéraire complet du road trip Québec–Toronto pour le Ed Sheeran LOOP Tour au Rogers Centre, 17 au 24 août 2026.">
8 +<link rel="preconnect" href="https://fonts.googleapis.com">
9 +<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10 +<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
11 +<style>
12 + :root {
13 + --bg: #0b0d14;
14 + --bg-2: #11141f;
15 + --card: #161a28;
16 + --card-2: #1c2133;
17 + --ink: #f2f4fa;
18 + --ink-soft: #9aa3ba;
19 + --line: rgba(255,255,255,.08);
20 + --orange: #ff6b2c;
21 + --pink: #ff2e88;
22 + --violet: #7c4dff;
23 + --teal: #2dd4bf;
24 + --grad: linear-gradient(100deg, #ff6b2c, #ff2e88 45%, #7c4dff);
25 + --radius: 22px;
26 + }
27 + * { margin: 0; padding: 0; box-sizing: border-box; }
28 + html { scroll-behavior: smooth; }
29 + body {
30 + font-family: "Inter", -apple-system, sans-serif;
31 + background: var(--bg);
32 + color: var(--ink);
33 + line-height: 1.65;
34 + overflow-x: hidden;
35 + }
36 + h1, h2, h3, .num, .cd-num { font-family: "Space Grotesk", "Inter", sans-serif; }
37 + .wrap { max-width: 1020px; margin: 0 auto; padding: 0 24px; }
38 +
39 + /* ---------- STICKY NAV ---------- */
40 + .nav {
41 + position: fixed; top: 0; left: 0; right: 0; z-index: 50;
42 + background: rgba(11,13,20,.72); backdrop-filter: blur(14px);
43 + border-bottom: 1px solid var(--line);
44 + }
45 + .nav-inner {
46 + max-width: 1020px; margin: 0 auto; padding: 12px 24px;
47 + display: flex; align-items: center; gap: 16px;
48 + }
49 + .nav .logo { font-weight: 800; font-size: 15px; letter-spacing: .02em; white-space: nowrap; }
50 + .nav .logo span { background: var(--grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
51 + .nav-days { display: flex; gap: 4px; margin-left: auto; overflow-x: auto; scrollbar-width: none; }
52 + .nav-days::-webkit-scrollbar { display: none; }
53 + .nav-days a {
54 + color: var(--ink-soft); text-decoration: none; font-size: 13px; font-weight: 600;
55 + padding: 6px 12px; border-radius: 999px; white-space: nowrap; transition: all .2s;
56 + }
57 + .nav-days a:hover { color: #fff; background: rgba(255,255,255,.08); }
58 + .nav-days a.hot { color: #fff; background: linear-gradient(100deg, #ff6b2c, #ff2e88); }
59 +
60 + /* ---------- HERO ---------- */
61 + .hero {
62 + position: relative; min-height: 92vh;
63 + display: flex; align-items: flex-end;
64 + isolation: isolate; overflow: hidden;
65 + }
66 + .hero-bg {
67 + position: absolute; inset: -6%;
68 + background: url("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3c/Sunset_Toronto_Skyline_Panorama_Crop_from_Snake_Island.jpg/1920px-Sunset_Toronto_Skyline_Panorama_Crop_from_Snake_Island.jpg") center 62% / cover no-repeat;
69 + z-index: -2; animation: kenburns 26s ease-in-out infinite alternate;
70 + }
71 + @keyframes kenburns { from { transform: scale(1); } to { transform: scale(1.09) translateY(-1.5%); } }
72 + .hero::after {
73 + content: ""; position: absolute; inset: 0; z-index: -1;
74 + background:
75 + radial-gradient(120% 70% at 70% 10%, transparent 40%, rgba(11,13,20,.55) 100%),
76 + linear-gradient(180deg, rgba(11,13,20,.45) 0%, rgba(11,13,20,.35) 45%, var(--bg) 100%);
77 + }
78 + .hero-inner { position: relative; width: 100%; max-width: 1020px; margin: 0 auto; padding: 140px 24px 72px; }
79 + .kicker {
80 + display: inline-flex; align-items: center; gap: 10px;
81 + text-transform: uppercase; letter-spacing: .3em; font-size: 12px; font-weight: 700;
82 + color: #ffd9c4; margin-bottom: 18px;
83 + }
84 + .kicker::before { content: ""; width: 34px; height: 2px; background: var(--grad); border-radius: 2px; }
85 + .hero h1 {
86 + font-size: clamp(40px, 7.5vw, 84px); font-weight: 700; line-height: 1.02;
87 + letter-spacing: -.02em; margin-bottom: 14px;
88 + }
89 + .hero h1 .grad {
90 + background: var(--grad); -webkit-background-clip: text; background-clip: text; color: transparent;
91 + background-size: 200% 100%; animation: shine 6s linear infinite;
92 + }
93 + @keyframes shine { to { background-position: 200% 0; } }
94 + .hero .sub { font-size: clamp(16px, 2.2vw, 20px); color: #d5daea; max-width: 640px; }
95 + .badges { margin-top: 26px; display: flex; flex-wrap: wrap; gap: 10px; }
96 + .badge {
97 + background: rgba(255,255,255,.08); backdrop-filter: blur(8px);
98 + border: 1px solid rgba(255,255,255,.18); border-radius: 999px;
99 + padding: 8px 18px; font-size: 14px; font-weight: 600;
100 + }
101 + .badge.hot { background: linear-gradient(100deg, rgba(255,107,44,.9), rgba(255,46,136,.9)); border-color: transparent; }
102 +
103 + /* ---------- ERIKA + SP ---------- */
104 + .lovers {
105 + display: inline-flex; align-items: center; gap: 12px;
106 + margin: 6px 0 16px;
107 + font-family: "Space Grotesk", "Inter", sans-serif;
108 + font-size: clamp(19px, 2.8vw, 26px); font-weight: 700; letter-spacing: .01em;
109 + }
110 + .lovers .name { background: var(--grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
111 + .lovers .heart {
112 + display: inline-block; color: #ff2e88; font-size: 1.15em; line-height: 1;
113 + animation: heartbeat 1.5s ease-in-out infinite;
114 + filter: drop-shadow(0 0 10px rgba(255,46,136,.55));
115 + }
116 + @keyframes heartbeat {
117 + 0%, 100% { transform: scale(1); }
118 + 14% { transform: scale(1.3); }
119 + 28% { transform: scale(1); }
120 + 42% { transform: scale(1.22); }
121 + 56% { transform: scale(1); }
122 + }
123 +
124 + /* ---------- COUNTDOWN ---------- */
125 + .countdown { margin-top: 34px; display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
126 + .cd-label { font-size: 13px; text-transform: uppercase; letter-spacing: .18em; color: #ffd9c4; font-weight: 700; width: 100%; }
127 + .cd-box {
128 + min-width: 86px; text-align: center; padding: 14px 10px 10px;
129 + background: rgba(255,255,255,.07); backdrop-filter: blur(10px);
130 + border: 1px solid rgba(255,255,255,.16); border-radius: 16px;
131 + }
132 + .cd-num { font-size: 34px; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; }
133 + .cd-unit { font-size: 11px; text-transform: uppercase; letter-spacing: .14em; color: var(--ink-soft); margin-top: 6px; }
134 +
135 + /* ---------- STATS ---------- */
136 + .stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-top: -10px; position: relative; z-index: 2; }
137 + .stat {
138 + background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
139 + padding: 20px; text-align: center; transition: transform .25s, border-color .25s;
140 + }
141 + .stat:hover { transform: translateY(-4px); border-color: rgba(255,107,44,.5); }
142 + .stat .num { font-size: 28px; font-weight: 700; background: var(--grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
143 + .stat .lbl { font-size: 12px; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .1em; margin-top: 2px; }
144 +
145 + /* ---------- ROUTE STRIP ---------- */
146 + .route-strip {
147 + margin: 30px 0 0; background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
148 + padding: 22px 26px; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; justify-content: center;
149 + font-weight: 700; font-size: 15px;
150 + }
151 + .route-strip .city { color: var(--ink); }
152 + .route-strip .leg { color: var(--ink-soft); font-weight: 500; font-size: 12.5px; padding: 0 8px; position: relative; }
153 +
154 + /* ---------- SECTIONS ---------- */
155 + h2.section {
156 + font-size: clamp(24px, 3.4vw, 34px); font-weight: 700; letter-spacing: -.01em;
157 + margin: 72px 0 8px; display: flex; align-items: center; gap: 14px;
158 + }
159 + h2.section::after { content: ""; flex: 1; height: 1px; background: linear-gradient(90deg, rgba(255,107,44,.6), transparent); }
160 + .section-sub { color: var(--ink-soft); margin-bottom: 30px; max-width: 720px; }
161 +
162 + /* ---------- DAY CARDS ---------- */
163 + .day {
164 + background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
165 + overflow: hidden; margin-bottom: 40px; scroll-margin-top: 90px;
166 + transition: border-color .3s;
167 + }
168 + .day:hover { border-color: rgba(255,255,255,.2); }
169 + .day-photo { height: 340px; overflow: hidden; position: relative; }
170 + .day-photo .img {
171 + position: absolute; inset: 0; background-size: cover; background-position: center;
172 + transition: transform .8s cubic-bezier(.2,.6,.2,1);
173 + }
174 + .day:hover .day-photo .img { transform: scale(1.06); }
175 + .day-photo::after {
176 + content: ""; position: absolute; inset: 0;
177 + background: linear-gradient(180deg, rgba(11,13,20,.15), transparent 35%, transparent 70%, rgba(22,26,40,.9));
178 + pointer-events: none;
179 + }
180 + .day-photo .tag {
181 + position: absolute; top: 18px; left: 18px; z-index: 2;
182 + background: rgba(11,13,20,.7); backdrop-filter: blur(8px); color: #fff;
183 + border: 1px solid rgba(255,255,255,.2);
184 + padding: 8px 18px; border-radius: 999px; font-size: 13px; font-weight: 800; letter-spacing: .08em;
185 + }
186 + .day-photo .drive {
187 + position: absolute; bottom: 18px; right: 18px; z-index: 2;
188 + background: rgba(255,255,255,.92); color: #10131d;
189 + padding: 8px 16px; border-radius: 999px; font-size: 13px; font-weight: 700;
190 + }
191 + .day-body { padding: 30px 34px 34px; }
192 + .day-body h3 { font-size: 24px; font-weight: 700; letter-spacing: -.01em; margin-bottom: 2px; }
193 + .day-body .date {
194 + color: var(--orange); font-weight: 800; font-size: 13px;
195 + text-transform: uppercase; letter-spacing: .14em; margin-bottom: 16px;
196 + }
197 + .timeline { list-style: none; margin: 14px 0 4px; }
198 + .timeline li {
199 + display: grid; grid-template-columns: 92px 1fr; gap: 16px;
200 + padding: 10px 0; border-bottom: 1px dashed var(--line); align-items: start;
201 + }
202 + .timeline li:last-child { border-bottom: none; }
203 + .timeline .t { font-weight: 800; color: var(--teal); font-size: 14px; padding-top: 2px; white-space: nowrap; font-variant-numeric: tabular-nums; }
204 + .timeline .d { color: #cdd3e4; }
205 + .timeline .d strong { color: #fff; }
206 + .timeline .d em { color: #ffb38a; font-style: normal; font-weight: 600; }
207 + .chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
208 + .chip {
209 + font-size: 13px; font-weight: 600; padding: 7px 15px; border-radius: 999px;
210 + background: rgba(45,212,191,.1); color: var(--teal); border: 1px solid rgba(45,212,191,.3);
211 + }
212 + .chip.hotel { background: rgba(255,107,44,.12); color: #ffa06b; border-color: rgba(255,107,44,.35); }
213 + .tip {
214 + margin-top: 18px; background: rgba(250,204,21,.08); border: 1px solid rgba(250,204,21,.3);
215 + border-radius: 14px; padding: 13px 17px; font-size: 14px; color: #fde68a;
216 + }
217 + .maplink {
218 + display: inline-flex; align-items: center; gap: 8px; margin-top: 18px;
219 + font-size: 14px; font-weight: 700; color: var(--teal); text-decoration: none;
220 + padding: 9px 18px; border: 1px solid rgba(45,212,191,.35); border-radius: 999px;
221 + transition: all .2s;
222 + }
223 + .maplink:hover { background: rgba(45,212,191,.12); transform: translateX(3px); }
224 +
225 + /* ---------- CONCERT SPOTLIGHT ---------- */
226 + .concert { position: relative; border: none; }
227 + .concert::before {
228 + content: ""; position: absolute; inset: 0; border-radius: var(--radius); padding: 2px;
229 + background: var(--grad); background-size: 300% 100%; animation: shine 5s linear infinite;
230 + -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
231 + -webkit-mask-composite: xor; mask-composite: exclude;
232 + pointer-events: none; z-index: 3;
233 + }
234 + .concert-banner {
235 + background: var(--grad); background-size: 300% 100%; animation: shine 5s linear infinite;
236 + color: #fff; text-align: center; padding: 12px; font-weight: 800; letter-spacing: .16em;
237 + text-transform: uppercase; font-size: 14px;
238 + }
239 + .concert .day-photo .tag { background: linear-gradient(100deg, #ff6b2c, #ff2e88); border-color: transparent; }
240 + .concert-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-top: 20px; }
241 + .concert-fact {
242 + background: var(--card-2); border: 1px solid var(--line); border-radius: 16px;
243 + padding: 16px 14px; text-align: center; transition: transform .25s;
244 + }
245 + .concert-fact:hover { transform: translateY(-3px); }
246 + .concert-fact .k { font-size: 11px; text-transform: uppercase; letter-spacing: .12em; color: var(--ink-soft); }
247 + .concert-fact .v { font-weight: 800; font-size: 16px; margin-top: 4px; background: var(--grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
248 + .eq { display: inline-flex; align-items: flex-end; gap: 3px; height: 16px; margin-left: 10px; }
249 + .eq i { width: 3px; background: #fff; border-radius: 2px; animation: eq 1s ease-in-out infinite; }
250 + .eq i:nth-child(1) { height: 60%; animation-delay: 0s; }
251 + .eq i:nth-child(2) { height: 100%; animation-delay: .18s; }
252 + .eq i:nth-child(3) { height: 45%; animation-delay: .34s; }
253 + .eq i:nth-child(4) { height: 80%; animation-delay: .5s; }
254 + @keyframes eq { 0%, 100% { transform: scaleY(.4); } 50% { transform: scaleY(1); } }
255 +
256 + /* ---------- INFO GRID ---------- */
257 + .info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; }
258 + .info-card {
259 + background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
260 + padding: 26px 28px; transition: border-color .3s;
261 + }
262 + .info-card:hover { border-color: rgba(255,255,255,.2); }
263 + .info-card h3 { font-size: 18px; font-weight: 700; margin-bottom: 14px; }
264 + .info-card ul { margin-left: 20px; }
265 + .info-card li { margin-bottom: 8px; font-size: 15px; color: #cdd3e4; }
266 + .info-card li strong { color: #fff; }
267 + table.budget { width: 100%; border-collapse: collapse; font-size: 15px; color: #cdd3e4; }
268 + table.budget td { padding: 9px 4px; border-bottom: 1px solid var(--line); }
269 + table.budget td:last-child { text-align: right; font-weight: 700; white-space: nowrap; color: #fff; }
270 + table.budget tr.total td {
271 + border-top: 2px solid rgba(255,107,44,.6); border-bottom: none;
272 + font-weight: 800; font-size: 16px; padding-top: 14px; color: #fff;
273 + }
274 +
275 + /* ---------- REVEAL ANIMATION ---------- */
276 + .reveal { opacity: 0; transform: translateY(28px); transition: opacity .7s ease, transform .7s ease; }
277 + .reveal.in { opacity: 1; transform: none; }
278 +
279 + /* ---------- FOOTER ---------- */
280 + footer { margin: 80px 0 50px; color: var(--ink-soft); font-size: 12.5px; text-align: center; }
281 + footer a { color: var(--ink-soft); }
282 + .footer-heart { margin-top: 8px; font-size: 13px; }
283 + .footer-heart span { background: var(--grad); -webkit-background-clip: text; background-clip: text; color: transparent; font-weight: 700; }
284 +
285 + @media (max-width: 720px) {
286 + .stats { grid-template-columns: repeat(2, 1fr); }
287 + .info-grid, .concert-grid { grid-template-columns: 1fr; }
288 + .hero-inner { padding-top: 110px; }
289 + .cd-box { min-width: 72px; }
290 + .day-body { padding: 24px 22px 28px; }
291 + .nav-days a { padding: 6px 9px; }
292 + }
293 +
294 + @media print {
295 + :root { --bg: #fff; --bg-2: #fff; --card: #fff; --card-2: #f6f7fa; --ink: #1a2233; --ink-soft: #4a556b; --line: #dde2ec; }
296 + body { background: #fff; color: #1a2233; }
297 + .nav, .countdown, .eq { display: none !important; }
298 + .hero { min-height: 340px; }
299 + .hero .sub { color: #333; }
300 + .day, .info-card, .stat, .route-strip { box-shadow: none; border: 1px solid #dde2ec; page-break-inside: avoid; }
301 + .timeline .d, .info-card li, table.budget { color: #1a2233; }
302 + .timeline .d strong, .info-card li strong, table.budget td:last-child { color: #000; }
303 + .reveal { opacity: 1 !important; transform: none !important; }
304 + -webkit-print-color-adjust: exact; print-color-adjust: exact;
305 + }
306 +</style>
307 +</head>
308 +<body>
309 +
310 +<nav class="nav">
311 + <div class="nav-inner">
312 + <div class="logo">🚗 <span>QC → TO 2026</span></div>
313 + <div class="nav-days">
314 + <a href="#j1">J1</a><a href="#j2">J2</a><a href="#j3">J3</a>
315 + <a href="#j4" class="hot">🎤 J4</a>
316 + <a href="#j5">J5</a><a href="#j6">J6</a><a href="#j7">J7</a><a href="#j8">J8</a>
317 + <a href="#infos">Infos</a>
318 + </div>
319 + </div>
320 +</nav>
321 +
322 +<header class="hero">
323 + <div class="hero-bg"></div>
324 + <div class="hero-inner">
325 + <div class="kicker">Road trip · 17 – 24 août 2026</div>
326 + <h1>Québec → Toronto<br><span class="grad">Ed Sheeran LOOP Tour</span></h1>
327 + <div class="lovers">Le trip de <span class="name">Erika</span> <span class="heart">♥</span> <span class="name">SP</span></div>
328 + <p class="sub">8 jours sur la route — Montréal, Ottawa, les Mille-Îles, Toronto et Niagara. Et au centre de tout : LE show au Rogers Centre.</p>
329 + <div class="badges">
330 + <span class="badge hot">🎤 20 août · Rogers Centre</span>
331 + <span class="badge">🚗 ~1 900 km</span>
332 + <span class="badge">🛏️ 7 nuits</span>
333 + <span class="badge">🏙️ 6 villes</span>
334 + </div>
335 + <div class="countdown" id="countdown">
336 + <div class="cd-label">Compte à rebours avant le show</div>
337 + <div class="cd-box"><div class="cd-num" id="cd-d">–</div><div class="cd-unit">jours</div></div>
338 + <div class="cd-box"><div class="cd-num" id="cd-h">–</div><div class="cd-unit">heures</div></div>
339 + <div class="cd-box"><div class="cd-num" id="cd-m">–</div><div class="cd-unit">min</div></div>
340 + <div class="cd-box"><div class="cd-num" id="cd-s">–</div><div class="cd-unit">sec</div></div>
341 + </div>
342 + </div>
343 +</header>
344 +
345 +<div class="wrap">
346 +
347 + <div class="stats reveal">
348 + <div class="stat"><div class="num">8</div><div class="lbl">jours</div></div>
349 + <div class="stat"><div class="num">1 900 km</div><div class="lbl">de route</div></div>
350 + <div class="stat"><div class="num">6</div><div class="lbl">villes-étapes</div></div>
351 + <div class="stat"><div class="num">20 août</div><div class="lbl">🎤 concert</div></div>
352 + </div>
353 +
354 + <div class="route-strip reveal">
355 + <span class="city">Québec</span><span class="leg">— 250 km →</span>
356 + <span class="city">Montréal</span><span class="leg">— 200 km →</span>
357 + <span class="city">Ottawa</span><span class="leg">— 195 km →</span>
358 + <span class="city">Kingston</span><span class="leg">— 265 km →</span>
359 + <span class="city">Toronto</span><span class="leg">— 540 km →</span>
360 + <span class="city">Montréal</span><span class="leg">— 250 km →</span>
361 + <span class="city">Québec</span>
362 + </div>
363 +
364 + <h2 class="section reveal">📅 Itinéraire jour par jour</h2>
365 + <p class="section-sub reveal">Toutes les durées de route sont sans trafic — ajoute 20-30 % aux heures de pointe (surtout en arrivant à Montréal et Toronto).</p>
366 +
367 + <!-- JOUR 1 -->
368 + <div class="day reveal" id="j1">
369 + <div class="day-photo">
370 + <div class="img" style="background-image:url('https://upload.wikimedia.org/wikipedia/commons/thumb/e/e2/Bas%C3%ADlica_de_Notre-Dame%2C_Montreal%2C_Canad%C3%A1%2C_2017-08-12%2C_DD_22-24_HDR.jpg/1920px-Bas%C3%ADlica_de_Notre-Dame%2C_Montreal%2C_Canad%C3%A1%2C_2017-08-12%2C_DD_22-24_HDR.jpg')"></div>
371 + <span class="tag">JOUR 1</span>
372 + <span class="drive">🚗 250 km · ~2 h 45</span>
373 + </div>
374 + <div class="day-body">
375 + <div class="date">Lundi 17 août 2026</div>
376 + <h3>Québec → Montréal</h3>
377 + <ul class="timeline">
378 + <li><span class="t">9 h 30</span><span class="d">Départ de Québec (A-20 ou A-40, au choix)</span></li>
379 + <li><span class="t">11 h 00</span><span class="d">Pause à <strong>Trois-Rivières</strong> — café et marche sur le vieux port</span></li>
380 + <li><span class="t">13 h 00</span><span class="d">Arrivée à Montréal, dîner dans le <strong>Vieux-Montréal</strong></span></li>
381 + <li><span class="t">14 h 30</span><span class="d"><strong>Basilique Notre-Dame</strong>, place Jacques-Cartier, rue Saint-Paul</span></li>
382 + <li><span class="t">18 h 00</span><span class="d">Montée au <strong>belvédère du Mont-Royal</strong> pour le coucher de soleil</span></li>
383 + <li><span class="t">20 h 00</span><span class="d">Souper sur une terrasse du Plateau ou dans le Mile End</span></li>
384 + </ul>
385 + <div class="chips">
386 + <span class="chip hotel">🛏️ Nuit : Montréal (Vieux-Port ou Plateau)</span>
387 + <span class="chip">🍽️ Olive &amp; Gourmando · Foiegwa · Schwartz's</span>
388 + </div>
389 + <a class="maplink" href="https://www.google.com/maps/dir/Qu%C3%A9bec,+QC/Trois-Rivi%C3%A8res,+QC/Montr%C3%A9al,+QC" target="_blank">🗺️ Ouvrir le trajet dans Google Maps →</a>
390 + </div>
391 + </div>
392 +
393 + <!-- JOUR 2 -->
394 + <div class="day reveal" id="j2">
395 + <div class="day-photo">
396 + <div class="img" style="background-image:url('https://upload.wikimedia.org/wikipedia/commons/thumb/b/b2/Centre_Block_-_Parliament_Hill.jpg/1920px-Centre_Block_-_Parliament_Hill.jpg')"></div>
397 + <span class="tag">JOUR 2</span>
398 + <span class="drive">🚗 200 km · ~2 h</span>
399 + </div>
400 + <div class="day-body">
401 + <div class="date">Mardi 18 août 2026</div>
402 + <h3>Montréal → Ottawa</h3>
403 + <ul class="timeline">
404 + <li><span class="t">9 h 00</span><span class="d">Déjeuner : bagels <strong>St-Viateur</strong> ou marché <strong>Jean-Talon</strong></span></li>
405 + <li><span class="t">10 h 30</span><span class="d">Départ vers Ottawa (A-40 → ON-417)</span></li>
406 + <li><span class="t">12 h 30</span><span class="d">Dîner au <strong>marché By</strong> (ByWard Market) — queue de castor obligatoire 🦫</span></li>
407 + <li><span class="t">14 h 00</span><span class="d"><strong>Colline du Parlement</strong> — édifices, flamme du centenaire, vue sur la rivière</span></li>
408 + <li><span class="t">15 h 30</span><span class="d">Au choix : <strong>Galerie nationale du Canada</strong> ou <strong>Musée canadien de l'histoire</strong> (côté Gatineau, superbe vue)</span></li>
409 + <li><span class="t">18 h 30</span><span class="d">Marche le long du <strong>canal Rideau</strong> (UNESCO), souper au centre-ville</span></li>
410 + </ul>
411 + <div class="chips">
412 + <span class="chip hotel">🛏️ Nuit : Ottawa (centre-ville / ByWard)</span>
413 + <span class="chip">🍽️ Play Food &amp; Wine · El Camino</span>
414 + </div>
415 + <a class="maplink" href="https://www.google.com/maps/dir/Montr%C3%A9al,+QC/Ottawa,+ON" target="_blank">🗺️ Ouvrir le trajet dans Google Maps →</a>
416 + </div>
417 + </div>
418 +
419 + <!-- JOUR 3 -->
420 + <div class="day reveal" id="j3">
421 + <div class="day-photo">
422 + <div class="img" style="background-image:url('https://upload.wikimedia.org/wikipedia/commons/thumb/6/6c/1000_Islands_Tower_view_July_2015_panorama_5.jpg/1920px-1000_Islands_Tower_view_July_2015_panorama_5.jpg')"></div>
423 + <span class="tag">JOUR 3</span>
424 + <span class="drive">🚗 460 km · ~5 h</span>
425 + </div>
426 + <div class="day-body">
427 + <div class="date">Mercredi 19 août 2026</div>
428 + <h3>Ottawa → Mille-Îles → Toronto</h3>
429 + <ul class="timeline">
430 + <li><span class="t">8 h 30</span><span class="d">Départ d'Ottawa vers <strong>Gananoque</strong> (~1 h 45)</span></li>
431 + <li><span class="t">10 h 30</span><span class="d"><strong>Croisière des Mille-Îles</strong> (1 h, City Cruises Gananoque) — châteaux, îlots, chenaux 🏝️ <em>Réserver en ligne!</em></span></li>
432 + <li><span class="t">12 h 00</span><span class="d">Dîner à <strong>Kingston</strong> — centre historique, hôtel de ville, front de lac</span></li>
433 + <li><span class="t">14 h 00</span><span class="d">Route vers Toronto par la 401 (~2 h 45)</span></li>
434 + <li><span class="t">17 h 30</span><span class="d">Check-in à l'hôtel (4 nuits, même hôtel — Entertainment District idéalement)</span></li>
435 + <li><span class="t">19 h 30</span><span class="d">Souper détendu sur <strong>King West</strong> et repérage du Rogers Centre à pied</span></li>
436 + </ul>
437 + <div class="chips">
438 + <span class="chip hotel">🛏️ Nuits 3-4-5-6 : Toronto (Entertainment District)</span>
439 + <span class="chip">💡 À pied du stade : Le Germain · Delta · Marriott City Centre</span>
440 + </div>
441 + <div class="tip">⚠️ Réserve l'hôtel <strong>dès maintenant</strong> : Ed Sheeran joue 3 soirs (20-21-22 août) + saison des Blue Jays = centre-ville plein et cher.</div>
442 + <a class="maplink" href="https://www.google.com/maps/dir/Ottawa,+ON/Gananoque,+ON/Kingston,+ON/Toronto,+ON" target="_blank">🗺️ Ouvrir le trajet dans Google Maps →</a>
443 + </div>
444 + </div>
445 +
446 + <!-- JOUR 4 — CONCERT -->
447 + <div class="day concert reveal" id="j4">
448 + <div class="concert-banner">⭐ Le grand soir — Ed Sheeran LOOP Tour <span class="eq"><i></i><i></i><i></i><i></i></span></div>
449 + <div class="day-photo">
450 + <div class="img" style="background-image:url('https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/Toronto_-_ON_-_Rogers_Centre_%28Nacht%29.jpg/1920px-Toronto_-_ON_-_Rogers_Centre_%28Nacht%29.jpg')"></div>
451 + <span class="tag">JOUR 4 · CONCERT 🎤</span>
452 + </div>
453 + <div class="day-body">
454 + <div class="date">Jeudi 20 août 2026</div>
455 + <h3>Toronto + Ed Sheeran au Rogers Centre</h3>
456 + <div class="concert-grid">
457 + <div class="concert-fact"><div class="k">Lieu</div><div class="v">Rogers Centre<br>1 Blue Jays Way</div></div>
458 + <div class="concert-fact"><div class="k">Portes / Show</div><div class="v">16 h 30 / 17 h 30</div></div>
459 + <div class="concert-fact"><div class="k">Premières parties</div><div class="v">Myles Smith<br>Lukas Graham</div></div>
460 + </div>
461 + <ul class="timeline" style="margin-top:22px">
462 + <li><span class="t">9 h 30</span><span class="d">Brunch tranquille, puis <strong>CN Tower</strong> (juste à côté du stade) — réserver un créneau en ligne</span></li>
463 + <li><span class="t">12 h 00</span><span class="d"><strong>Aquarium Ripley's</strong> ou promenade sur le <strong>Harbourfront</strong></span></li>
464 + <li><span class="t">15 h 00</span><span class="d">Souper TÔT près du stade (King West / Front St) — les restos débordent les soirs de show</span></li>
465 + <li><span class="t">16 h 30</span><span class="d"><strong>Ouverture des portes</strong> au Rogers Centre — arriver tôt, sécurité + fouille</span></li>
466 + <li><span class="t">17 h 30</span><span class="d">🎶 Début du spectacle — premières parties puis <strong>~2 h 30 de Ed Sheeran</strong> (hits + album <em>Play</em> + loop pedal)</span></li>
467 + <li><span class="t">22 h 30</span><span class="d">Retour à pied à l'hôtel — aucun stress de stationnement 🙌</span></li>
468 + </ul>
469 + <div class="chips">
470 + <span class="chip hotel">🛏️ Nuit : Toronto</span>
471 + <span class="chip">🎟️ Ticketmaster.ca — app mobile requise (billets électroniques)</span>
472 + </div>
473 + <div class="tip">💡 Laisse l'auto au stationnement de l'hôtel toute la journée. Batterie de téléphone pleine pour le billet électronique + petite veste (le toit peut être ouvert en soirée).</div>
474 + </div>
475 + </div>
476 +
477 + <!-- JOUR 5 -->
478 + <div class="day reveal" id="j5">
479 + <div class="day-photo">
480 + <div class="img" style="background-image:url('https://upload.wikimedia.org/wikipedia/commons/thumb/e/e6/Maid_of_the_Mist_VII_approaching_the_Horseshoe_Falls%2C_West_view_20170418_1.jpg/1920px-Maid_of_the_Mist_VII_approaching_the_Horseshoe_Falls%2C_West_view_20170418_1.jpg')"></div>
481 + <span class="tag">JOUR 5</span>
482 + <span class="drive">🚗 260 km A/R · ~3 h</span>
483 + </div>
484 + <div class="day-body">
485 + <div class="date">Vendredi 21 août 2026</div>
486 + <h3>Excursion — Chutes Niagara &amp; Niagara-on-the-Lake</h3>
487 + <ul class="timeline">
488 + <li><span class="t">8 h 30</span><span class="d">Départ de Toronto par la QEW (~1 h 30 sans trafic)</span></li>
489 + <li><span class="t">10 h 30</span><span class="d"><strong>Croisière <em>Voyage to the Falls</em></strong> (Niagara City Cruises) — au pied des chutes, poncho fourni 🌊 <em>Réserver en ligne!</em></span></li>
490 + <li><span class="t">12 h 00</span><span class="d"><strong>Journey Behind the Falls</strong> + terrasse Table Rock, dîner avec vue</span></li>
491 + <li><span class="t">15 h 00</span><span class="d">Route panoramique <strong>Niagara Parkway</strong> vers <strong>Niagara-on-the-Lake</strong> — village adorable, crème glacée, boutiques</span></li>
492 + <li><span class="t">16 h 30</span><span class="d">Dégustation dans un vignoble (Inniskillin, Peller Estates) — <strong>vin de glace</strong> 🍷 (conducteur désigné!)</span></li>
493 + <li><span class="t">18 h 30</span><span class="d">Retour vers Toronto, souper en chemin ou en ville</span></li>
494 + </ul>
495 + <div class="chips">
496 + <span class="chip hotel">🛏️ Nuit : Toronto</span>
497 + <span class="chip">📸 Meilleure lumière sur les chutes : le matin (soleil derrière toi côté canadien)</span>
498 + </div>
499 + <a class="maplink" href="https://www.google.com/maps/dir/Toronto,+ON/Niagara+Falls,+ON/Niagara-on-the-Lake,+ON/Toronto,+ON" target="_blank">🗺️ Ouvrir le trajet dans Google Maps →</a>
500 + </div>
501 + </div>
502 +
503 + <!-- JOUR 6 -->
504 + <div class="day reveal" id="j6">
505 + <div class="day-photo">
506 + <div class="img" style="background-image:url('https://upload.wikimedia.org/wikipedia/commons/thumb/7/7f/Distillery_District%2C_Trinity_Street_looking_north%2C_2026.jpg/1920px-Distillery_District%2C_Trinity_Street_looking_north%2C_2026.jpg')"></div>
507 + <span class="tag">JOUR 6</span>
508 + <span class="drive">🚶 Journée à pied + traversier</span>
509 + </div>
510 + <div class="day-body">
511 + <div class="date">Samedi 22 août 2026</div>
512 + <h3>Toronto à fond</h3>
513 + <ul class="timeline">
514 + <li><span class="t">9 h 00</span><span class="d">Traversier au quai Jack Layton vers les <strong>Toronto Islands</strong> — LA vue iconique sur la skyline 🌆</span></li>
515 + <li><span class="t">10 h 00</span><span class="d">Vélo ou marche sur <strong>Centre Island</strong> et <strong>Ward's Island</strong>, plage si le cœur t'en dit</span></li>
516 + <li><span class="t">13 h 00</span><span class="d">Retour en ville, dîner au <strong>St. Lawrence Market</strong> — sandwich au peameal bacon chez Carousel Bakery</span></li>
517 + <li><span class="t">15 h 00</span><span class="d"><strong>Distillery District</strong> — ruelles pavées, galeries, cafés, microbrasseries</span></li>
518 + <li><span class="t">17 h 30</span><span class="d"><strong>Kensington Market</strong> &amp; Chinatown pour l'ambiance funky (ou Queen West pour le magasinage)</span></li>
519 + <li><span class="t">20 h 00</span><span class="d">Souper + rooftop sur King West, ou match des Blue Jays s'ils sont en ville ⚾</span></li>
520 + </ul>
521 + <div class="chips">
522 + <span class="chip hotel">🛏️ Nuit : Toronto</span>
523 + <span class="chip">🚋 Tramways TTC + marche — pas besoin de l'auto aujourd'hui</span>
524 + </div>
525 + </div>
526 + </div>
527 +
528 + <!-- JOUR 7 -->
529 + <div class="day reveal" id="j7">
530 + <div class="day-photo">
531 + <div class="img" style="background-image:url('https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Sandbanks_Provincial_Park_Ontario_Jul_2020.jpg/1920px-Sandbanks_Provincial_Park_Ontario_Jul_2020.jpg')"></div>
532 + <span class="tag">JOUR 7</span>
533 + <span class="drive">🚗 540 km · ~5 h 30</span>
534 + </div>
535 + <div class="day-body">
536 + <div class="date">Dimanche 23 août 2026</div>
537 + <h3>Toronto → Montréal (retour, étape 1)</h3>
538 + <ul class="timeline">
539 + <li><span class="t">9 h 00</span><span class="d">Brunch d'adieu à Toronto, derniers achats</span></li>
540 + <li><span class="t">11 h 00</span><span class="d">Départ sur la 401 Est</span></li>
541 + <li><span class="t">13 h 00</span><span class="d"><em>Option plage</em> : détour au parc <strong>Sandbanks</strong> (Prince Edward County, +45 min) — dunes et plages magnifiques 🏖️ <em>ou</em> simple pause à <strong>Kingston</strong></span></li>
542 + <li><span class="t">18 h 30</span><span class="d">Arrivée à Montréal, check-in</span></li>
543 + <li><span class="t">20 h 00</span><span class="d">Dernière soirée de vacances — souper dans le Vieux-Montréal 🥂</span></li>
544 + </ul>
545 + <div class="chips">
546 + <span class="chip hotel">🛏️ Nuit : Montréal</span>
547 + <span class="chip">💡 Sandbanks : réserver le stationnement journalier sur ontarioparks.ca</span>
548 + </div>
549 + <a class="maplink" href="https://www.google.com/maps/dir/Toronto,+ON/Kingston,+ON/Montr%C3%A9al,+QC" target="_blank">🗺️ Ouvrir le trajet dans Google Maps →</a>
550 + </div>
551 + </div>
552 +
553 + <!-- JOUR 8 -->
554 + <div class="day reveal" id="j8">
555 + <div class="day-photo">
556 + <div class="img" style="background-image:url('https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/Ch%C3%A2teau_Frontenac_in_Quebec_city%2C_Canada_colorfull.jpg/1920px-Ch%C3%A2teau_Frontenac_in_Quebec_city%2C_Canada_colorfull.jpg')"></div>
557 + <span class="tag">JOUR 8</span>
558 + <span class="drive">🚗 250 km · ~2 h 45</span>
559 + </div>
560 + <div class="day-body">
561 + <div class="date">Lundi 24 août 2026</div>
562 + <h3>Montréal → Québec — retour à la maison</h3>
563 + <ul class="timeline">
564 + <li><span class="t">10 h 00</span><span class="d">Déjeuner tranquille, départ en fin d'avant-midi</span></li>
565 + <li><span class="t">13 h 00</span><span class="d">Arrivée à Québec, reposé et la tête pleine de souvenirs ✅</span></li>
566 + </ul>
567 + <div class="chips">
568 + <span class="chip">🎶 Playlist du retour : <em>Play</em>, <em>÷</em>, <em>×</em> et <em>+</em> — évidemment</span>
569 + </div>
570 + <a class="maplink" href="https://www.google.com/maps/dir/Montr%C3%A9al,+QC/Qu%C3%A9bec,+QC" target="_blank">🗺️ Ouvrir le trajet dans Google Maps →</a>
571 + </div>
572 + </div>
573 +
574 + <!-- ===== INFOS PRATIQUES ===== -->
575 + <h2 class="section reveal" id="infos">🧳 Infos pratiques</h2>
576 +
577 + <div class="info-grid">
578 + <div class="info-card reveal">
579 + <h3>✅ À réserver d'avance (par priorité)</h3>
580 + <ul>
581 + <li><strong>Hôtel Toronto (4 nuits, 19-23 août)</strong> — le plus urgent, 3 soirs de Ed Sheeran cette fin de semaine-là</li>
582 + <li><strong>Billets du concert</strong> — Ticketmaster.ca, dans l'app mobile</li>
583 + <li><strong>Croisière Niagara</strong> (niagaracruises.com) — créneau du matin</li>
584 + <li><strong>Croisière Mille-Îles</strong> (cityexperiences.com, Gananoque)</li>
585 + <li><strong>CN Tower</strong> — billet horodaté en ligne (évite 1 h de file)</li>
586 + <li>Hôtels Montréal (2 nuits) et Ottawa (1 nuit)</li>
587 + </ul>
588 + </div>
589 +
590 + <div class="info-card reveal">
591 + <h3>💰 Budget estimé (2 personnes)</h3>
592 + <table class="budget">
593 + <tr><td>Hôtels (7 nuits, moy. ~250 $)</td><td>1 750 $</td></tr>
594 + <tr><td>Essence (~1 900 km)</td><td>250 $</td></tr>
595 + <tr><td>Stationnements (Toronto ~40 $/jr)</td><td>200 $</td></tr>
596 + <tr><td>Repas (~120 $/jour)</td><td>950 $</td></tr>
597 + <tr><td>Activités (croisières, CN Tower…)</td><td>350 $</td></tr>
598 + <tr><td>Billets de concert (variable)</td><td>—</td></tr>
599 + <tr class="total"><td>Total (hors billets)</td><td>≈ 3 500 $</td></tr>
600 + </table>
601 + </div>
602 +
603 + <div class="info-card reveal">
604 + <h3>🚗 Conduite &amp; stationnement</h3>
605 + <ul>
606 + <li>Éviter d'arriver à Toronto entre 15 h et 19 h (la 401/Gardiner = 🐌)</li>
607 + <li>Le soir du concert : <strong>ne pas bouger l'auto</strong> — tout se fait à pied du centre-ville</li>
608 + <li>Péage optionnel 407 ETR : rapide mais cher (~40-60 $) — la 401 suffit</li>
609 + <li>Green P (app) pour les stationnements municipaux à Toronto</li>
610 + <li>Essence moins chère en périphérie qu'au centre-ville</li>
611 + </ul>
612 + </div>
613 +
614 + <div class="info-card reveal">
615 + <h3>🎒 À ne pas oublier</h3>
616 + <ul>
617 + <li>Téléphone chargé + batterie externe (billets électroniques au stade)</li>
618 + <li>Petit sac transparent ou minimal — politique de sacs stricte au Rogers Centre</li>
619 + <li>Veste légère pour le soir (toit du stade parfois ouvert)</li>
620 + <li>Maillot + serviette (Toronto Islands, Sandbanks)</li>
621 + <li>Bouteilles d'eau réutilisables pour la route</li>
622 + <li>Permis de conduire + preuve d'assurance auto</li>
623 + </ul>
624 + </div>
625 + </div>
626 +
627 + <footer>
628 + Concert vérifié : Ed Sheeran LOOP Tour, Rogers Centre, jeudi 20 août 2026, 17 h 30 (Ticketmaster.ca / Live Nation)<br>
629 + Photos : <a href="https://commons.wikimedia.org" target="_blank">Wikimedia Commons</a> (licences libres)
630 + <div class="footer-heart">Le road trip d'<span>Erika ♥ SP</span> — été 2026</div>
631 + </footer>
632 +
633 +</div>
634 +
635 +<script>
636 + // Countdown to showtime (17:30 EDT, Aug 20 2026)
637 + const SHOW = new Date("2026-08-20T17:30:00-04:00").getTime();
638 + const pad = n => String(n).padStart(2, "0");
639 + function tick() {
640 + let ms = SHOW - Date.now();
641 + const cd = document.getElementById("countdown");
642 + if (ms <= 0) {
643 + cd.querySelector(".cd-label").textContent = "🎶 C'est ce soir (ou c'est déjà passé)!";
644 + ms = 0;
645 + }
646 + const d = Math.floor(ms / 864e5), h = Math.floor(ms % 864e5 / 36e5),
647 + m = Math.floor(ms % 36e5 / 6e4), s = Math.floor(ms % 6e4 / 1e3);
648 + document.getElementById("cd-d").textContent = d;
649 + document.getElementById("cd-h").textContent = pad(h);
650 + document.getElementById("cd-m").textContent = pad(m);
651 + document.getElementById("cd-s").textContent = pad(s);
652 + }
653 + tick(); setInterval(tick, 1000);
654 +
655 + // Scroll reveal
656 + const io = new IntersectionObserver(entries => {
657 + for (const e of entries) if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); }
658 + }, { threshold: 0.08 });
659 + document.querySelectorAll(".reveal").forEach(el => io.observe(el));
660 +</script>
661 +</body>
662 +</html>
added server.mjs +52 −0
@@ -0,0 +1,52 @@
1 +// Zero-dep static server for the voyage2026 itinerary.
2 +// Usage: node server.mjs [port] — serves this directory, / -> index, /healthz -> ok
3 +import { createServer } from "node:http";
4 +import { readFile } from "node:fs/promises";
5 +import { join, extname, normalize } from "node:path";
6 +import { fileURLToPath } from "node:url";
7 +
8 +const ROOT = fileURLToPath(new URL(".", import.meta.url));
9 +const PORT = Number(process.argv[2] || process.env.PORT || 8060);
10 +const INDEX = "itineraire-toronto-2026.html";
11 +
12 +const MIME = {
13 + ".html": "text/html; charset=utf-8",
14 + ".css": "text/css; charset=utf-8",
15 + ".js": "text/javascript; charset=utf-8",
16 + ".json": "application/json",
17 + ".png": "image/png",
18 + ".jpg": "image/jpeg",
19 + ".jpeg": "image/jpeg",
20 + ".webp": "image/webp",
21 + ".svg": "image/svg+xml",
22 + ".ico": "image/x-icon",
23 + ".pdf": "application/pdf",
24 +};
25 +
26 +createServer(async (req, res) => {
27 + const url = new URL(req.url, "http://x");
28 + if (url.pathname === "/healthz") {
29 + res.writeHead(200, { "content-type": "text/plain" });
30 + return res.end("ok");
31 + }
32 + let path = decodeURIComponent(url.pathname);
33 + if (path === "/" || path === "/index.html") path = "/" + INDEX;
34 + const file = normalize(join(ROOT, path));
35 + if (!file.startsWith(ROOT)) {
36 + res.writeHead(403);
37 + return res.end("forbidden");
38 + }
39 + try {
40 + const body = await readFile(file);
41 + res.writeHead(200, {
42 + "content-type": MIME[extname(file).toLowerCase()] || "application/octet-stream",
43 + "cache-control": "public, max-age=300",
44 + });
45 + res.end(body);
46 + } catch {
47 + res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
48 + res.end("404 — page introuvable");
49 + }
50 +}).listen(PORT, "0.0.0.0", () => {
51 + console.log(`voyage2026 static server on http://0.0.0.0:${PORT} (root: ${ROOT})`);
52 +});
53