← Commit history

Add responsive inline video to the shared newbie panel with native pane resize checks

John Lauer ·b77a51ceaa ·1mo ago ·parent 5c1edc1
4 files changed +80−13
assets/module/newbie-panel.js+56−9
@@ -78,6 +78,19 @@   .anp-left{flex-direction:row; align-items:center; gap:12px;} } +/* A video is content, not an icon in the legacy 220px demo-button column.+ * Opt-in only: existing button-only panels keep their original layout. */+.anp.anp--video{grid-template-columns:minmax(0,720px) minmax(360px,1fr);align-items:start;}+.anp--video .anp-left{min-width:0;display:flex;flex-direction:column;align-items:stretch;gap:12px;}+.anp-video{display:block;width:100%;height:auto;max-height:none;aspect-ratio:16/9;object-fit:contain;background:#080c11;border-radius:8px;}+.anp-video-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:10px 16px;}+.anp--video .anp-phrase span{white-space:normal;overflow:visible;text-overflow:clip;overflow-wrap:anywhere;padding:10px 8px 10px 0;}+.anp--video .anp-tbl{max-width:none;}+.anp--video .anp-right{align-self:center;}+@container anp (max-width:1099px){+  .anp.anp--video{grid-template-areas:"head" "left" "right";grid-template-columns:minmax(0,1fr);}+}+ .anp-toasts{position:fixed; left:50%; bottom:22px; transform:translateX(-50%); z-index:99999;   display:flex; flex-direction:column-reverse; gap:8px; pointer-events:none;} .anp-toast{pointer-events:auto; display:flex; align-items:center; gap:10px; max-width:min(92vw,520px);@@ -331,7 +344,9 @@     var key = 'anp:' + slug + ':open';      var wrap = el('div', 'anp-wrap');-    var panel = el('section', 'anp');+    var videoCfg = cfg.demo && cfg.demo.video;+    var video = null;+    var panel = el('section', 'anp' + (videoCfg && videoCfg.src ? ' anp--video' : ''));     panel.setAttribute('aria-label', 'Say it: sample prompts for ' + appName);      var head = el('div', 'anp-head');@@ -345,14 +360,26 @@     panel.appendChild(head);      var left = el('div', 'anp-left');+    var controls = left;+    if (videoCfg && videoCfg.src) {+      video = el('video', 'anp-video');+      video.src = videoCfg.src;+      if (videoCfg.poster) video.poster = videoCfg.poster;+      video.controls = true; video.playsInline = true; video.preload = 'none';+      video.setAttribute('aria-label', videoCfg.title || 'Demonstration video');+      left.appendChild(video);+      controls = el('div', 'anp-video-toolbar');+      left.appendChild(controls);+    }     var demo = el('button', 'anp-demo', ICON.play + '<span>' + esc((cfg.demo && cfg.demo.label) || 'Play the demo') + '</span>');     demo.type = 'button';     demo.addEventListener('click', function () {-      if (cfg.demo && typeof cfg.demo.onPlay === 'function') cfg.demo.onPlay();+      if (video) video.play().catch(function () { toast('Press Play on the video to start playback.', 'info'); });+      else if (cfg.demo && typeof cfg.demo.onPlay === 'function') cfg.demo.onPlay();       else toast('This dashboard has no demo wired up yet', 'err');     });-    left.appendChild(demo);-    if (cfg.demo && cfg.demo.meta) left.appendChild(el('span', 'anp-meta', esc(cfg.demo.meta)));+    controls.appendChild(demo);+    if (cfg.demo && cfg.demo.meta) controls.appendChild(el('span', 'anp-meta', esc(cfg.demo.meta)));     panel.appendChild(left);      var right = el('div', 'anp-right');@@ -411,6 +438,7 @@      * open pins it open forever and graduating it closed marks it user-dismissed. It looks      * like "the panel sometimes ignores graduation". (lbr, 2026-08-25) */     function setOpen(open, remember, source) {+      if (!open && video) video.pause();       panel.hidden = !open;       wrap.style.display = open ? '' : 'none';   /* no dead margin when collapsed */       if (toggleBtn) {@@ -457,7 +485,7 @@       close:  function (o) { setOpen(false, !(o && o.remember === false), 'api'); },       toggle: function () { setOpen(panel.hidden, true, 'api'); },       isOpen: function () { return !panel.hidden; },-      overBudget: over, toast: toast, selfCheck: function () { return selfCheck(panel); }+      video: video, overBudget: over, toast: toast, selfCheck: function () { return selfCheck(panel); }     };   } @@ -532,8 +560,22 @@     if (!aligned) ok = false;     rows.push({ what: 'copy icons in one column', want: '1 x-position', got: Object.keys(xs).length + ' x-positions', pass: aligned });     var cols = cs.gridTemplateColumns.split(' ').filter(Boolean).length;-    rows.push({ what: 'columns at this width', want: panel.getBoundingClientRect().width >= TWO_COL_MIN ? '2' : '1',-                got: String(cols), pass: true });+    var withVideo = panel.classList.contains('anp--video');+    var expectedCols = panel.parentElement.clientWidth >= (withVideo ? 1100 : TWO_COL_MIN) ? 2 : 1;+    var colsOk = cols === expectedCols;+    if (!colsOk) ok = false;+    rows.push({ what: 'columns at this width', want: String(expectedCols), got: String(cols), pass: colsOk });+    if (withVideo) {+      var vr = panel.querySelector('.anp-video').getBoundingClientRect();+      var lr = panel.querySelector('.anp-left').getBoundingClientRect();+      var rr = panel.querySelector('.anp-right').getBoundingClientRect();+      var mediaOk = Math.abs(vr.width - lr.width) < 2 && vr.width >= Math.min(300, lr.width) &&+                    Math.abs(vr.height - vr.width * 9 / 16) < 2 &&+                    (expectedCols === 1 ? rr.top >= lr.bottom - 1 : rr.left >= vr.right);+      if (!mediaOk) ok = false;+      rows.push({what:'video size and placement', want:'16:9, fills its column, no prompt overlap',+        got:Math.round(vr.width)+' × '+Math.round(vr.height)+'px', pass:mediaOk});+    }     /* THE GUTTER ROW. Four apps measured at the SAME 744px pane reported panel widths of      * 734, 735, 702 and 694, because each page put a different amount of its own padding      * around the mount point. 694 is under the 700 threshold, so that app rendered ONE@@ -601,7 +643,12 @@      * remeasured with this probe, neither had less than 141px of slack. A metric that can only      * return one value on success is not evidence of success. */     var spans = [].slice.call(panel.querySelectorAll('.anp-phrase span'));-    if (spans.length) {+    if (withVideo && spans.length) {+      var wrapOk = spans.every(function (sp) { return sp.scrollWidth <= sp.clientWidth + 1 &&+        getComputedStyle(sp).whiteSpace === 'normal'; });+      if (!wrapOk) ok = false;+      rows.push({what:'readable video-panel prompts',want:'wrap without horizontal clipping',got:wrapOk?'all readable':'clipped or nowrap',pass:wrapOk});+    } else if (spans.length) {       var probe = document.createElement('span');       probe.style.cssText = 'position:absolute;visibility:hidden;white-space:nowrap;font:' +                             getComputedStyle(spans[0]).font;@@ -686,6 +733,6 @@   }    root.AdomNewbiePanel = { mount: mount, toast: toast, selfCheck: selfCheck, copyText: copyText,-                           checkSend: checkSend, version: '2.22.0',+                           checkSend: checkSend, version: '2.23.0',                            TWO_COL_MIN: TWO_COL_MIN, PHRASE_BUDGET: PHRASE_BUDGET }; })(typeof window !== 'undefined' ? window : this);
package.json+2−2
@@ -1,6 +1,6 @@ {   "slug": "adom-ui-design",-  "version": "3.1.0",+  "version": "3.2.0",   "type": "skill",   "description": "The Adom UI Design System plus a bundle of focused UI skills (tooltips, floating-windows, toasts, responsive-ui, app-shell, ai-driven-apps, clickable-paths, dark-default, input-feedback, settings-layout). One install drops the whole set of skill files onto your container, plus shared icon assets (the settings cog) so every Adom app draws the same glyph.",   "dependencies": {},@@ -113,4 +113,4 @@     "type": "image",     "path": "screenshots/hero.png"   }-}\ No newline at end of file+}
page.json+2−2
@@ -1,6 +1,6 @@ {   "slug": "adom-ui-design",-  "version": "3.1.0",+  "version": "3.2.0",   "type": "skill",   "description": "The Adom UI Design System plus a bundle of focused UI skills (tooltips, floating-windows, toasts, responsive-ui, app-shell, ai-driven-apps, clickable-paths, dark-default, input-feedback, settings-layout). One install drops the whole set of skill files onto your container, plus shared icon assets (the settings cog) so every Adom app draws the same glyph.",   "dependencies": {},@@ -122,4 +122,4 @@   ],   "visibility": "public",   "org": "adom"-}\ No newline at end of file+}
skills/newbie-panel/SKILL.md+20
@@ -67,6 +67,26 @@ column. Every one of those apps had read this skill. **A written spec is not a m </script> ``` +### Inline video — module 2.23.0++Pass `demo.video` to opt into the responsive player layout. Do not append a video into `.anp-left`: the legacy button column is fixed at 220px and switches to a horizontal row on narrow panes. That squeezes a player and can overflow its controls.++```js+AdomNewbiePanel.mount({+  mount: '#newbie', app: 'codex', appName: 'Codex', slug: 'codex',+  toggleButton: '#help',+  demo: {+    label: 'Play the Fusion demo', meta: '3:43 · narrated',+    video: {src: '/demo.mp4', poster: '/poster.jpg', title: 'Fusion routing demo'}+  },+  phrases: ['Codex Astra, reproduce this Fusion demo']+});+```++The module owns a 16:9 player, its controls and wrapping toolbar. At a panel-container width of 1100px or more, video and prompts sit beside each other; below that, they stack with the video first. The desktop media column is capped at 720px. Video-panel prompts wrap so narrow panes retain the entire instruction. The normal button-only layout is unchanged. Closing the panel pauses its video; reopening never starts audio automatically. `mount()` returns `video` for an app that needs to coordinate another player. When video is configured, the shared Play button plays that video; omit a separate `onPlay` handler.++`selfCheck()` checks actual column count, video ratio/column fit, prompt overlap and wrapping. Verify the real Hydrogen pane at narrow, intermediate and wide widths, and capture open and closed states. Also test immediately below and above the 1100px container breakpoint; pane chrome and scrollbars mean the viewport width is not the container width. Apps adopt the fix by vendoring this module and passing `demo.video`, then publishing their own package; installing this design skill alone does not rewrite already-vendored dashboards.+ ### ⛔ VENDOR THE FILE INTO YOUR OWN PACKAGE. NEVER READ IT FROM THE PACK PATH.  Copy `newbie-panel.js` into your app's own package and serve it from there. **Do not point a