// Video captions: match the language the member already chose with the 🌐 button, and // make the fact that captions EXIST visible without hunting for the player's CC button. // // Marty, 2026-09-17: the first build relied on the native CC control. He could not find // it on his own page, which means members would not either, so every captioned player // now carries a visible row of language chips underneath. // // Still deliberately OFF by default for an English reader: someone reading English does // not want text over the picture. Someone who already switched the site to Italian // almost certainly does, so that one turns itself on. // // Tracks are built by tools/captions.mjs. See memory rm-circle-video-captions. (function () { var NAMES = { en: 'English', it: 'Italiano', fr: 'Français', es: 'Español', de: 'Deutsch', pt: 'Português', nl: 'Nederlands', pl: 'Polski', ro: 'Română', ru: 'Русский', uk: 'Українська', tr: 'Türkçe', ar: 'العربية', hi: 'हिन्दी', vi: 'Tiếng Việt', id: 'Bahasa', th: 'ไทย', zh: '中文', ja: '日本語', ko: '한국어', sw: 'Kiswahili', fil: 'Filipino' }; function chosen() { try { return String(localStorage.getItem('rmc.lang') || '').toLowerCase(); } catch (e) { return ''; } } function tracksOf(v) { var out = [], tt = v.textTracks; for (var i = 0; tt && i < tt.length; i++) if (tt[i].kind === 'captions' || tt[i].kind === 'subtitles') out.push(tt[i]); return out; } function show(v, lang) { var list = tracksOf(v), hit = null; for (var i = 0; i < list.length; i++) { list[i].mode = 'disabled'; // never leave a stale track showing if (lang && String(list[i].language || '').toLowerCase() === lang) hit = list[i]; } if (hit) hit.mode = 'showing'; paint(v, lang); } function paint(v, active) { var bar = v.parentNode && v.parentNode.querySelector('.cap-bar'); if (!bar) return; var btns = bar.querySelectorAll('button[data-lang]'); for (var i = 0; i < btns.length; i++) { var on = btns[i].getAttribute('data-lang') === (active || ''); btns[i].style.background = on ? 'rgba(240,197,109,.22)' : 'transparent'; btns[i].style.borderColor = on ? 'rgba(240,197,109,.7)' : 'var(--line, #24425d)'; btns[i].style.color = on ? 'var(--gold, #f0c56d)' : 'var(--muted, #8fa6bd)'; btns[i].setAttribute('aria-pressed', on ? 'true' : 'false'); } } function build(v) { var list = tracksOf(v); if (!list.length || !v.parentNode) return; if (v.parentNode.querySelector('.cap-bar')) return; // already built var bar = document.createElement('div'); bar.className = 'cap-bar'; bar.style.cssText = 'display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:8px 6px 2px;font-size:12px'; var lab = document.createElement('span'); lab.textContent = 'Subtitles:'; lab.style.cssText = 'color:var(--muted,#8fa6bd);text-transform:uppercase;letter-spacing:.07em;font-weight:700;margin-right:2px'; bar.appendChild(lab); var mk = function (lang, text) { var b = document.createElement('button'); b.type = 'button'; b.setAttribute('data-lang', lang); b.textContent = text; b.style.cssText = 'cursor:pointer;border:1px solid var(--line,#24425d);background:transparent;color:var(--muted,#8fa6bd);' + 'border-radius:999px;padding:4px 11px;font-size:12px;font-family:inherit;line-height:1.4'; b.addEventListener('click', function () { show(v, lang); }); return b; }; bar.appendChild(mk('', 'Off')); for (var i = 0; i < list.length; i++) { var code = String(list[i].language || '').toLowerCase(); bar.appendChild(mk(code, NAMES[code] || (list[i].label || code))); } v.parentNode.insertBefore(bar, v.nextSibling); } function init() { var vids = document.querySelectorAll('video'); var lang = chosen(); for (var i = 0; i < vids.length; i++) { if (!tracksOf(vids[i]).length) continue; build(vids[i]); // English readers get nothing over the picture; a chosen language turns itself on show(vids[i], lang && lang !== 'en' ? lang : ''); } } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init(); window.addEventListener('storage', function (e) { if (!e || e.key === 'rmc.lang') init(); }); window.RMCCaptions = { refresh: init }; })();