@supersuit/artifacts 0.4.6 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
`@supersuit/artifacts`. One entry per version, newest first. Each entry says what changed, how a
|
|
4
4
|
host can tell whether it is affected (DETECTOR), what a host does about it (REMEDY), and the tests.
|
|
5
5
|
|
|
6
|
+
## 0.4.7 (2026-09-27)
|
|
7
|
+
|
|
8
|
+
**A page of videos opens as fast as a page of text.**
|
|
9
|
+
|
|
10
|
+
- 0.4.6 marked every inline video `autoplay`, so all of them downloaded the moment the page opened
|
|
11
|
+
(21 diagrams, 6.8 MB, competing with the text and narration on a phone). The markup now carries
|
|
12
|
+
`preload="metadata"` and `data-artifact-video` instead, and a small client component,
|
|
13
|
+
`VideoAutoplay`, mounted only on pages that embed an `.mp4`, loads and plays each video as it
|
|
14
|
+
comes within a screen of view and pauses it when it leaves.
|
|
15
|
+
- **DETECTOR:** a page with many inline videos is slow to become readable on cellular.
|
|
16
|
+
- **REMEDY:** update the dependency; no host change.
|
|
17
|
+
- **Tests:** the markup no longer autoplays and preloads only metadata; `VideoAutoplay` plays a
|
|
18
|
+
video only once it intersects, raises its preload, and pauses it when it leaves.
|
|
19
|
+
|
|
6
20
|
## 0.4.6 (2026-09-27)
|
|
7
21
|
|
|
8
22
|
**Pages play video: `` is a silent, looping, autoplaying inline video.**
|
package/lib/artifacts/render.js
CHANGED
|
@@ -3,6 +3,7 @@ import ReactMarkdown from 'react-markdown';
|
|
|
3
3
|
import remarkGfm from 'remark-gfm';
|
|
4
4
|
import { hasNotesWidget, headingsOf } from './widgets.js';
|
|
5
5
|
import { HeadingNotes, NoteToggle, NotesEarlier, NotesProvider } from '../widgets/notes.js';
|
|
6
|
+
import { VideoAutoplay } from '../reader/video-autoplay.js';
|
|
6
7
|
// Every colour is a theme variable (brand/theme.ts), so a page follows its light, dark or system mode.
|
|
7
8
|
const GOLD = 'var(--a-accent)';
|
|
8
9
|
function hostOf(url) {
|
|
@@ -49,12 +50,14 @@ export function isReachableHref(href) {
|
|
|
49
50
|
* is decoded frame by frame on the CPU and stuttered on an iPhone at 24fps (2026-09-27); video
|
|
50
51
|
* is decoded in hardware. */
|
|
51
52
|
const VIDEO = /\.mp4(?:[?#].*)?$/i;
|
|
53
|
+
const VIDEO_IN_MD = /\]\([^)\s]+\.mp4(?:[?#][^)]*)?\)/i;
|
|
52
54
|
const escapeAttr = (s) => s.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
53
55
|
// Written as markup, not JSX: React renders `muted` as a property and leaves it out of server
|
|
54
|
-
// HTML, and iOS refuses to
|
|
55
|
-
// iPhone from going full screen.
|
|
56
|
+
// HTML, and iOS refuses to play a video unprompted whose markup is not muted. `playsinline` keeps
|
|
57
|
+
// iPhone from going full screen. No `autoplay`: VideoAutoplay starts each one as it nears the
|
|
58
|
+
// screen, so a page of videos does not download them all on open.
|
|
56
59
|
function InlineVideo({ src, label }) {
|
|
57
|
-
const html = `<video src="${escapeAttr(src)}" aria-label="${escapeAttr(label)}"
|
|
60
|
+
const html = `<video src="${escapeAttr(src)}" aria-label="${escapeAttr(label)}" data-artifact-video muted loop playsinline preload="metadata" disablepictureinpicture class="block w-full"></video>`;
|
|
58
61
|
return _jsx("span", { className: "my-6 block overflow-hidden rounded-lg border border-[color:var(--a-line)]", dangerouslySetInnerHTML: { __html: html } });
|
|
59
62
|
}
|
|
60
63
|
const components = {
|
|
@@ -148,5 +151,5 @@ export function ArtifactMarkdown({ markdown, notes }) {
|
|
|
148
151
|
const body = (_jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], components: on ? notesComponents(headings) : pageComponents(headings), children: markdown }));
|
|
149
152
|
return (
|
|
150
153
|
// break-words: a bare URL in prose is one unbreakable word and pushed phones 7px sideways (2026-09-26).
|
|
151
|
-
|
|
154
|
+
_jsxs("div", { className: "break-words text-[17px] text-[color:var(--a-body)]", children: [on ? (_jsx(NotesProvider, { artifactId: notes.artifactId, headings: headings.map(({ slug, text }) => ({ slug, text })), accent: notes.accent, children: body })) : body, VIDEO_IN_MD.test(markdown) ? _jsx(VideoAutoplay, {}) : null] }));
|
|
152
155
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function VideoAutoplay(): null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
// Plays each inline video only while it is near the screen. With `autoplay` in the markup every
|
|
3
|
+
// video on the page downloads the moment it opens (the Freedom whitepaper: 21 diagrams, 6.8 MB,
|
|
4
|
+
// competing with the text and the narration on a phone). Here a video loads and plays as it
|
|
5
|
+
// comes within a screen of view and pauses when it leaves, so opening the page costs the text.
|
|
6
|
+
import { useEffect } from 'react';
|
|
7
|
+
export function VideoAutoplay() {
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const videos = Array.from(document.querySelectorAll('video[data-artifact-video]'));
|
|
10
|
+
if (!videos.length || typeof IntersectionObserver === 'undefined') {
|
|
11
|
+
for (const v of videos)
|
|
12
|
+
void v.play?.().catch(() => { });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const io = new IntersectionObserver((entries) => {
|
|
16
|
+
for (const e of entries) {
|
|
17
|
+
const v = e.target;
|
|
18
|
+
if (e.isIntersecting) {
|
|
19
|
+
v.preload = 'auto';
|
|
20
|
+
void v.play().catch(() => { });
|
|
21
|
+
}
|
|
22
|
+
else
|
|
23
|
+
v.pause();
|
|
24
|
+
}
|
|
25
|
+
}, { rootMargin: '100% 0px' });
|
|
26
|
+
for (const v of videos)
|
|
27
|
+
io.observe(v);
|
|
28
|
+
return () => io.disconnect();
|
|
29
|
+
}, []);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supersuit/artifacts",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "Publish markdown as branded, read-aloud pages from a Next.js app: the artifacts store, the route factory, the read-along reader, brand packs, password and confidential pages, and share cards.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|