@@ -210,150 +210,3 @@ 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- const TOGGLE_ID = "dark-mode-toggle" , STYLE_ID = "universal-dark-mode-sheet" ;
218- const SESSION_INIT = "dmInitialized" , SESSION_USER_TOGGLED = "dmUserToggled" ;
219- let applyTimer = 0 , observer = null ;
220-
221- // USER CONFIG: array of CSS selectors or elements that dark mode should ignore
222- const EXCLUDE_ELEMENTS = [ "#my-special-div123" , ".no-dark123" ] ;
223-
224- // insert stylesheet once
225- if ( ! document . getElementById ( STYLE_ID ) ) {
226- const s = document . createElement ( "style" ) ;
227- s . id = STYLE_ID ;
228- s . textContent = `
229- .dark-mode {
230- --dm-bg:#0f0f10;--dm-surface:#1e1e1e;--dm-text:#eaeaea;--dm-muted:#bdbdbd;--dm-accent:#eaeaea;
231- }
232- .dark-mode, .dark-mode body {background-color:var(--dm-bg) !important; color:var(--dm-text) !important;}
233- .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;}
234- .dark-mode .dm-title {color:var(--dm-accent) !important;}
235- .dark-mode .dm-note {color:var(--dm-muted) !important; font-style:italic;}
236- .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;}
237- .dm-transition * {transition: color 0.25s ease, background-color 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease !important;}
238- ` ;
239- document . head . appendChild ( s ) ;
240- }
241-
242- function parseRgb ( str ) {
243- if ( ! str ) return null ;
244- const m = str . match ( / r g b a ? \( ( \d + ) [ ^ \d ] + ( \d + ) [ ^ \d ] + ( \d + ) / i) ;
245- if ( m ) return [ + m [ 1 ] , + m [ 2 ] , + m [ 3 ] ] ;
246- const h = ( str . match ( / ^ # ( [ 0 - 9 a - f ] { 6 } ) $ / i) || [ ] ) [ 1 ] ;
247- return h ? [ parseInt ( h . slice ( 0 , 2 ) , 16 ) , parseInt ( h . slice ( 2 , 4 ) , 16 ) , parseInt ( h . slice ( 4 , 6 ) , 16 ) ] : null ;
248- }
249- const brightness = rgb => rgb ? ( rgb [ 0 ] * 299 + rgb [ 1 ] * 587 + rgb [ 2 ] * 114 ) / 1000 : 255 ;
250-
251- function isExcluded ( el ) {
252- if ( ! EXCLUDE_ELEMENTS || ! EXCLUDE_ELEMENTS . length ) return false ;
253- return EXCLUDE_ELEMENTS . some ( sel => {
254- if ( typeof sel === "string" ) return el . matches ( sel ) ;
255- return el === sel ;
256- } ) ;
257- }
258-
259- function applyDark ( ) {
260- clearTimeout ( applyTimer ) ;
261- applyTimer = setTimeout ( ( ) => requestAnimationFrame ( ( ) => {
262- document . querySelectorAll ( "body *" ) . forEach ( el => {
263- if ( el . dataset . dmProcessed || isExcluded ( el ) ) return ;
264- try {
265- el . dataset . dmOriginalStyle = el . getAttribute ( "style" ) || "" ;
266- const cs = getComputedStyle ( el ) , bg = cs . backgroundColor || "" , fs = parseFloat ( cs . fontSize || 0 ) ;
267- const added = [ ] ;
268- if ( el . tagName === "BUTTON" || el . getAttribute ( "role" ) === "button" ||
269- ( el . tagName === "INPUT" && [ "button" , "submit" , "reset" ] . includes ( ( el . type || "" ) . toLowerCase ( ) ) ) ) {
270- added . push ( "dm-btn" ) ; el . classList . add ( "dm-btn" ) ;
271- } else {
272- const txt = ( el . textContent || "" ) . trim ( ) ;
273- if ( / [ \p{ Emoji} ] / u. test ( txt ) || fs >= 18 ) { added . push ( "dm-title" ) ; el . classList . add ( "dm-title" ) ; }
274- else if ( fs <= 12 || cs . fontStyle === "italic" ) { added . push ( "dm-note" ) ; el . classList . add ( "dm-note" ) ; }
275- const rgb = parseRgb ( bg ) ;
276- if ( ( rgb && brightness ( rgb ) > 150 ) || ( cs . backgroundImage && cs . backgroundImage !== "none" ) ) {
277- added . push ( "dm-panel" ) ; el . classList . add ( "dm-panel" ) ;
278- }
279- }
280- if ( added . length ) el . dataset . dmAddedClasses = added . join ( " " ) ;
281- el . dataset . dmProcessed = "1" ;
282- } catch ( e ) { }
283- } ) ;
284- } ) , 50 ) ;
285- }
286-
287- function restoreStyles ( ) {
288- clearTimeout ( applyTimer ) ;
289- if ( observer ) observer . disconnect ( ) ;
290- document . querySelectorAll ( "[data-dm-processed='1']" ) . forEach ( el => {
291- try {
292- const orig = el . dataset . dmOriginalStyle || "" ;
293- orig ? el . setAttribute ( "style" , orig ) : el . removeAttribute ( "style" ) ;
294- ( el . dataset . dmAddedClasses || "" ) . split ( / \s + / ) . forEach ( c => c && el . classList . remove ( c ) ) ;
295- delete el . dataset . dmProcessed ; delete el . dataset . dmOriginalStyle ; delete el . dataset . dmAddedClasses ;
296- } catch ( e ) { }
297- } ) ;
298- BODY . classList . remove ( "dark-mode" ) ;
299- }
300-
301- function ensureToggle ( ) {
302- let t = document . getElementById ( TOGGLE_ID ) ;
303- if ( ! t ) {
304- t = Object . assign ( document . createElement ( "button" ) , {
305- id : TOGGLE_ID ,
306- textContent : "🌙" ,
307- onclick ( ) {
308- const enabled = BODY . classList . toggle ( "dark-mode" ) ;
309- this . textContent = enabled ? "☀️" : "🌙" ;
310- sessionStorage . setItem ( SESSION_USER_TOGGLED , "1" ) ;
311- localStorage . setItem ( STORAGE_KEY , enabled ? "true" : "false" ) ;
312- if ( enabled ) {
313- BODY . classList . add ( "dm-transition" ) ;
314- applyDark ( ) ;
315- startObserver ( ) ;
316- setTimeout ( ( ) => BODY . classList . remove ( "dm-transition" ) , 260 ) ;
317- } else restoreStyles ( ) ;
318- }
319- } ) ;
320- Object . assign ( t . style , { position :"fixed" , bottom :"15px" , right :"15px" , background :"transparent" , border :"2px solid rgba(200,200,200,0.22)" , borderRadius :"8px" , fontSize :"1.4rem" , cursor :"pointer" , zIndex :2147483647 , padding :"6px" , lineHeight :"1" , display :"flex" , alignItems :"center" , justifyContent :"center" , transition :"transform 0.18s ease,border-color 0.18s ease,opacity 0.18s ease" } ) ;
321- t . onmouseenter = ( ) => { t . style . transform = "scale(1.15)" ; t . style . borderColor = "rgba(200,200,200,0.7)" ; } ;
322- t . onmouseleave = ( ) => { t . style . transform = "scale(1)" ; t . style . borderColor = "rgba(200,200,200,0.22)" ; } ;
323- document . body . appendChild ( t ) ;
324- }
325- return t ;
326- }
327-
328- function startObserver ( ) {
329- observer && observer . disconnect ( ) ;
330- observer = new MutationObserver ( muts => {
331- if ( ! isEnabled ( ) ) return ;
332- for ( const m of muts ) if ( m . addedNodes && m . addedNodes . length ) { applyDark ( ) ; break ; }
333- } ) ;
334- observer . observe ( document . body , { childList : true , subtree : true } ) ;
335- }
336-
337- const isEnabled = ( ) => BODY . classList . contains ( "dark-mode" ) ;
338-
339- function init ( ) {
340- const toggle = ensureToggle ( ) ;
341- const saved = localStorage . getItem ( STORAGE_KEY ) , inited = sessionStorage . getItem ( SESSION_INIT ) ;
342- if ( ! inited ) {
343- sessionStorage . setItem ( SESSION_INIT , "1" ) ;
344- toggle . textContent = saved === "true" ? "☀️" : "🌙" ;
345- return ;
346- }
347- const userToggled = sessionStorage . getItem ( SESSION_USER_TOGGLED ) ;
348- if ( userToggled === "1" || saved === "true" ) {
349- BODY . classList . toggle ( "dark-mode" , saved === "true" ) ;
350- toggle . textContent = saved === "true" ? "☀️" : "🌙" ;
351- if ( saved === "true" ) { applyDark ( ) ; startObserver ( ) ; }
352- } else toggle . textContent = "🌙" ;
353- }
354-
355- if ( document . readyState === "loading" ) document . addEventListener ( "DOMContentLoaded" , init , { once : true } ) ;
356- else init ( ) ;
357-
358- window . addEventListener ( "popstate" , ( ) => { clearTimeout ( applyTimer ) ; applyTimer = setTimeout ( init , 40 ) ; } ) ;
359- } ) ( ) ;
0 commit comments