import { Panel } from './Panel'; import type { NewsItem } from '@/types'; import { escapeHtml, sanitizeUrl } from '@/utils/sanitize'; import { t } from '@/services/i18n'; import { setTrustedHtml, trustedHtml } from '@/utils/dom-utils'; /** * BreakthroughsTickerPanel -- Horizontally scrolling ticker of science breakthroughs. * * Displays a continuously scrolling strip of science news items. The animation * is driven entirely by CSS (added in plan 06-03). The JS builds the DOM with * doubled content for seamless infinite scroll. Hover-pause and tab-hidden * pause are handled by CSS (:hover rule and .animations-paused body class). */ export class BreakthroughsTickerPanel extends Panel { private tickerTrack: HTMLElement | null = null; constructor() { super({ id: 'breakthroughs', title: 'Breakthroughs', trackActivity: false }); this.createTickerDOM(); } /** * Create the ticker wrapper and track elements. */ private createTickerDOM(): void { const wrapper = document.createElement('div'); wrapper.className = 'breakthroughs-ticker-wrapper'; const track = document.createElement('div'); track.className = 'breakthroughs-ticker-track'; wrapper.appendChild(track); this.tickerTrack = track; // Clear loading state and append the ticker setTrustedHtml(this.content, trustedHtml('', "legacy direct innerHTML migration")); this.content.appendChild(wrapper); } /** * Receive science news items and populate the ticker track. * Content is doubled for seamless infinite CSS scroll animation. */ public setItems(items: NewsItem[]): void { if (!this.tickerTrack) return; if (items.length === 0) { setTrustedHtml(this.tickerTrack, trustedHtml(`${t('components.breakthroughsTicker.noData')}`, "legacy direct innerHTML migration")); return; } // Build HTML for one set of items const itemsHtml = items .map( (item) => `` + `${escapeHtml(item.source)}` + `${escapeHtml(item.title)}` + ``, ) .join(''); // Double the content for seamless infinite scroll setTrustedHtml(this.tickerTrack, trustedHtml(itemsHtml + itemsHtml, "legacy direct innerHTML migration")); } /** * Clean up animation and call parent destroy. */ public destroy(): void { if (this.tickerTrack) { this.tickerTrack.style.animationPlayState = 'paused'; this.tickerTrack = null; } super.destroy(); } }