2222 position : absolute;
2323 top : 12px ;
2424 right : 12px ;
25- background : rgba (20 , 20 , 30 , 0.85 );
25+ background : rgba (20 , 20 , 30 , 0.65 );
2626 color : # e5e7eb ;
2727 padding : 14px 18px ;
2828 border-radius : 8px ;
172172 # coverage-view-toggle button : hover : not (.active ) {
173173 background : rgba (255 , 255 , 255 , 0.05 );
174174 }
175+ # legend {
176+ margin-top : 12px ;
177+ padding-top : 10px ;
178+ border-top : 1px solid rgba (255 , 255 , 255 , 0.1 );
179+ }
180+ # legend-header {
181+ display : flex;
182+ align-items : center;
183+ justify-content : space-between;
184+ margin-bottom : 6px ;
185+ }
186+ # legend-header .legend-title {
187+ margin-bottom : 0 ;
188+ }
189+ # legend-toggle {
190+ background : none;
191+ border : none;
192+ color : # 9ca3af ;
193+ cursor : pointer;
194+ padding : 0 ;
195+ font-size : 0.75rem ;
196+ line-height : 1 ;
197+ transition : color 0.15s , transform 0.2s ;
198+ display : flex;
199+ align-items : center;
200+ }
201+ # legend-toggle : hover {
202+ color : # e5e7eb ;
203+ }
204+ # legend-toggle .collapsed {
205+ transform : rotate (-180deg );
206+ }
207+ # legend-body {
208+ overflow : hidden;
209+ transition : opacity 0.2s ;
210+ }
211+ # legend-body .collapsed {
212+ display : none;
213+ }
214+ .legend-title {
215+ font-size : 0.7rem ;
216+ text-transform : uppercase;
217+ letter-spacing : 0.05em ;
218+ color : # 9ca3af ;
219+ margin-bottom : 6px ;
220+ }
221+ .legend-row {
222+ display : flex;
223+ align-items : center;
224+ gap : 7px ;
225+ font-size : 0.78rem ;
226+ color : # e5e7eb ;
227+ margin-bottom : 4px ;
228+ }
229+ .legend-swatch {
230+ width : 12px ;
231+ height : 12px ;
232+ border-radius : 50% ;
233+ flex-shrink : 0 ;
234+ }
235+ .legend-gradient {
236+ height : 10px ;
237+ border-radius : 4px ;
238+ background : linear-gradient (to right,
239+ # 2563eb, # 60a5fa, # fbbf24, # f97316, # ef4444 );
240+ margin-bottom : 3px ;
241+ }
242+ .legend-gradient-labels {
243+ display : flex;
244+ justify-content : space-between;
245+ font-size : 0.72rem ;
246+ color : # 9ca3af ;
247+ }
175248 </ style >
176249</ head >
177250< body >
@@ -203,6 +276,26 @@ <h3>Where the Plow</h3>
203276 < input type ="range " id ="time-slider " min ="0 " max ="1000 " value ="1000 " />
204277 < div id ="coverage-loading "> Loading coverage data...</ div >
205278 </ div >
279+ < div id ="legend ">
280+ < div id ="legend-header ">
281+ < div class ="legend-title "> Legend</ div >
282+ < button id ="legend-toggle " title ="Collapse legend "> ▲</ button >
283+ </ div >
284+ < div id ="legend-body ">
285+ < div id ="legend-vehicles ">
286+ < div class ="legend-row "> < span class ="legend-swatch " style ="background:#2563eb "> </ span > Plow truck</ div >
287+ < div class ="legend-row "> < span class ="legend-swatch " style ="background:#ea580c "> </ span > Loader</ div >
288+ < div class ="legend-row "> < span class ="legend-swatch " style ="background:#16a34a "> </ span > Grader</ div >
289+ < div class ="legend-row "> < span class ="legend-swatch " style ="background:#6b7280 "> </ span > Other</ div >
290+ </ div >
291+ < div id ="legend-heatmap " style ="display:none ">
292+ < div class ="legend-gradient "> </ div >
293+ < div class ="legend-gradient-labels ">
294+ < span > Low</ span > < span > High</ span >
295+ </ div >
296+ </ div >
297+ </ div >
298+ </ div >
206299 < div id ="panel-footer ">
207300 Data from < a href ="https://map.stjohns.ca/avl/ " target ="_blank " rel ="noopener "> City of St. John's AVL</ a > < br >
208301 < a href ="https://github.com/jackharrhy/where-the-plow " target ="_blank " rel ="noopener "> View on GitHub</ a >
@@ -302,11 +395,24 @@ <h3>Where the Plow</h3>
302395 btnLines . addEventListener ( 'click' , ( ) => switchCoverageView ( 'lines' ) ) ;
303396 btnHeatmap . addEventListener ( 'click' , ( ) => switchCoverageView ( 'heatmap' ) ) ;
304397
398+ function showLegend ( type ) {
399+ document . getElementById ( 'legend-vehicles' ) . style . display = type === 'vehicles' ? '' : 'none' ;
400+ document . getElementById ( 'legend-heatmap' ) . style . display = type === 'heatmap' ? '' : 'none' ;
401+ }
402+
403+ const legendToggleBtn = document . getElementById ( 'legend-toggle' ) ;
404+ const legendBody = document . getElementById ( 'legend-body' ) ;
405+ legendToggleBtn . addEventListener ( 'click' , ( ) => {
406+ const collapsed = legendBody . classList . toggle ( 'collapsed' ) ;
407+ legendToggleBtn . classList . toggle ( 'collapsed' , collapsed ) ;
408+ } ) ;
409+
305410 function switchCoverageView ( view ) {
306411 if ( view === coverageView ) return ;
307412 coverageView = view ;
308413 btnLines . classList . toggle ( 'active' , view === 'lines' ) ;
309414 btnHeatmap . classList . toggle ( 'active' , view === 'heatmap' ) ;
415+ showLegend ( view === 'heatmap' ? 'heatmap' : 'vehicles' ) ;
310416 renderCoverage ( parseInt ( timeSlider . value ) ) ;
311417 }
312418
@@ -511,6 +617,7 @@ <h3>Where the Plow</h3>
511617 }
512618 document . getElementById ( 'vehicle-count' ) . style . display = '' ;
513619 document . getElementById ( 'db-size' ) . style . display = '' ;
620+ showLegend ( 'vehicles' ) ;
514621 startAutoRefresh ( ) ;
515622 }
516623
@@ -520,6 +627,7 @@ <h3>Where the Plow</h3>
520627 coverageView = 'lines' ;
521628 btnLines . classList . add ( 'active' ) ;
522629 btnHeatmap . classList . remove ( 'active' ) ;
630+ showLegend ( 'vehicles' ) ;
523631 if ( map . getLayer ( 'vehicle-circles' ) ) {
524632 map . setLayoutProperty ( 'vehicle-circles' , 'visibility' , 'none' ) ;
525633 }
0 commit comments