From ca5b19209c7492bfeffb6df8fb45fcb7556f33c8 Mon Sep 17 00:00:00 2001 From: mkelvers Date: Thu, 7 May 2026 11:20:40 +0200 Subject: [PATCH] fix: writing bit stuff cleaner --- static/dedupe.ts | 17 ++- static/discover.ts | 2 +- static/dropdown.ts | 18 ++- static/player.ts | 220 +++++++++++++++++------------ static/search.ts | 10 +- static/theme.ts | 94 ++++-------- static/timezone.ts | 2 +- static/toast.ts | 72 ++++++---- templates/base.gohtml | 18 ++- templates/components/footer.gohtml | 13 ++ templates/components/header.gohtml | 15 +- 11 files changed, 274 insertions(+), 207 deletions(-) diff --git a/static/dedupe.ts b/static/dedupe.ts index 9395686..266bcde 100644 --- a/static/dedupe.ts +++ b/static/dedupe.ts @@ -1,11 +1,15 @@ const dedupe = (): void => { const seen = new Set() const elements = document.querySelectorAll('[data-id]') + elements.forEach((item) => { const id = item.getAttribute('data-id') - if (id && seen.has(id)) { + if (!id) { + return + } + if (seen.has(id)) { item.remove() - } else if (id) { + } else { seen.add(id) } }) @@ -13,8 +17,9 @@ const dedupe = (): void => { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', dedupe) -} else { - dedupe() -} -// Also run on window load to be sure + } else { + dedupe() + } + + window.addEventListener('load', dedupe) window.addEventListener('load', dedupe) diff --git a/static/discover.ts b/static/discover.ts index fdc2314..e00eb41 100644 --- a/static/discover.ts +++ b/static/discover.ts @@ -7,7 +7,7 @@ const setActiveDiscoverTab = (clickedTab: Element): void => { } const triggers = group.querySelectorAll('[data-tab-trigger]') - triggers.forEach((tab: Element): void => { + triggers.forEach((tab) => { const activeClasses = parseClassList(tab.getAttribute('data-tab-active-classes')) const inactiveClasses = parseClassList(tab.getAttribute('data-tab-inactive-classes')) tab.classList.remove(...activeClasses) diff --git a/static/dropdown.ts b/static/dropdown.ts index 135ca69..9a3c89c 100644 --- a/static/dropdown.ts +++ b/static/dropdown.ts @@ -9,7 +9,7 @@ class UIDropdown extends HTMLElement { this.handleClickOutside = this.handleClickOutside.bind(this) } - connectedCallback() { + connectedCallback(): void { const trigger = this.querySelector('[data-trigger]') this.contentEl = this.querySelector('[data-content]') @@ -20,7 +20,7 @@ class UIDropdown extends HTMLElement { document.addEventListener('click', this.handleClickOutside) } - disconnectedCallback() { + disconnectedCallback(): void { const trigger = this.querySelector('[data-trigger]') if (trigger) { trigger.removeEventListener('click', this.toggle) @@ -28,8 +28,10 @@ class UIDropdown extends HTMLElement { document.removeEventListener('click', this.handleClickOutside) } - toggle() { - if (this.isClosing) return + toggle(): void { + if (this.isClosing) { + return + } this.isOpen = !this.isOpen if (this.contentEl) { if (this.isOpen) { @@ -40,8 +42,10 @@ class UIDropdown extends HTMLElement { } } - close() { - if (this.isClosing) return + close(): void { + if (this.isClosing) { + return + } this.isClosing = true this.isOpen = false if (this.contentEl) { @@ -52,7 +56,7 @@ class UIDropdown extends HTMLElement { }, 100) } - handleClickOutside(event: MouseEvent) { + handleClickOutside(event: MouseEvent): void { if (!this.contains(event.target as Node)) { this.close() } diff --git a/static/player.ts b/static/player.ts index 4f69e02..10a3116 100644 --- a/static/player.ts +++ b/static/player.ts @@ -2,7 +2,7 @@ declare const htmx: { ajax(verb: string, path: string, target: HTMLElement): Promise } -export {} +export { } import DOMPurify from 'dompurify' @@ -23,32 +23,6 @@ interface SkipSegment { end: number } -interface EpisodeData { - mal_id: number - title: string - current_episode: string - total_episodes: number - initial_mode: string - token: string - available_modes: string[] - mode_sources: Record - segments: SkipSegment[] - episode_title: string -} - -interface EpisodeData { - mal_id: number - title: string - current_episode: string - total_episodes: number - initial_mode: string - token: string - available_modes: string[] - mode_sources: Record - segments: SkipSegment[] - episode_title: string -} - let playerInitialized = false const initPlayer = (): void => { @@ -66,7 +40,6 @@ const initPlayer = (): void => { const iconPlay = container.querySelector('[data-icon-play]') as SVGElement const iconPause = container.querySelector('[data-icon-pause]') as SVGElement const muteBtn = container.querySelector('[data-mute]') as HTMLButtonElement - const volumeWrap = container.querySelector('[data-volume-wrap]') as HTMLElement const volumePanel = container.querySelector('[data-volume-panel]') as HTMLElement const volumeRange = container.querySelector('[data-volume-range]') as HTMLInputElement const iconVolume = container.querySelector('[data-icon-volume]') as SVGElement @@ -99,7 +72,9 @@ const initPlayer = (): void => { const animeImage = container.getAttribute('data-anime-image') || '' const animeAiring = (container.getAttribute('data-anime-airing') || '').toLowerCase() === 'true' const safeJsonParse = (raw: string | null, fallback: T): T => { - if (!raw) return fallback + if (!raw) { + return fallback + } try { return JSON.parse(raw) as T } catch { @@ -107,6 +82,12 @@ const initPlayer = (): void => { } } + const clearElement = (el: HTMLElement): void => { + while (el.firstChild) { + el.removeChild(el.firstChild) + } + } + let modeSources = safeJsonParse(container.getAttribute('data-mode-sources'), {} as Record) let availableModes = safeJsonParse(container.getAttribute('data-available-modes'), [] as string[]) const backendInitialMode = container.getAttribute('data-initial-mode') || 'dub' @@ -140,7 +121,7 @@ const initPlayer = (): void => { let isScrubbing = false let lastKnownVolume = 1 let pendingSeekTime: number | null = null - let activeSkipSegment: { type: string, start: number, end: number } | null = null + let activeSkipSegment: { type: string, start: number, end: number } | null = null let activeSubtitles: Array<{ start: number, end: number, text: string }> = [] let currentSubtitleTracks: Array<{ lang: string, label: string, url: string }> = [] @@ -277,20 +258,32 @@ const initPlayer = (): void => { return } - activeSegments = parsedSegments.filter((segment: { start: number, end: number, type: string }) => { + activeSegments = parsedSegments.filter((segment) => { const start = segment.start const end = segment.end const segmentDuration = end - start - if (segmentDuration < minSegmentDurationSeconds || segmentDuration > maxSegmentDurationSeconds) return false - if (start < 0 || end <= start || end > bounds.duration + 1) return false + + if (segmentDuration < minSegmentDurationSeconds || segmentDuration > maxSegmentDurationSeconds) { + return false + } + if (start < 0 || end <= start || end > bounds.duration + 1) { + return false + } + if (segment.type === 'op') { - if (start > maxIntroStartSeconds) return false - if (start > bounds.duration * 0.5) return false + if (start > maxIntroStartSeconds) { + return false + } + if (start > bounds.duration * 0.5) { + return false + } return true } + if (segment.type === 'ed') { return start >= bounds.duration * minOutroStartRatio } + return false }) } @@ -302,12 +295,16 @@ const initPlayer = (): void => { return segment.start + boundedDelay } + const findActiveSegment = (time: number): typeof activeSegments[number] | undefined => { + return activeSegments.find((segment) => { + const activationTime = skipActivationTime(segment) + return time >= activationTime && time < segment.end + }) + } + const updateSkipButton = (currentTime: number): void => { const currentDisplayTime = displayTimeFromAbsolute(currentTime) - const segment = activeSegments.find((item: { start: number, end: number }) => { - const activationTime = skipActivationTime(item) - return currentDisplayTime >= activationTime && currentDisplayTime < item.end - }) + const segment = findActiveSegment(currentDisplayTime) if (!segment) { activeSkipSegment = null @@ -333,8 +330,10 @@ const initPlayer = (): void => { } const renderSegments = (): void => { - if (!segmentsTrack) return - segmentsTrack.innerHTML = '' + if (!segmentsTrack) { + return + } + clearElement(segmentsTrack) const bounds = timelineBounds() if (bounds.duration <= 0) return @@ -396,7 +395,6 @@ const initPlayer = (): void => { break } } - // If we couldn't find a range containing current time (can happen immediately after seeking), // fallback to the highest buffered end that is greater than current time if (bufferedEnd === 0) { @@ -573,12 +571,14 @@ const initPlayer = (): void => { }, keepalive: true, body: payload, - }).catch(() => {}) + }).catch(() => { }) } const parseVttTime = (raw: string): number => { const parts = raw.trim().split(':') - if (parts.length < 2) return 0 + if (parts.length < 2) { + return 0 + } const secPart = parts.pop() || '0' const minPart = parts.pop() || '0' const hourPart = parts.pop() || '0' @@ -588,31 +588,58 @@ const initPlayer = (): void => { return (hours * 3600) + (minutes * 60) + seconds } - const parseVtt = (text: string): Array<{ start: number, end: number, text: string }> => { + const parseVttCue = (timeLine: string, lines: string[], startIndex: number): { start: number; end: number; text: string } | null => { + if (!timeLine.includes('-->')) { + return null + } + + const [startRaw, endRaw] = timeLine.split('-->') + const start = parseVttTime(startRaw) + const end = parseVttTime(endRaw) + + const payload: string[] = [] + let i = startIndex + 1 + while (i < lines.length && lines[i].trim() !== '') { + payload.push(lines[i]) + i += 1 + } + + const textContent = payload.join('\n').replace(/<[^>]+>/g, '').trim() + if (!textContent) { + return null + } + + return { start, end, text: textContent } + } + + const parseVtt = (text: string): Array<{ start: number; end: number; text: string }> => { const lines = text.replace(/\r/g, '').split('\n') - const cues: Array<{ start: number, end: number, text: string }> = [] + const cues: Array<{ start: number; end: number; text: string }> = [] let i = 0 + while (i < lines.length) { const line = lines[i].trim() - if (!line) { i += 1; continue } - let timeLine = line - if (!line.includes('-->') && i + 1 < lines.length) { - timeLine = lines[i + 1].trim() + if (!line) { i += 1 + continue + } + + if (i + 1 < lines.length && !line.includes('-->') && lines[i + 1].includes('-->')) { + const cue = parseVttCue(lines[i + 1].trim(), lines, i + 1) + if (cue) { + cues.push(cue) + } + i += 2 + continue + } + + const cue = parseVttCue(line, lines, i) + if (cue) { + cues.push(cue) } - if (!timeLine.includes('-->')) { i += 1; continue } - const [startRaw, endRaw] = timeLine.split('-->') - const start = parseVttTime(startRaw) - const end = parseVttTime(endRaw) i += 1 - const payload: string[] = [] - while (i < lines.length && lines[i].trim() !== '') { - payload.push(lines[i]) - i += 1 - } - const textContent = payload.join('\n').replace(/<[^>]+>/g, '').trim() - if (textContent) cues.push({ start, end, text: textContent }) } + return cues } @@ -634,26 +661,38 @@ const initPlayer = (): void => { subtitleText.classList.add('hidden') } + const findActiveCue = (time: number): { start: number; end: number; text: string } | undefined => { + return activeSubtitles.find((item) => { + return time >= item.start && time <= item.end + }) + } + const updateSubtitleRender = (currentTime: number): void => { - if (!subtitleText) return + if (!subtitleText) { + return + } if (!activeSubtitles.length) { hideSubtitleText() return } - const cue = activeSubtitles.find(item => currentTime >= item.start && currentTime <= item.end) + + const cue = findActiveCue(currentTime) if (!cue) { hideSubtitleText() return } + subtitleText.textContent = cue.text subtitleText.classList.remove('hidden') subtitleText.classList.add('block') } const updateSubtitleOptions = (): void => { - if (!subtitleSelect) return + if (!subtitleSelect) { + return + } currentSubtitleTracks = subtitlesForMode(currentMode) - subtitleSelect.innerHTML = '' + clearElement(subtitleSelect) const none = document.createElement('option') none.value = 'none' none.textContent = 'Off' @@ -674,30 +713,32 @@ const initPlayer = (): void => { } const updateQualityOptions = (): void => { - if (!qualitySelect) return + if (!qualitySelect) { + return + } const modeSource = modeSources[currentMode] const qualities = modeSource?.qualities || [] - - qualitySelect.innerHTML = '' + + clearElement(qualitySelect) const best = document.createElement('option') best.value = 'best' best.textContent = 'Auto / Best' qualitySelect.appendChild(best) - + qualities.forEach(q => { const option = document.createElement('option') option.value = q option.textContent = q qualitySelect.appendChild(option) }) - + const preferred = getPreferredQuality() if (qualities.includes(preferred)) { qualitySelect.value = preferred } else { qualitySelect.value = 'best' } - + const wrapper = qualitySelect.parentElement if (wrapper) { wrapper.classList.toggle('hidden', qualities.length === 0) @@ -736,7 +777,7 @@ const initPlayer = (): void => { video.src = nextURL video.load() pendingSeekTime = previousTime - if (wasPlaying) video.play().catch(() => {}) + if (wasPlaying) video.play().catch(() => { }) updateSubtitleOptions() updateQualityOptions() updateModeButtons(currentMode) @@ -834,7 +875,7 @@ const initPlayer = (): void => { video.src = nextURL video.load() pendingSeekTime = previousTime - if (wasPlaying) video.play().catch(() => {}) + if (wasPlaying) video.play().catch(() => { }) } // Initialize @@ -862,17 +903,17 @@ const initPlayer = (): void => { if (nextStart > 0) { try { video.currentTime = nextStart - } catch {} + } catch { } } } if (pendingSeekTime !== null && Number.isFinite(pendingSeekTime)) { try { video.currentTime = absoluteTimeFromDisplay(pendingSeekTime) - } catch {} + } catch { } pendingSeekTime = null } if (shouldAutoPlay) { - video.play().catch(() => {}) + video.play().catch(() => { }) } updateTimeline(video.currentTime) updateSkipButton(video.currentTime) @@ -919,8 +960,6 @@ const initPlayer = (): void => { }) } - - const goToNextEpisode = async (): Promise => { const currentEpNum = Number.parseInt(currentEpisode, 10) if (Number.isNaN(currentEpNum)) return @@ -968,7 +1007,7 @@ const initPlayer = (): void => { const wasPlaying = video.ended || !video.paused video.src = streamUrl video.load() - if (wasPlaying) video.play().catch(() => {}) + if (wasPlaying) video.play().catch(() => { }) currentEpisode = String(nextEpisode) pendingSeekTime = null @@ -1032,7 +1071,7 @@ const initPlayer = (): void => { } } - + const completeAnime = async (episodeNumber: number): Promise => { if (completionSent) return @@ -1067,7 +1106,11 @@ const initPlayer = (): void => { const dropdownTrigger = document.querySelector('[data-dropdown-trigger]') as HTMLButtonElement | null if (dropdownTrigger) { - dropdownTrigger.innerHTML = 'Completed ' + dropdownTrigger.textContent = 'Completed ' + const caret = document.createElement('span') + caret.className = 'text-xs' + caret.textContent = '▾' + dropdownTrigger.appendChild(caret) } const watchStatusDropdown = document.getElementById('watch-status-dropdown') @@ -1098,7 +1141,7 @@ const initPlayer = (): void => { wrapper.id = 'watch-status-dropdown' wrapper.innerHTML = DOMPurify.sanitize(html) watchStatusDropdown.replaceWith(wrapper) - }).catch(() => {}) + }).catch(() => { }) } } catch { completionSent = false @@ -1272,24 +1315,24 @@ const initPlayer = (): void => { document.addEventListener('keydown', (event) => { const target = event.target as HTMLElement if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) return - + // Key codes const code = event.code const key = event.key - + // Space or K: Toggle Play/Pause if (code === 'Space' || code === 'KeyK') { event.preventDefault() togglePlayPause() showControls() } - + // ArrowLeft or J: Seek Backward 10s if (code === 'ArrowLeft' || code === 'KeyJ') { event.preventDefault() seekBy(-10) } - + // ArrowRight or L: Seek Forward 10s if (code === 'ArrowRight' || code === 'KeyL') { event.preventDefault() @@ -1313,7 +1356,7 @@ const initPlayer = (): void => { video.muted = nextVolume === 0 showControls() } - + // KeyM: Toggle Mute if (code === 'KeyM') { event.preventDefault() @@ -1327,7 +1370,7 @@ const initPlayer = (): void => { } showControls() } - + // KeyF: Toggle Fullscreen if (code === 'KeyF') { event.preventDefault() @@ -1381,7 +1424,6 @@ const initPlayer = (): void => { if (episodeGrid) { episodeGrid.querySelectorAll('[data-episode-id]').forEach((el) => { el.classList.remove('ring-2', 'ring-accent', 'text-accent') - const epNum = parseInt(el.getAttribute('data-episode-id') || '0', 10) const isCurrent = el.classList.contains('bg-accent/20') if (!isCurrent) { el.classList.remove('bg-accent/20') diff --git a/static/search.ts b/static/search.ts index 2d0d380..28b3032 100644 --- a/static/search.ts +++ b/static/search.ts @@ -88,7 +88,7 @@ const renderQuickSearchResults = (query: string, results: QuickSearchResult[]): title.textContent = 'Anime' searchResults.appendChild(title) - results.forEach((result: QuickSearchResult): void => { + results.forEach((result: QuickSearchResult) => { searchResults.appendChild(buildSearchResultItem(result)) }) @@ -104,10 +104,10 @@ const renderQuickSearchResults = (query: string, results: QuickSearchResult[]): const fetchAndRenderQuickSearch = (query: string): void => { fetch('/api/search-quick?q=' + encodeURIComponent(query)) .then((res: Response) => res.json()) - .then((results: QuickSearchResult[]): void => { + .then((results: QuickSearchResult[]) => { renderQuickSearchResults(query, results) }) - .catch((err: unknown): void => { + .catch((err: unknown) => { console.error('Search error:', err) }) } @@ -128,13 +128,13 @@ const onSearchInput = (event: Event): void => { return } - searchTimeout = window.setTimeout((): void => { + searchTimeout = window.setTimeout(() => { fetchAndRenderQuickSearch(query) }, 300) } const onSearchBlur = (): void => { - window.setTimeout((): void => { + window.setTimeout(() => { clearSearchResults() }, 200) } diff --git a/static/theme.ts b/static/theme.ts index c254744..7db0a66 100644 --- a/static/theme.ts +++ b/static/theme.ts @@ -1,79 +1,41 @@ -type Theme = "light" | "dark"; +type Theme = 'light' | 'dark' -const STORAGE_KEY = "theme"; +const STORAGE_KEY = 'theme' const getSavedTheme = (): Theme => { - const raw = localStorage.getItem(STORAGE_KEY); - if (raw === "light" || raw === "dark") return raw; - return "dark"; -}; + const raw = localStorage.getItem(STORAGE_KEY) + if (raw === 'light' || raw === 'dark') { + return raw + } + return 'dark' +} const applyTheme = (theme: Theme): void => { - const html = document.documentElement; - html.setAttribute("data-theme", theme); - localStorage.setItem(STORAGE_KEY, theme); - updateToggleButtons(theme); -}; + document.documentElement.setAttribute('data-theme', theme) + localStorage.setItem(STORAGE_KEY, theme) +} const cycleTheme = (): void => { - const current = getSavedTheme(); - const next: Theme = current === "light" ? "dark" : "light"; - applyTheme(next); -}; - -const updateToggleButtons = (theme: Theme): void => { - const headerBtn = document.getElementById( - "theme-toggle", - ) as HTMLButtonElement | null; - const footerBtn = document.getElementById( - "footer-theme-toggle", - ) as HTMLButtonElement | null; - - const updateButton = (btn: HTMLButtonElement | null): void => { - if (!btn) return; - - const label = btn.querySelector("[data-theme-label]") as HTMLElement | null; - if (label) { - label.textContent = theme; - } - - const svg = btn.querySelector("svg"); - if (!svg) return; - - if (theme === "light") { - svg.innerHTML = - ''; - svg.setAttribute("stroke", "currentColor"); - svg.setAttribute("fill", "none"); - } else { - svg.innerHTML = - ''; - svg.setAttribute("stroke", "currentColor"); - svg.setAttribute("fill", "none"); - } - }; - - updateButton(headerBtn); - updateButton(footerBtn); -}; + const current = getSavedTheme() + const next: Theme = current === 'light' ? 'dark' : 'light' + applyTheme(next) +} const initTheme = (): void => { - const saved = getSavedTheme(); - applyTheme(saved); + const saved = getSavedTheme() + applyTheme(saved) - // Use event delegation to handle theme toggles - document.addEventListener("click", (e) => { - const target = e.target as HTMLElement; - const btn = target.closest("#theme-toggle, #footer-theme-toggle") as HTMLButtonElement | null; - + document.addEventListener('click', (e) => { + const target = e.target as HTMLElement + const btn = target.closest('#theme-toggle, #footer-theme-toggle') as HTMLButtonElement | null if (btn) { - cycleTheme(); + cycleTheme() } - }); -}; - -if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", initTheme); -} else { - initTheme(); + }) +} + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initTheme) +} else { + initTheme() } diff --git a/static/timezone.ts b/static/timezone.ts index dc7d51f..ccb31a0 100644 --- a/static/timezone.ts +++ b/static/timezone.ts @@ -239,7 +239,7 @@ const updateNode = (node: Element, localOffsetMinutes: number): void => { const updateAll = (): void => { const localOffsetMinutes = -new Date().getTimezoneOffset() const nodes = document.querySelectorAll('[data-jst-text]') - nodes.forEach((node: Element): void => updateNode(node, localOffsetMinutes)) + nodes.forEach((node) => updateNode(node, localOffsetMinutes)) } const initTimezoneConversion = (): void => { diff --git a/static/toast.ts b/static/toast.ts index a841292..3e5ef6a 100644 --- a/static/toast.ts +++ b/static/toast.ts @@ -1,43 +1,55 @@ +export {} + interface ToastOptions { - message: string; - duration?: number; + message: string + duration?: number } -const toastContainer = () => { - let container = document.getElementById('toast-container'); +const toastContainer = (): HTMLElement => { + let container = document.getElementById('toast-container') if (!container) { - container = document.createElement('div'); - container.id = 'toast-container'; - container.className = 'fixed bottom-4 right-4 z-100 flex flex-col gap-2'; - document.body.appendChild(container); + container = document.createElement('div') + container.id = 'toast-container' + container.className = 'fixed bottom-4 right-4 z-100 flex flex-col gap-2' + document.body.appendChild(container) } - return container; -}; + return container +} -const showToast = ({ message, duration = 3000 }: ToastOptions) => { - const container = toastContainer(); - const toast = document.createElement('div'); - - toast.className = 'bg-white/10 border border-white/20 flex items-center gap-3 px-4 py-3 shadow-lg transform transition-all duration-300 translate-y-2 opacity-0'; - toast.innerHTML = ` - ${message} - - `; +const showToast = ({ message, duration = 3000 }: ToastOptions): void => { + const container = toastContainer() + const template = document.getElementById('toast-template') as HTMLTemplateElement | null - container.appendChild(toast); + if (!template) { + return + } + + const toast = template.content.cloneNode(true) as HTMLElement + const messageEl = toast.querySelector('.toast-message') + const closeBtn = toast.querySelector('.toast-close') + + if (messageEl) { + messageEl.textContent = message + } + + closeBtn?.addEventListener('click', () => toast.remove()) + + container.appendChild(toast) requestAnimationFrame(() => { - toast.classList.remove('translate-y-2', 'opacity-0'); - }); + toast.classList.remove('translate-y-2', 'opacity-0') + }) setTimeout(() => { - toast.classList.add('translate-y-2', 'opacity-0'); - setTimeout(() => toast.remove(), 300); - }, duration); -}; + toast.classList.add('translate-y-2', 'opacity-0') + setTimeout(() => toast.remove(), 300) + }, duration) +} -(window as unknown as Record).showToast = showToast; +declare global { + interface Window { + showToast: typeof showToast + } +} -export { showToast }; \ No newline at end of file +window.showToast = showToast diff --git a/templates/base.gohtml b/templates/base.gohtml index 172f1a8..6e30e8f 100644 --- a/templates/base.gohtml +++ b/templates/base.gohtml @@ -17,7 +17,7 @@ opacity: 0 !important; margin-left: 0 !important; } - + /* Re-enable transitions after initialization */ .sidebar-ready #mobile-menu, .sidebar-ready .nav-label-container { @@ -25,8 +25,24 @@ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 300ms; } + + /* Theme toggle icon visibility */ + html[data-theme="dark"] .theme-icon-dark { display: none; } + html[data-theme="dark"] .theme-icon-light { display: block; } + html[data-theme="light"] .theme-icon-light { display: none; } + html[data-theme="light"] .theme-icon-dark { display: block; } + diff --git a/templates/components/footer.gohtml b/templates/components/footer.gohtml index f6172b5..97ee584 100644 --- a/templates/components/footer.gohtml +++ b/templates/components/footer.gohtml @@ -6,6 +6,19 @@ MyAnimeList -
+
+