1dbee99c49
Marty asked why they start muted. Browsers only permit autoplay when a video
is muted, so "expand and play" necessarily meant "expand and play silently" —
and for these that is the wrong trade, because the narration IS the training.
A member's first visit was showing them a silent walkthrough.
So the panel now opens paused with the play button showing. Pressing it is a
user gesture, which means it plays with sound, every time. preload stays
'none' until the panel opens, then goes to 'metadata' so an open panel shows
a real first frame instead of a black rectangle.
Each bar now also carries its runtime ("· 71 sec"), so nobody has to guess
whether they are committing to one minute or ten.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
116 lines
5.0 KiB
JavaScript
116 lines
5.0 KiB
JavaScript
// Collapsible walkthrough player for Suite tool pages.
|
|
//
|
|
// Placement reasoning (Marty's call): the video belongs at the TOP of the tool,
|
|
// because a video in a separate library never gets watched — but it must not be
|
|
// an embedded player, because after the first week most visits are returning
|
|
// members who came to USE the tool, and a player pushes the tool below the fold
|
|
// on mobile every single time.
|
|
//
|
|
// So: a one-line bar that expands inline, auto-opened on the member's FIRST
|
|
// visit to that specific tool and collapsed on every visit after. The state is
|
|
// per-tool, per-browser. localStorage can throw outright in some contexts
|
|
// (private windows, blocked site data), so every access is guarded and the bar
|
|
// falls back to "collapsed but present" rather than breaking the page.
|
|
(function () {
|
|
'use strict';
|
|
|
|
var el = document.currentScript;
|
|
var SRC = el && el.getAttribute('data-video');
|
|
var TITLE = (el && el.getAttribute('data-title')) || 'Watch the walkthrough';
|
|
var KEY = 'rmc.tv.' + ((el && el.getAttribute('data-key')) || (location.pathname.replace(/\W+/g, '') || 'tool'));
|
|
var SECS = (el && el.getAttribute('data-secs')) || '';
|
|
if (!SRC) return;
|
|
|
|
function seen() { try { return localStorage.getItem(KEY) === '1'; } catch (e) { return true; } }
|
|
function markSeen() { try { localStorage.setItem(KEY, '1'); } catch (e) {} }
|
|
|
|
function build() {
|
|
var host = document.querySelector('main') || document.body;
|
|
var wrap = document.createElement('div');
|
|
wrap.className = 'tv-wrap';
|
|
|
|
var bar = document.createElement('button');
|
|
bar.type = 'button';
|
|
bar.className = 'tv-bar';
|
|
bar.setAttribute('aria-expanded', 'false');
|
|
bar.innerHTML = '<span class="tv-play">▶</span><span class="tv-t">' + TITLE +
|
|
(SECS ? ' <span class="tv-secs">' + SECS + '</span>' : '') + '</span><span class="tv-chev">⌄</span>';
|
|
|
|
var body = document.createElement('div');
|
|
body.className = 'tv-body';
|
|
body.hidden = true;
|
|
|
|
var vid = document.createElement('video');
|
|
vid.controls = true;
|
|
// Nothing is fetched until the panel is actually opened; open() then bumps
|
|
// this to 'metadata' so an expanded panel shows a real first frame rather
|
|
// than a black rectangle.
|
|
vid.preload = 'none';
|
|
vid.playsInline = true;
|
|
vid.src = SRC;
|
|
body.appendChild(vid);
|
|
|
|
function open() {
|
|
body.hidden = false;
|
|
wrap.classList.add('on');
|
|
bar.setAttribute('aria-expanded', 'true');
|
|
// Deliberately NOT autoplaying. Browsers only permit autoplay when the
|
|
// video is muted, and a silent walkthrough is worthless here — the
|
|
// narration IS the training. So on first visit the panel opens with the
|
|
// video paused and the play button showing; pressing it is a user
|
|
// gesture, which means it plays with sound, every time.
|
|
vid.preload = 'metadata';
|
|
}
|
|
function close() {
|
|
body.hidden = true;
|
|
wrap.classList.remove('on');
|
|
bar.setAttribute('aria-expanded', 'false');
|
|
vid.pause();
|
|
}
|
|
|
|
bar.addEventListener('click', function () {
|
|
if (body.hidden) { open(); markSeen(); }
|
|
else close();
|
|
});
|
|
|
|
wrap.appendChild(bar);
|
|
wrap.appendChild(body);
|
|
|
|
// Sit directly under the page heading if there is one, otherwise at the top.
|
|
var h1 = host.querySelector('h1');
|
|
var lede = h1 && h1.nextElementSibling && /lede|sub/.test(h1.nextElementSibling.className || '')
|
|
? h1.nextElementSibling : h1;
|
|
if (lede && lede.parentNode) lede.parentNode.insertBefore(wrap, lede.nextSibling);
|
|
else host.insertBefore(wrap, host.firstChild);
|
|
|
|
if (!seen()) { open(); markSeen(); }
|
|
}
|
|
|
|
function style() {
|
|
if (document.getElementById('tv-style')) return;
|
|
var s = document.createElement('style');
|
|
s.id = 'tv-style';
|
|
s.textContent = [
|
|
'.tv-wrap{margin:0 0 18px}',
|
|
'.tv-bar{display:flex;align-items:center;gap:10px;width:100%;text-align:left;cursor:pointer;',
|
|
' background:rgba(212,175,55,.08);border:1px solid var(--gold,#d4af37);border-radius:12px;',
|
|
' color:var(--text,#e8eef4);padding:11px 15px;font:inherit;font-size:14.5px;font-weight:700}',
|
|
'.tv-bar:hover{background:rgba(212,175,55,.14)}',
|
|
'.tv-bar:focus-visible{outline:2px solid var(--teal,#4ed6cb);outline-offset:2px}',
|
|
'.tv-play{color:var(--gold,#d4af37);font-size:12px}',
|
|
'.tv-t{flex:1}',
|
|
'.tv-secs{color:var(--muted,#8fa6bb);font-weight:600;font-size:12.5px}',
|
|
'.tv-chev{color:var(--muted,#8fa6bb);transition:transform .18s}',
|
|
'.tv-wrap.on .tv-chev{transform:rotate(180deg)}',
|
|
'.tv-body{margin-top:10px;border:1px solid var(--line,#24425d);border-radius:12px;overflow:hidden;line-height:0}',
|
|
'.tv-body video{width:100%;height:auto;display:block;background:#000}',
|
|
'@media (prefers-reduced-motion:reduce){.tv-chev{transition:none}}'
|
|
].join('');
|
|
document.head.appendChild(s);
|
|
}
|
|
|
|
function go() { style(); build(); }
|
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', go);
|
|
else go();
|
|
})();
|