window.App = window.App || {}; App.marquee = App.marquee || {}; // A title is always one line. When it doesn't fit its box it scrolls sideways // instead of wrapping or being silently cut off. // // Four surfaces show a title that way -- the grid card, the favorites bar card, // the fullscreen player, the reels slide -- and they must scroll at the same // speed to look like one app, so the measurement lives here rather than being // written out again next to each of them. Whether a given title is *currently* // scrolling is the caller's business (see the `is-title-active` handling in // videos.js and favorites.js): a whole grid of marching text is unreadable, so // most callers animate only the title the reader is actually looking at. (function() { // Drive the duration off the distance so every title scrolls at the same // gentle rate rather than a fixed duration, which made longer titles whip // past. The floor keeps short ones from snapping. const SPEED_PX_PER_SEC = 28; const MIN_DURATION_S = 6; // Sub-pixel rounding isn't overflow worth animating. const OVERFLOW_SLACK_PX = 4; // Trailing space so the last word clears the edge before it wraps around. const TAIL_GAP_PX = 12; // Measures `text` inside `wrap` and prepares the animation: sets // --marquee-distance / --marquee-duration on `text`, and marks `wrap` with // `has-marquee` so CSS can decide what to do about it. Returns whether the // title overflows -- callers use that to skip the bookkeeping (scroll // observers, hover handlers) that only scrolling titles need. // // Reads layout, so call it when the element is in the document and visible; // a hidden element measures as zero-width and reports no overflow. App.marquee.measure = function(wrap, text) { if (!wrap || !text) return false; const overflow = text.scrollWidth - wrap.clientWidth; if (overflow <= OVERFLOW_SLACK_PX) { wrap.classList.remove('has-marquee'); text.style.removeProperty('--marquee-distance'); text.style.removeProperty('--marquee-duration'); return false; } const distance = overflow + TAIL_GAP_PX; const duration = Math.max(MIN_DURATION_S, distance / SPEED_PX_PER_SEC); text.style.setProperty('--marquee-distance', `${distance}px`); text.style.setProperty('--marquee-duration', `${duration.toFixed(2)}s`); wrap.classList.add('has-marquee'); return true; }; })();