-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathindex.html
More file actions
626 lines (621 loc) · 23.4 KB
/
index.html
File metadata and controls
626 lines (621 loc) · 23.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
/>
<title>Plan Your GR Visit</title>
<meta
name="description"
content="Plan downtown trips, parking, and DASH shuttles."
/>
<meta name="application-name" content="Plan Your Grand Rapids Visit" />
<meta
name="theme-color"
content="#0249aa"
media="(prefers-color-scheme: light)"
/>
<meta
name="theme-color"
content="#013078"
media="(prefers-color-scheme: dark)"
/>
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Plan GR" />
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
<meta name="msapplication-TileColor" content="#0249aa" />
<meta name="color-scheme" content="light" />
<meta property="og:title" content="Plan Your Grand Rapids Visit" />
<meta property="og:description" content="End the parking spiral." />
<meta property="og:type" content="website" />
<link rel="manifest" href="images/manifest.webmanifest" />
<!-- Home-screen icons derived from Wikimedia Commons Logo_of_Grand_Rapids,_Michigan.svg (CC BY-SA 4.0). -->
<link
rel="icon"
type="image/svg+xml"
href="images/grand-rapids-city-logo.svg"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="images/favicon-32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="images/favicon-16.png"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="images/apple-touch-icon.png"
/>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
crossorigin=""
/>
<link rel="stylesheet" href="src/styles.css" />
<link rel="stylesheet" href="src/visit/visit.css" />
<script type="text/javascript">
(function () {
var host = window.location.hostname;
var isLocal =
host === "localhost" ||
host === "127.0.0.1" ||
host === "[::1]" ||
host.endsWith(".localhost");
if (isLocal || navigator.webdriver) {
return;
}
(function (c, l, a, r, i, t, y) {
c[a] =
c[a] ||
function () {
(c[a].q = c[a].q || []).push(arguments);
};
t = l.createElement(r);
t.async = 1;
t.src = "https://www.clarity.ms/tag/" + i;
y = l.getElementsByTagName(r)[0];
y.parentNode.insertBefore(t, y);
})(window, document, "clarity", "script", "wwtq25g4z0");
})();
</script>
</head>
<body class="bg-slate-50 text-slate-900 flex flex-col min-h-screen">
<main
class="max-w-md mx-auto px-2 py-2 flex-1 w-full flex flex-col min-h-0"
>
<div id="appView"></div>
<div id="modesView" class="hidden">
<div class="bg-white rounded-xl shadow-sm border border-slate-200 p-4">
<div class="flex flex-wrap items-start justify-between gap-2 mb-3">
<h2 class="font-semibold text-lg">How each mode works</h2>
<a
id="modesPageBackLink"
href="#/visit"
class="text-sm text-blue-600 hover:underline shrink-0"
>← Parking map</a
>
</div>
<p class="text-sm text-slate-600 mb-4">
These are the travel options you can combine on the visit page.
Where we have location data, a map shows how the app uses it around
Grand Rapids.
</p>
<div
id="modesPageSections"
class="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3"
></div>
</div>
</div>
<div id="dataView" class="hidden">
<div class="bg-white rounded-xl shadow-sm border border-slate-200 p-4">
<nav
id="dataViewTabs"
class="data-view-tabs hidden"
role="tablist"
aria-label="Data explorer"
></nav>
<div
id="dataViewParkingModes"
class="data-parking-toolbar hidden mt-2 mb-3"
></div>
<div
id="dataViewDestinationsBar"
class="data-destinations-toolbar hidden mt-2 mb-3"
></div>
<div
id="dataViewRoutesModes"
class="data-routes-toolbar hidden mt-2 mb-3"
></div>
<div
id="dataViewMap"
class="hidden mt-2 mb-2 rounded-lg border border-slate-200 overflow-hidden"
style="height: calc(100vh - 10rem)"
></div>
<div id="dataViewDetail" class="hidden">
<h2
id="dataViewDetailTitle"
class="font-medium text-base mt-2 mb-1"
></h2>
<pre
id="dataViewContent"
class="overflow-auto bg-slate-50 p-3 rounded-lg border border-slate-200 whitespace-pre-wrap break-words"
></pre>
</div>
</div>
</div>
<div
id="parkingView"
class="hidden flex flex-1 flex-col min-h-0 min-w-0 w-full"
aria-label="Parking map"
>
<div
id="parkingMapChrome"
class="hidden shrink-0 mb-2 flex flex-col gap-3 px-0 md:flex-row md:flex-nowrap md:items-center md:justify-center md:gap-x-5 md:px-5 lg:gap-x-10 xl:gap-x-12 lg:px-6"
>
<div
class="group relative w-full max-w-2xl min-w-0 shrink-0 md:w-auto md:min-w-[18rem] md:grow-0"
>
<select
id="parkingDestinationSelect"
class="w-full min-w-0 cursor-pointer appearance-none rounded-lg border border-slate-300 bg-white py-2 pl-3 pr-10 text-sm text-slate-900 shadow-sm transition-colors transition-shadow hover:border-slate-400 hover:bg-slate-50 hover:shadow focus:outline-none focus:ring-0 focus-visible:ring-2 focus-visible:ring-slate-400 sm:pl-4"
aria-label="Where are you going?"
>
<option value="" disabled hidden selected>
Where are you going?
</option>
</select>
<span
id="parkingDestChevron"
class="pointer-events-none absolute inset-y-px right-px flex w-9 items-center justify-center rounded-r-[0.4375rem] border-l border-slate-200 bg-slate-50 text-slate-600 transition-colors group-hover:bg-slate-100 group-hover:text-slate-700"
aria-hidden="true"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-4 shrink-0"
>
<path d="M6 8l4 4 4-4" />
</svg>
</span>
<button
type="button"
id="parkingResetBtn"
class="absolute inset-y-px right-px z-10 hidden flex w-9 items-center justify-center rounded-r-[0.4375rem] border-l border-slate-300/90 bg-slate-100 text-slate-500 shadow-sm hover:bg-slate-200 hover:text-slate-700 focus:outline-none focus:ring-2 focus:ring-slate-400"
aria-label="Reset destination and filters"
title="Reset destination and filters"
>
<svg
class="size-4 shrink-0"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M18 6L6 18" />
<path d="M6 6l12 12" />
</svg>
</button>
</div>
<div
class="flex min-w-0 w-full max-w-full flex-col items-stretch gap-4 md:w-auto md:max-w-max"
>
<div
id="parkingEveningBudgetWrap"
class="flex min-w-0 w-full items-center gap-2"
>
<label
for="parkingMaxEveningSlider"
class="shrink-0 text-xs font-medium text-slate-600"
>Willing to pay</label
>
<input
id="parkingMaxEveningSlider"
type="range"
min="0"
max="50"
step="5"
value="40"
class="min-w-0 flex-1 accent-green-700"
aria-label="Willing to pay"
/>
<span
id="parkingMaxEveningBudgetOut"
class="w-[4.75rem] shrink-0 text-right text-sm font-semibold tabular-nums text-slate-800 sm:w-16"
>$40</span
>
</div>
<div
class="flex w-full min-w-0 flex-wrap items-start gap-x-2 gap-y-1"
role="group"
aria-labelledby="parkingFilterHeading"
>
<div class="flex shrink-0 items-center gap-1 pt-0.5">
<span
id="parkingFilterHeading"
class="text-xs font-medium leading-5 text-slate-600"
>To park in</span
>
</div>
<div
id="parkingFilterBar"
class="flex min-w-0 flex-1 basis-[min(100%,12rem)] flex-nowrap items-center justify-start gap-2 overflow-x-auto pb-0.5 md:gap-x-3"
role="toolbar"
aria-label="Parking categories"
></div>
</div>
<div
id="parkingMaxWalkWrap"
class="flex min-w-0 w-full items-center gap-2"
>
<label
for="parkingMaxWalkSlider"
class="shrink-0 text-xs font-medium text-slate-600"
>And then walk</label
>
<input
id="parkingMaxWalkSlider"
type="range"
min="0"
max="15"
step="1"
value="8"
class="min-w-0 flex-1 accent-blue-600"
aria-label="Maximum walk from parking to the nearest DASH stop"
/>
<span
id="parkingMaxWalkBudgetOut"
class="min-w-[6.5rem] shrink-0 text-right text-xs font-semibold tabular-nums leading-tight text-slate-800 sm:min-w-[7.5rem] sm:text-sm"
>0.8 mi (~19 min)</span
>
</div>
</div>
</div>
<div class="relative min-h-0 flex-1 min-w-0 w-full">
<div id="parkingAppMap" class="absolute inset-0 min-h-0 w-full"></div>
<button
type="button"
id="parkingLocateBtn"
class="parking-map-control-help parking-map-control-locate"
aria-label="Show my location on the map"
aria-pressed="false"
title="Show my location on the map"
></button>
<button
type="button"
id="parkingLegendHelpBtn"
class="parking-map-control-help"
aria-label="Show the map legend"
title="Show the map legend"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="parking-map-control-help-icon"
aria-hidden="true"
>
<circle cx="12" cy="12" r="10" />
<path d="M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3" />
<path d="M12 17h.01" />
</svg>
</button>
</div>
<section
id="parkingRouteInstructions"
class="mt-2 flex shrink-0 flex-col gap-3 rounded-lg border border-slate-200 bg-white p-3 sm:gap-4 sm:p-4"
aria-label="Steps from parking to the venue"
>
<div
id="parkingRouteInstructionsBody"
class="parking-route-instructions-body text-sm text-slate-800"
aria-live="polite"
></div>
<p
id="parkingRouteUnverifiedDataNote"
class="text-xs italic leading-snug text-slate-300"
>
This data has not been verified. Rates and availability vary.
</p>
</section>
</div>
</main>
<div
id="modesExplainModal"
class="hidden fixed inset-0 z-[5000] flex items-center justify-center overflow-y-auto bg-slate-900/50 p-2 sm:p-4"
role="dialog"
aria-modal="true"
aria-hidden="true"
aria-labelledby="modesExplainModalTitle"
>
<div
id="modesExplainModalPanel"
class="flex w-full max-w-4xl flex-col overflow-hidden rounded-xl border border-slate-200 bg-white shadow-xl"
>
<div
class="flex items-start justify-between gap-3 p-4 border-b border-slate-200 shrink-0"
>
<div class="min-w-0">
<h2 id="modesExplainModalTitle" class="font-semibold text-lg">
How each mode works
</h2>
<p class="text-sm text-slate-600 mt-1">
Where we have location data, a map shows how the app uses it
around Grand Rapids.
</p>
</div>
<button
type="button"
id="modesExplainModalClose"
class="shrink-0 rounded-lg p-2 text-slate-500 hover:text-slate-800 hover:bg-slate-100"
aria-label="Close"
>
<svg
class="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
<div
id="modesExplainModalScroll"
class="max-h-[calc(100vh-10rem)] overflow-y-auto overscroll-contain p-4"
>
<div
id="modesExplainModalSections"
class="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3"
></div>
</div>
</div>
</div>
<div
id="parkingLegendModal"
class="hidden fixed inset-0 z-[5000] flex items-center justify-center overflow-y-auto bg-slate-900/50 p-2 sm:p-4"
role="dialog"
aria-modal="true"
aria-hidden="true"
aria-labelledby="parkingLegendModalTitle"
>
<div
id="parkingLegendModalPanel"
class="flex w-full max-w-lg flex-col overflow-hidden rounded-xl border border-slate-200 bg-white shadow-xl sm:max-w-xl"
>
<div
class="flex shrink-0 items-start justify-between gap-3 border-b border-slate-200 p-4"
>
<div class="min-w-0">
<h2 id="parkingLegendModalTitle" class="font-semibold text-lg">
About Plan GR
</h2>
<p class="mt-1 text-sm text-slate-600">
This app helps you explore alternative parking strategies to plan
your visit to one of the large entertainment venues downtown.
</p>
</div>
<button
type="button"
id="parkingLegendModalClose"
class="shrink-0 rounded-lg p-2 text-slate-500 hover:bg-slate-100 hover:text-slate-800"
aria-label="Close"
>
<svg
class="h-6 w-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
<div
id="parkingLegendModalScroll"
class="max-h-[calc(100vh-10rem)] overflow-y-auto overscroll-contain p-4"
>
<div class="flex flex-col gap-5">
<section
class="parking-legend-section rounded-lg border border-slate-200 bg-slate-50/80 p-3 sm:flex sm:flex-row sm:items-stretch sm:gap-4"
aria-labelledby="parkingLegend-public-garage-title"
>
<div class="min-w-0 flex-1">
<h3
id="parkingLegend-public-garage-title"
class="font-semibold text-slate-900"
>
Public Garages
</h3>
<p class="mt-1 text-sm leading-snug text-slate-600">
These are city-managed structures with multiple levels of
parking. These have affordable rates and many spots can be
<a
href="https://grandrapidsevents.parkmobile.io/"
class="parking-route-transit-app-link"
target="_blank"
rel="noopener noreferrer"
aria-label="Reserve parking on ParkMobile"
><span class="parking-route-step-link-content"
><span class="parking-route-step-link-label"
>reserved</span
></span
></a
>
in advance.
</p>
</div>
<div
id="parkingLegendMap-public-garage"
class="parking-legend-mini-map mt-3 h-32 w-full shrink-0 overflow-hidden rounded-md border border-slate-200 bg-slate-100 sm:mt-0 sm:w-44"
role="img"
aria-label="Sample map for public garages"
></div>
</section>
<section
class="parking-legend-section rounded-lg border border-slate-200 bg-slate-50/80 p-3 sm:flex sm:flex-row sm:items-stretch sm:gap-4"
aria-labelledby="parkingLegend-public-lot-title"
>
<div class="min-w-0 flex-1">
<h3
id="parkingLegend-public-lot-title"
class="font-semibold text-slate-900"
>
Public Lots
</h3>
<p class="mt-1 text-sm leading-snug text-slate-600">
These are city-managed surface lots. They don't have as many
spots as garages but may be easier for large vehicles to
access.
</p>
</div>
<div
id="parkingLegendMap-public-lot"
class="parking-legend-mini-map mt-3 h-32 w-full shrink-0 overflow-hidden rounded-md border border-slate-200 bg-slate-100 sm:mt-0 sm:w-44"
role="img"
aria-label="Sample map for public lots"
></div>
</section>
<section
class="parking-legend-section rounded-lg border border-slate-200 bg-slate-50/80 p-3 sm:flex sm:flex-row sm:items-stretch sm:gap-4"
aria-labelledby="parkingLegend-private-garage-title"
>
<div class="min-w-0 flex-1">
<h3
id="parkingLegend-private-garage-title"
class="font-semibold text-slate-900"
>
Private Garages
</h3>
<p class="mt-1 text-sm leading-snug text-slate-600">
These are privately-owned structures with multiple levels of
parking. Costs and hours are not readily available.
</p>
</div>
<div
id="parkingLegendMap-private-garage"
class="parking-legend-mini-map mt-3 h-32 w-full shrink-0 overflow-hidden rounded-md border border-slate-200 bg-slate-100 sm:mt-0 sm:w-44"
role="img"
aria-label="Sample map for private garages"
></div>
</section>
<section
class="parking-legend-section rounded-lg border border-slate-200 bg-slate-50/80 p-3 sm:flex sm:flex-row sm:items-stretch sm:gap-4"
aria-labelledby="parkingLegend-private-lot-title"
>
<div class="min-w-0 flex-1">
<h3
id="parkingLegend-private-lot-title"
class="font-semibold text-slate-900"
>
Private Lots
</h3>
<p class="mt-1 text-sm leading-snug text-slate-600">
These are privately-owned surface lots. They often provide
self-service parking but rates are unpredictable.
</p>
</div>
<div
id="parkingLegendMap-private-lot"
class="parking-legend-mini-map mt-3 h-32 w-full shrink-0 overflow-hidden rounded-md border border-slate-200 bg-slate-100 sm:mt-0 sm:w-44"
role="img"
aria-label="Sample map for private lots"
></div>
</section>
<section
class="parking-legend-section rounded-lg border border-slate-200 bg-slate-50/80 p-3 sm:flex sm:flex-row sm:items-stretch sm:gap-4"
aria-labelledby="parkingLegend-dash-title"
>
<div class="min-w-0 flex-1">
<h3
id="parkingLegend-dash-title"
class="font-semibold text-slate-900"
>
The Downtown Area Shuttle
</h3>
<p class="mt-1 text-sm leading-snug text-slate-600">
DASH is a free shuttle that operates in a clockwise loop. It
reduces walking distances and provides access to more parking
options.
</p>
<p class="mt-2 text-xs italic leading-snug text-slate-400">
Starting two hours before amphitheater showtimes, it follows
an "Event Loop" detour for better service.
</p>
</div>
<div
id="parkingLegendMap-dash"
class="parking-legend-mini-map mt-3 h-32 w-full shrink-0 overflow-hidden rounded-md border border-slate-200 bg-slate-100 sm:mt-0 sm:w-44"
role="img"
aria-label="DASH route map"
></div>
</section>
</div>
</div>
</div>
</div>
<div id="footer" class="py-2 text-center text-xs text-slate-400 shrink-0">
<a href="https://plangr.com" class="text-slate-500 hover:text-slate-600"
><b>Plan GR</b></a
>
is an
<a
href="https://github.com/citizenlabsgr/multimodality"
target="_blank"
rel="noopener noreferrer"
class="text-slate-500 hover:text-slate-600"
><b>open source</b></a
>
project by
<a
href="https://citizenlabs.org/"
target="_blank"
rel="noopener noreferrer"
class="text-slate-500 hover:text-slate-600"
><b>Citizen Labs</b></a
>
</div>
<script
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
crossorigin=""
></script>
<script type="module" src="src/main.mjs"></script>
</body>
</html>