@@ -210,3 +210,87 @@ function downloadCsv(schedule) {
210210document . getElementById ( "goBackBtn" ) . addEventListener ( "click" , ( ) => {
211211 window . location . href = "../index.html" ;
212212} ) ;
213+
214+ // Universal Dark Mode Toggle
215+ ( function ( ) {
216+ const STORAGE_KEY = "darkMode" , body = document . body ;
217+
218+ // Dark mode CSS
219+ const sheet = document . createElement ( "style" ) ;
220+ sheet . id = "universal-dark-mode-sheet" ;
221+ sheet . textContent = `
222+ .dark-mode {
223+ --dm-bg:#0f0f10;--dm-surface:#1e1e1e;--dm-text:#eaeaea;--dm-muted:#bdbdbd;--dm-accent:#eaeaea;
224+ }
225+ .dark-mode, .dark-mode body {background-color:var(--dm-bg)!important;color:var(--dm-text)!important;}
226+ .dark-mode .dm-btn {background:#2a2a2a!important;color:var(--dm-text)!important;border:1px solid rgba(200,200,200,0.45)!important;border-radius:8px!important;box-shadow:0 0 10px rgba(200,200,200,0.18)!important;}
227+ .dark-mode .dm-title {color:var(--dm-accent)!important;}
228+ .dark-mode .dm-note {color:var(--dm-muted)!important;font-style:italic;}
229+ .dark-mode .dm-panel {background:var(--dm-surface)!important;color:var(--dm-text)!important;border:1px solid rgba(255,255,255,0.03)!important;box-shadow:0 6px 18px rgba(0,0,0,0.35) inset!important;}
230+ .dark-mode * {transition:color 0.25s ease,background-color 0.25s ease,border-color 0.25s ease,box-shadow 0.25s ease!important;}
231+ ` ;
232+ document . head . appendChild ( sheet ) ;
233+
234+ // Toggle button
235+ const toggle = Object . assign ( document . createElement ( "button" ) , {
236+ id : "dark-mode-toggle" ,
237+ textContent : "🌙" ,
238+ onclick ( ) {
239+ const enabled = body . classList . toggle ( "dark-mode" ) ;
240+ toggle . textContent = enabled ? "☀️" : "🌙" ;
241+ localStorage . setItem ( STORAGE_KEY , enabled ? "true" : "false" ) ;
242+ enabled ? applyDark ( ) : restoreStyles ( ) ;
243+ }
244+ } ) ;
245+ Object . assign ( toggle . style , {
246+ position :"fixed" , bottom :"15px" , right :"15px" , background :"transparent" ,
247+ border :"2px solid rgba(200,200,200,0.22)" , borderRadius :"8px" ,
248+ fontSize :"1.4rem" , cursor :"pointer" , zIndex :2147483647 ,
249+ padding :"6px" , lineHeight :"1" , display :"flex" ,
250+ alignItems :"center" , justifyContent :"center" ,
251+ transition :"transform 0.18s ease,border-color 0.18s ease,opacity 0.18s ease"
252+ } ) ;
253+ toggle . onmouseenter = ( ) => { toggle . style . transform = "scale(1.15)" ; toggle . style . borderColor = "rgba(200,200,200,0.7)" ; } ;
254+ toggle . onmouseleave = ( ) => { toggle . style . transform = "scale(1)" ; toggle . style . borderColor = "rgba(200,200,200,0.22)" ; } ;
255+ document . body . appendChild ( toggle ) ;
256+
257+ // Load preference
258+ const saved = localStorage . getItem ( STORAGE_KEY ) ;
259+ if ( saved === "true" || ( ! saved && window . matchMedia ( "(prefers-color-scheme: dark)" ) . matches ) ) {
260+ body . classList . add ( "dark-mode" ) ; toggle . textContent = "☀️" ; applyDark ( ) ;
261+ }
262+
263+ const brightness = rgb => ( rgb [ 0 ] * 299 + rgb [ 1 ] * 587 + rgb [ 2 ] * 114 ) / 1000 ;
264+
265+ function applyDark ( ) {
266+ document . querySelectorAll ( "*" ) . forEach ( el => {
267+ if ( el . dataset . dmProcessed ) return ;
268+ el . dataset . dmOriginalStyle = el . getAttribute ( "style" ) || "" ;
269+ const added = [ ] ;
270+ try {
271+ const cs = getComputedStyle ( el ) , bg = cs . backgroundColor || "" , color = cs . color || "" , fs = parseFloat ( cs . fontSize || 0 ) ;
272+ if ( el . tagName === "BUTTON" || el . getAttribute ( "role" ) === "button" ) added . push ( "dm-btn" , el . classList . add ( "dm-btn" ) ) ;
273+ else {
274+ if ( / [ \p{ Emoji} ] / u. test ( el . textContent || "" ) || fs >= 18 ) added . push ( "dm-title" , el . classList . add ( "dm-title" ) ) ;
275+ else if ( fs <= 12 || cs . fontStyle === "italic" ) added . push ( "dm-note" , el . classList . add ( "dm-note" ) ) ;
276+ if ( bg && bg !== "rgba(0, 0, 0, 0)" && bg . match ( / \d + / ) ) {
277+ const rgb = bg . match ( / \d + / g) . map ( Number ) ;
278+ if ( brightness ( rgb ) > 150 ) added . push ( "dm-panel" , el . classList . add ( "dm-panel" ) ) ;
279+ }
280+ if ( el . tagName === "INPUT" && [ "button" , "submit" , "reset" ] . includes ( ( el . type || "" ) . toLowerCase ( ) ) ) added . push ( "dm-btn" , el . classList . add ( "dm-btn" ) ) ;
281+ }
282+ if ( added . length ) el . dataset . dmAddedClasses = added . join ( " " ) ;
283+ } catch ( e ) { }
284+ el . dataset . dmProcessed = "1" ;
285+ } ) ;
286+ }
287+
288+ function restoreStyles ( ) {
289+ document . querySelectorAll ( "[data-dm-processed='1']" ) . forEach ( el => {
290+ const orig = el . dataset . dmOriginalStyle || "" ; orig ? el . setAttribute ( "style" , orig ) : el . removeAttribute ( "style" ) ;
291+ ( el . dataset . dmAddedClasses || "" ) . split ( / \s + / ) . forEach ( c => c && el . classList . remove ( c ) ) ;
292+ delete el . dataset . dmProcessed ; delete el . dataset . dmOriginalStyle ; delete el . dataset . dmAddedClasses ;
293+ } ) ;
294+ body . classList . remove ( "dark-mode" ) ;
295+ }
296+ } ) ( ) ;
0 commit comments