@dataimago/ui 0.1.0-alpha.4 → 0.1.0-alpha.43
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/README.md +31 -5
- package/dist/index.cjs +1086 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +386 -1
- package/dist/index.d.ts +386 -1
- package/dist/index.js +1066 -1
- package/dist/index.js.map +1 -1
- package/package.json +12 -9
package/dist/index.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
"use strict";
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
@@ -30,10 +31,36 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
31
|
// src/index.ts
|
|
31
32
|
var index_exports = {};
|
|
32
33
|
__export(index_exports, {
|
|
34
|
+
BrandAnimation: () => BrandAnimation,
|
|
33
35
|
Button: () => Button,
|
|
36
|
+
CAPTIONS_SCHEMA: () => CAPTIONS_SCHEMA,
|
|
37
|
+
CAPTIONS_STORAGE_KEY: () => CAPTIONS_STORAGE_KEY,
|
|
38
|
+
CaptionLayer: () => CaptionLayer,
|
|
39
|
+
CaptionedVideo: () => CaptionedVideo,
|
|
40
|
+
CopulaLockup: () => CopulaLockup,
|
|
41
|
+
Logo: () => Logo,
|
|
34
42
|
LogoMonogram: () => LogoMonogram,
|
|
35
43
|
LogoMonogramBg: () => LogoMonogramBg,
|
|
36
|
-
LogoWordmark: () => LogoWordmark
|
|
44
|
+
LogoWordmark: () => LogoWordmark,
|
|
45
|
+
NavbarLogo: () => NavbarLogo,
|
|
46
|
+
THEME_ATTRIBUTE: () => THEME_ATTRIBUTE,
|
|
47
|
+
THEME_STORAGE_KEY: () => THEME_STORAGE_KEY,
|
|
48
|
+
ThemeScript: () => ThemeScript,
|
|
49
|
+
ThemeToggle: () => ThemeToggle,
|
|
50
|
+
captionLook: () => captionLook,
|
|
51
|
+
isCaptionTrack: () => isCaptionTrack,
|
|
52
|
+
nextThemePreference: () => nextThemePreference,
|
|
53
|
+
pageAt: () => pageAt,
|
|
54
|
+
paletteVar: () => paletteVar,
|
|
55
|
+
readThemePreference: () => readThemePreference,
|
|
56
|
+
resolveTheme: () => resolveTheme,
|
|
57
|
+
setThemePreference: () => setThemePreference,
|
|
58
|
+
splitMath: () => splitMath,
|
|
59
|
+
themeInitScript: () => themeInitScript,
|
|
60
|
+
toneColor: () => toneColor,
|
|
61
|
+
useResolvedTheme: () => useResolvedTheme,
|
|
62
|
+
useThemePreference: () => useThemePreference,
|
|
63
|
+
wordTone: () => wordTone
|
|
37
64
|
});
|
|
38
65
|
module.exports = __toCommonJS(index_exports);
|
|
39
66
|
|
|
@@ -249,11 +276,1068 @@ function LogoWordmark({
|
|
|
249
276
|
}
|
|
250
277
|
);
|
|
251
278
|
}
|
|
279
|
+
|
|
280
|
+
// src/brand/Logo.tsx
|
|
281
|
+
var import_brand = require("@dataimago/brand");
|
|
282
|
+
|
|
283
|
+
// src/brand/decorative.ts
|
|
284
|
+
function silenceSvg(markup) {
|
|
285
|
+
return markup.replace(/<title\b[^>]*>[\s\S]*?<\/title>/gi, "").replace(/<svg\b([^>]*)>/i, (_m, attrs) => {
|
|
286
|
+
const kept = attrs.replace(/\srole="[^"]*"/i, "").replace(/\saria-label="[^"]*"/i, "").replace(/\saria-labelledby="[^"]*"/i, "");
|
|
287
|
+
return `<svg${kept} aria-hidden="true" focusable="false">`;
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
function isDecorative(ariaHidden) {
|
|
291
|
+
return ariaHidden === true || ariaHidden === "true";
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// src/brand/Logo.tsx
|
|
295
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
296
|
+
function Logo({ name, variant = "PATH", width, style, ...rest }) {
|
|
297
|
+
const key = `${name}_${variant}`;
|
|
298
|
+
let markup = import_brand.svgs[key];
|
|
299
|
+
if (!markup) {
|
|
300
|
+
console.warn(`[@dataimago/ui] <Logo>: unknown mark "${key}".`);
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
if (width != null) {
|
|
304
|
+
markup = markup.replace(/\swidth="[^"]*"/, ` width="${width}"`).replace(/\sheight="[^"]*"/, "");
|
|
305
|
+
}
|
|
306
|
+
if (isDecorative(rest["aria-hidden"])) markup = silenceSvg(markup);
|
|
307
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
308
|
+
"span",
|
|
309
|
+
{
|
|
310
|
+
style: { display: "inline-flex", ...style },
|
|
311
|
+
dangerouslySetInnerHTML: { __html: markup },
|
|
312
|
+
...rest
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/brand/CopulaLockup.tsx
|
|
318
|
+
var import_brand2 = require("@dataimago/brand");
|
|
319
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
320
|
+
var BADGE = 16 / 32;
|
|
321
|
+
var GAP = 19.7 / 32;
|
|
322
|
+
var CROP = 10 / 32;
|
|
323
|
+
var VISUALLY_HIDDEN = {
|
|
324
|
+
border: 0,
|
|
325
|
+
clip: "rect(0 0 0 0)",
|
|
326
|
+
height: 1,
|
|
327
|
+
margin: -1,
|
|
328
|
+
overflow: "hidden",
|
|
329
|
+
padding: 0,
|
|
330
|
+
position: "absolute",
|
|
331
|
+
whiteSpace: "nowrap",
|
|
332
|
+
width: 1
|
|
333
|
+
};
|
|
334
|
+
var round = (n) => Math.round(n * 100) / 100;
|
|
335
|
+
function pathEntry(id) {
|
|
336
|
+
return import_brand2.manifest.find((e) => e.id === id && e.variant === "PATH");
|
|
337
|
+
}
|
|
338
|
+
var plainLink = ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { ...props, children });
|
|
339
|
+
function CopulaLockup({
|
|
340
|
+
property,
|
|
341
|
+
propertyLabel,
|
|
342
|
+
propertyHref = "/",
|
|
343
|
+
dataimago = "shown",
|
|
344
|
+
brandHref = "https://dataimago.ai",
|
|
345
|
+
height = 32,
|
|
346
|
+
linkClassName,
|
|
347
|
+
badgeClassName,
|
|
348
|
+
renderLink = plainLink,
|
|
349
|
+
style,
|
|
350
|
+
...rest
|
|
351
|
+
}) {
|
|
352
|
+
const prop = pathEntry(property);
|
|
353
|
+
const brand = pathEntry("dataimago");
|
|
354
|
+
if (!prop || !brand) {
|
|
355
|
+
console.warn(`[@dataimago/ui] <CopulaLockup>: unknown mark "${property}".`);
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
const shown = dataimago === "shown";
|
|
359
|
+
const badge = BADGE * height;
|
|
360
|
+
const reach = GAP * height - badge / 2;
|
|
361
|
+
const crop = -CROP * height;
|
|
362
|
+
const width = (e) => round(e.w * height / e.h);
|
|
363
|
+
const trailing = (prop.w - prop.ink.x2) * height / prop.h;
|
|
364
|
+
const leading = brand.ink.x1 * height / brand.h;
|
|
365
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
366
|
+
"span",
|
|
367
|
+
{
|
|
368
|
+
style: { alignItems: "center", display: "inline-flex", flexWrap: "wrap", rowGap: 4, ...style },
|
|
369
|
+
...rest,
|
|
370
|
+
children: [
|
|
371
|
+
renderLink({
|
|
372
|
+
href: propertyHref,
|
|
373
|
+
"aria-label": propertyLabel,
|
|
374
|
+
className: linkClassName,
|
|
375
|
+
// The crop only centres the cap-height band on the badge; alone (hidden),
|
|
376
|
+
// the mark keeps its full box so nothing next to it can overlap it.
|
|
377
|
+
style: {
|
|
378
|
+
display: "inline-flex",
|
|
379
|
+
marginBlock: shown ? crop : 0,
|
|
380
|
+
marginRight: shown ? round(reach - trailing) : 0
|
|
381
|
+
},
|
|
382
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Logo, { name: property, "aria-hidden": true, width: width(prop) })
|
|
383
|
+
}),
|
|
384
|
+
shown ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
385
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: badgeClassName, style: { display: "inline-flex" }, children: [
|
|
386
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Logo, { name: "is_badge", variant: "CUTOUT", "aria-hidden": true, width: round(badge) }),
|
|
387
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: VISUALLY_HIDDEN, children: "is" })
|
|
388
|
+
] }),
|
|
389
|
+
renderLink({
|
|
390
|
+
href: brandHref,
|
|
391
|
+
"aria-label": "dataimago",
|
|
392
|
+
className: linkClassName,
|
|
393
|
+
style: { display: "inline-flex", marginBlock: crop, marginLeft: round(reach - leading) },
|
|
394
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Logo, { name: "dataimago", "aria-hidden": true, width: width(brand) })
|
|
395
|
+
})
|
|
396
|
+
] }) : null
|
|
397
|
+
]
|
|
398
|
+
}
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// src/brand/BrandAnimation.tsx
|
|
403
|
+
var React2 = __toESM(require("react"), 1);
|
|
404
|
+
var import_brand3 = require("@dataimago/brand");
|
|
405
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
406
|
+
function usePrefersReducedMotion() {
|
|
407
|
+
const [reduced, setReduced] = React2.useState(true);
|
|
408
|
+
React2.useEffect(() => {
|
|
409
|
+
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
410
|
+
setReduced(mq.matches);
|
|
411
|
+
const onChange = (e) => setReduced(e.matches);
|
|
412
|
+
mq.addEventListener("change", onChange);
|
|
413
|
+
return () => mq.removeEventListener("change", onChange);
|
|
414
|
+
}, []);
|
|
415
|
+
return reduced;
|
|
416
|
+
}
|
|
417
|
+
function BrandAnimation({
|
|
418
|
+
name,
|
|
419
|
+
width,
|
|
420
|
+
style,
|
|
421
|
+
...rest
|
|
422
|
+
}) {
|
|
423
|
+
const reduced = usePrefersReducedMotion();
|
|
424
|
+
const entry = import_brand3.animationManifest.find((a) => a.id === name);
|
|
425
|
+
let markup = import_brand3.animations[name];
|
|
426
|
+
if (!entry || !markup) {
|
|
427
|
+
console.warn(`[@dataimago/ui] <BrandAnimation>: unknown animation "${name}".`);
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
if (reduced) {
|
|
431
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
432
|
+
Logo,
|
|
433
|
+
{
|
|
434
|
+
name: entry.poster.id,
|
|
435
|
+
variant: entry.poster.variant,
|
|
436
|
+
width,
|
|
437
|
+
style,
|
|
438
|
+
...rest
|
|
439
|
+
}
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
if (width != null) {
|
|
443
|
+
markup = markup.replace(/\swidth="[^"]*"/, ` width="${width}"`).replace(/\sheight="[^"]*"/, "");
|
|
444
|
+
}
|
|
445
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
446
|
+
"span",
|
|
447
|
+
{
|
|
448
|
+
style: { display: "inline-flex", ...style },
|
|
449
|
+
dangerouslySetInnerHTML: { __html: markup },
|
|
450
|
+
...rest
|
|
451
|
+
}
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// src/brand/NavbarLogo.tsx
|
|
456
|
+
var React3 = __toESM(require("react"), 1);
|
|
457
|
+
var import_brand4 = require("@dataimago/brand");
|
|
458
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
459
|
+
function usePrefersReducedMotion2() {
|
|
460
|
+
const [reduced, setReduced] = React3.useState(true);
|
|
461
|
+
React3.useEffect(() => {
|
|
462
|
+
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
463
|
+
setReduced(mq.matches);
|
|
464
|
+
const onChange = (e) => setReduced(e.matches);
|
|
465
|
+
mq.addEventListener("change", onChange);
|
|
466
|
+
return () => mq.removeEventListener("change", onChange);
|
|
467
|
+
}, []);
|
|
468
|
+
return reduced;
|
|
469
|
+
}
|
|
470
|
+
function NavbarLogo({ name, width, style, ...rest }) {
|
|
471
|
+
const reduced = usePrefersReducedMotion2();
|
|
472
|
+
const entry = import_brand4.navbarManifest.find((n) => n.id === name);
|
|
473
|
+
let markup = import_brand4.navbarLogos[name];
|
|
474
|
+
if (!entry || !markup) {
|
|
475
|
+
console.warn(`[@dataimago/ui] <NavbarLogo>: unknown navbar mark "${name}".`);
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
if (reduced) {
|
|
479
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
480
|
+
Logo,
|
|
481
|
+
{
|
|
482
|
+
name: entry.poster.id,
|
|
483
|
+
variant: entry.poster.variant,
|
|
484
|
+
width,
|
|
485
|
+
style,
|
|
486
|
+
...rest
|
|
487
|
+
}
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
if (width != null) {
|
|
491
|
+
markup = markup.replace(/\swidth="[^"]*"/, ` width="${width}"`).replace(/\sheight="[^"]*"/, "");
|
|
492
|
+
}
|
|
493
|
+
if (isDecorative(rest["aria-hidden"])) markup = silenceSvg(markup);
|
|
494
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
495
|
+
"span",
|
|
496
|
+
{
|
|
497
|
+
style: { display: "inline-flex", ...style },
|
|
498
|
+
dangerouslySetInnerHTML: { __html: markup },
|
|
499
|
+
...rest
|
|
500
|
+
}
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// src/theme/ThemeToggle.tsx
|
|
505
|
+
var React5 = __toESM(require("react"), 1);
|
|
506
|
+
|
|
507
|
+
// src/theme/theme.ts
|
|
508
|
+
var React4 = __toESM(require("react"), 1);
|
|
509
|
+
var THEME_STORAGE_KEY = "dataimago-theme";
|
|
510
|
+
var THEME_ATTRIBUTE = "data-bs-theme";
|
|
511
|
+
var CHANGE_EVENT = "dataimago:themechange";
|
|
512
|
+
var themeInitScript = `(function(){try{var p=localStorage.getItem(${JSON.stringify(THEME_STORAGE_KEY)});if(p==='light'||p==='dark'){document.documentElement.setAttribute(${JSON.stringify(THEME_ATTRIBUTE)},p);}}catch(e){}})();`;
|
|
513
|
+
function isPreference(value) {
|
|
514
|
+
return value === "system" || value === "light" || value === "dark";
|
|
515
|
+
}
|
|
516
|
+
function readThemePreference() {
|
|
517
|
+
if (typeof window === "undefined") return "system";
|
|
518
|
+
try {
|
|
519
|
+
const stored = window.localStorage.getItem(THEME_STORAGE_KEY);
|
|
520
|
+
return isPreference(stored) ? stored : "system";
|
|
521
|
+
} catch {
|
|
522
|
+
return "system";
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
function applyAttribute(preference) {
|
|
526
|
+
const root = document.documentElement;
|
|
527
|
+
if (preference === "system") root.removeAttribute(THEME_ATTRIBUTE);
|
|
528
|
+
else root.setAttribute(THEME_ATTRIBUTE, preference);
|
|
529
|
+
}
|
|
530
|
+
function setThemePreference(preference) {
|
|
531
|
+
if (typeof window === "undefined") return;
|
|
532
|
+
try {
|
|
533
|
+
if (preference === "system") window.localStorage.removeItem(THEME_STORAGE_KEY);
|
|
534
|
+
else window.localStorage.setItem(THEME_STORAGE_KEY, preference);
|
|
535
|
+
} catch {
|
|
536
|
+
}
|
|
537
|
+
applyAttribute(preference);
|
|
538
|
+
window.dispatchEvent(new Event(CHANGE_EVENT));
|
|
539
|
+
}
|
|
540
|
+
function resolveTheme() {
|
|
541
|
+
if (typeof document === "undefined") return "light";
|
|
542
|
+
const forced = document.documentElement.getAttribute(THEME_ATTRIBUTE);
|
|
543
|
+
if (forced === "light" || forced === "dark") return forced;
|
|
544
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
545
|
+
}
|
|
546
|
+
function syncAttribute() {
|
|
547
|
+
applyAttribute(readThemePreference());
|
|
548
|
+
}
|
|
549
|
+
function subscribePreference(onChange) {
|
|
550
|
+
syncAttribute();
|
|
551
|
+
const onStorage = (e) => {
|
|
552
|
+
if (e.key !== null && e.key !== THEME_STORAGE_KEY) return;
|
|
553
|
+
applyAttribute(readThemePreference());
|
|
554
|
+
onChange();
|
|
555
|
+
};
|
|
556
|
+
window.addEventListener(CHANGE_EVENT, onChange);
|
|
557
|
+
window.addEventListener("storage", onStorage);
|
|
558
|
+
return () => {
|
|
559
|
+
window.removeEventListener(CHANGE_EVENT, onChange);
|
|
560
|
+
window.removeEventListener("storage", onStorage);
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function subscribeResolved(onChange) {
|
|
564
|
+
const observer = new MutationObserver(onChange);
|
|
565
|
+
observer.observe(document.documentElement, {
|
|
566
|
+
attributes: true,
|
|
567
|
+
attributeFilter: [THEME_ATTRIBUTE]
|
|
568
|
+
});
|
|
569
|
+
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
570
|
+
media.addEventListener("change", onChange);
|
|
571
|
+
window.addEventListener(CHANGE_EVENT, onChange);
|
|
572
|
+
syncAttribute();
|
|
573
|
+
return () => {
|
|
574
|
+
observer.disconnect();
|
|
575
|
+
media.removeEventListener("change", onChange);
|
|
576
|
+
window.removeEventListener(CHANGE_EVENT, onChange);
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
function useThemePreference() {
|
|
580
|
+
const preference = React4.useSyncExternalStore(
|
|
581
|
+
subscribePreference,
|
|
582
|
+
readThemePreference,
|
|
583
|
+
() => "system"
|
|
584
|
+
);
|
|
585
|
+
return [preference, setThemePreference];
|
|
586
|
+
}
|
|
587
|
+
function useResolvedTheme() {
|
|
588
|
+
return React4.useSyncExternalStore(subscribeResolved, resolveTheme, () => "light");
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// src/theme/ThemeToggle.tsx
|
|
592
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
593
|
+
var DEFAULT_LABELS = {
|
|
594
|
+
name: "Theme",
|
|
595
|
+
system: "System",
|
|
596
|
+
light: "Light",
|
|
597
|
+
dark: "Dark"
|
|
598
|
+
};
|
|
599
|
+
function systemTheme() {
|
|
600
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
601
|
+
}
|
|
602
|
+
function nextThemePreference(shown, system) {
|
|
603
|
+
const next = shown === "dark" ? "light" : "dark";
|
|
604
|
+
return next === system ? "system" : next;
|
|
605
|
+
}
|
|
606
|
+
var noSubscribe = () => () => {
|
|
607
|
+
};
|
|
608
|
+
function useHydrated() {
|
|
609
|
+
return React5.useSyncExternalStore(noSubscribe, () => true, () => false);
|
|
610
|
+
}
|
|
611
|
+
function Icon({ theme }) {
|
|
612
|
+
const common = {
|
|
613
|
+
width: 20,
|
|
614
|
+
height: 20,
|
|
615
|
+
viewBox: "0 0 24 24",
|
|
616
|
+
fill: "none",
|
|
617
|
+
stroke: "currentColor",
|
|
618
|
+
strokeWidth: 1.75,
|
|
619
|
+
strokeLinecap: "round",
|
|
620
|
+
strokeLinejoin: "round",
|
|
621
|
+
"aria-hidden": true,
|
|
622
|
+
focusable: false
|
|
623
|
+
};
|
|
624
|
+
if (theme === "dark") {
|
|
625
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { ...common, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" }) });
|
|
626
|
+
}
|
|
627
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { ...common, children: [
|
|
628
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "12", cy: "12", r: "4" }),
|
|
629
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" })
|
|
630
|
+
] });
|
|
631
|
+
}
|
|
632
|
+
function ThemeToggle({ labels, className = "", style, ...rest }) {
|
|
633
|
+
const [preference, setPreference] = useThemePreference();
|
|
634
|
+
const resolved = useResolvedTheme();
|
|
635
|
+
const hydrated = useHydrated();
|
|
636
|
+
const shown = hydrated ? resolved : void 0;
|
|
637
|
+
const text = { ...DEFAULT_LABELS, ...labels };
|
|
638
|
+
const label = shown ? `${text.name}: ${text[shown]}${preference === "system" ? ` (${text.system})` : ""}` : text.name;
|
|
639
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
640
|
+
"button",
|
|
641
|
+
{
|
|
642
|
+
...rest,
|
|
643
|
+
className: ["di-ui-theme-toggle", className].filter(Boolean).join(" "),
|
|
644
|
+
"aria-label": label,
|
|
645
|
+
title: label,
|
|
646
|
+
"data-theme-preference": preference,
|
|
647
|
+
"data-theme": shown,
|
|
648
|
+
style: {
|
|
649
|
+
alignItems: "center",
|
|
650
|
+
background: "var(--di-theme-toggle-bg, transparent)",
|
|
651
|
+
border: 0,
|
|
652
|
+
borderRadius: "var(--radius-md, 0.5rem)",
|
|
653
|
+
color: "var(--di-theme-toggle-fg, inherit)",
|
|
654
|
+
cursor: "pointer",
|
|
655
|
+
display: "inline-flex",
|
|
656
|
+
height: "48px",
|
|
657
|
+
justifyContent: "center",
|
|
658
|
+
padding: 0,
|
|
659
|
+
transition: "background-color 150ms ease",
|
|
660
|
+
width: "48px",
|
|
661
|
+
...style
|
|
662
|
+
},
|
|
663
|
+
type: "button",
|
|
664
|
+
onClick: () => setPreference(nextThemePreference(resolveTheme(), systemTheme())),
|
|
665
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { display: "inline-flex", visibility: shown ? "visible" : "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Icon, { theme: shown ?? "light" }) })
|
|
666
|
+
}
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/theme/ThemeScript.tsx
|
|
671
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
672
|
+
function ThemeScript({ nonce }) {
|
|
673
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("script", { nonce, dangerouslySetInnerHTML: { __html: themeInitScript } });
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// src/video/CaptionedVideo.tsx
|
|
677
|
+
var import_react2 = require("react");
|
|
678
|
+
|
|
679
|
+
// src/video/CaptionLayer.tsx
|
|
680
|
+
var import_react = require("react");
|
|
681
|
+
|
|
682
|
+
// src/video/captions.ts
|
|
683
|
+
var CAPTIONS_SCHEMA = "dataimago.video_captions.v1";
|
|
684
|
+
function pageAt(pages, t) {
|
|
685
|
+
let lo = 0, hi = pages.length - 1;
|
|
686
|
+
while (lo <= hi) {
|
|
687
|
+
const mid = lo + hi >> 1, p = pages[mid];
|
|
688
|
+
if (t < p.start) hi = mid - 1;
|
|
689
|
+
else if (t >= p.end) lo = mid + 1;
|
|
690
|
+
else return p;
|
|
691
|
+
}
|
|
692
|
+
return void 0;
|
|
693
|
+
}
|
|
694
|
+
function wordTone(at, t, span) {
|
|
695
|
+
if (span <= 0) return t >= at ? 1 : 0;
|
|
696
|
+
return Math.min(1, Math.max(0, (t - at) / span));
|
|
697
|
+
}
|
|
698
|
+
function toneColor(tone, ink, inkMute) {
|
|
699
|
+
if (tone >= 1) return ink;
|
|
700
|
+
if (tone <= 0) return inkMute;
|
|
701
|
+
return `color-mix(in srgb, ${ink} ${Math.round(tone * 100)}%, ${inkMute})`;
|
|
702
|
+
}
|
|
703
|
+
function splitMath(text) {
|
|
704
|
+
const m = text.match(/^(.*?)\$([^$\s]+)\$(.*)$/);
|
|
705
|
+
return m ? { before: m[1], tex: m[2], after: m[3] } : null;
|
|
706
|
+
}
|
|
707
|
+
function paletteVar(token) {
|
|
708
|
+
return /^color\.palette\.[a-z]+\.\d{2,3}$/.test(token) ? `var(--${token.replace(/\./g, "-")})` : null;
|
|
709
|
+
}
|
|
710
|
+
function captionLook(treatment, o) {
|
|
711
|
+
const { size, scale, ink, paper } = o;
|
|
712
|
+
const halo = [2, 6, 14, 24].map((r) => `0 0 ${r * scale}px ${paper}`).join(", ");
|
|
713
|
+
const panel = treatment === "boxed" ? { background: ink, padding: "0.35em 0.7em", borderRadius: 4 * scale } : treatment === "frosted" ? {
|
|
714
|
+
background: `color-mix(in srgb, ${paper} 82%, transparent)`,
|
|
715
|
+
backdropFilter: `blur(${14 * scale}px)`,
|
|
716
|
+
WebkitBackdropFilter: `blur(${14 * scale}px)`,
|
|
717
|
+
padding: "0.5em 0.95em",
|
|
718
|
+
borderRadius: size * 0.6,
|
|
719
|
+
boxShadow: `0 ${size * 0.15}px ${size * 0.8}px color-mix(in srgb, ${ink} 10%, transparent)`,
|
|
720
|
+
border: `${Math.max(1, scale)}px solid color-mix(in srgb, ${ink} 8%, transparent)`
|
|
721
|
+
} : { padding: "0.2em 0.4em", textShadow: halo };
|
|
722
|
+
const text = {
|
|
723
|
+
fontFamily: o.family,
|
|
724
|
+
fontWeight: treatment === "boxed" ? 400 : o.weight,
|
|
725
|
+
fontSize: size,
|
|
726
|
+
lineHeight: o.lineHeight,
|
|
727
|
+
textAlign: "center",
|
|
728
|
+
letterSpacing: treatment === "boxed" ? 0 : "0.005em",
|
|
729
|
+
...treatment === "boxed" ? { color: paper } : {}
|
|
730
|
+
};
|
|
731
|
+
return { panel, text, lift: treatment === "frosted" ? Math.round(size * 0.95) : 0, toned: treatment !== "boxed" };
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// src/video/mathml.ts
|
|
735
|
+
var MATHML_NS = "http://www.w3.org/1998/Math/MathML";
|
|
736
|
+
var ELEMENTS = /* @__PURE__ */ new Set([
|
|
737
|
+
"math",
|
|
738
|
+
"mrow",
|
|
739
|
+
"mi",
|
|
740
|
+
"mn",
|
|
741
|
+
"mo",
|
|
742
|
+
"ms",
|
|
743
|
+
"mtext",
|
|
744
|
+
"mspace",
|
|
745
|
+
"msub",
|
|
746
|
+
"msup",
|
|
747
|
+
"msubsup",
|
|
748
|
+
"mfrac",
|
|
749
|
+
"msqrt",
|
|
750
|
+
"mroot",
|
|
751
|
+
"mover",
|
|
752
|
+
"munder",
|
|
753
|
+
"munderover",
|
|
754
|
+
"mtable",
|
|
755
|
+
"mtr",
|
|
756
|
+
"mtd",
|
|
757
|
+
"mpadded",
|
|
758
|
+
"mphantom",
|
|
759
|
+
"mstyle",
|
|
760
|
+
"menclose",
|
|
761
|
+
"semantics",
|
|
762
|
+
"annotation"
|
|
763
|
+
]);
|
|
764
|
+
var ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
765
|
+
"display",
|
|
766
|
+
"mathvariant",
|
|
767
|
+
"stretchy",
|
|
768
|
+
"fence",
|
|
769
|
+
"separator",
|
|
770
|
+
"form",
|
|
771
|
+
"lspace",
|
|
772
|
+
"rspace",
|
|
773
|
+
"accent",
|
|
774
|
+
"accentunder",
|
|
775
|
+
"movablelimits",
|
|
776
|
+
"largeop",
|
|
777
|
+
"symmetric",
|
|
778
|
+
"minsize",
|
|
779
|
+
"maxsize",
|
|
780
|
+
"width",
|
|
781
|
+
"height",
|
|
782
|
+
"depth",
|
|
783
|
+
"voffset",
|
|
784
|
+
"linethickness",
|
|
785
|
+
"columnalign",
|
|
786
|
+
"rowalign",
|
|
787
|
+
"columnspacing",
|
|
788
|
+
"rowspacing",
|
|
789
|
+
"scriptlevel",
|
|
790
|
+
"displaystyle",
|
|
791
|
+
"notation",
|
|
792
|
+
"encoding"
|
|
793
|
+
]);
|
|
794
|
+
function rebuild(node, doc) {
|
|
795
|
+
if (node.nodeType === Node.TEXT_NODE) return doc.createTextNode(node.textContent ?? "");
|
|
796
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return null;
|
|
797
|
+
const el = node;
|
|
798
|
+
const tag = el.localName;
|
|
799
|
+
if (el.namespaceURI !== MATHML_NS || !ELEMENTS.has(tag)) return null;
|
|
800
|
+
if (tag === "annotation") return null;
|
|
801
|
+
const out = doc.createElementNS(MATHML_NS, tag);
|
|
802
|
+
for (const a of Array.from(el.attributes)) {
|
|
803
|
+
if (!ATTRIBUTES.has(a.name)) continue;
|
|
804
|
+
out.setAttribute(a.name, a.value);
|
|
805
|
+
}
|
|
806
|
+
for (const child of Array.from(el.childNodes)) {
|
|
807
|
+
const c = rebuild(child, doc);
|
|
808
|
+
if (c) out.appendChild(c);
|
|
809
|
+
}
|
|
810
|
+
return out;
|
|
811
|
+
}
|
|
812
|
+
var cache = /* @__PURE__ */ new Map();
|
|
813
|
+
function safeMathML(markup, doc = document) {
|
|
814
|
+
if (!cache.has(markup)) {
|
|
815
|
+
const parsed = new DOMParser().parseFromString(`<!doctype html><body>${markup}`, "text/html");
|
|
816
|
+
const root = parsed.body.firstElementChild;
|
|
817
|
+
const math = root && root.localName === "math" ? rebuild(root, doc) : null;
|
|
818
|
+
cache.set(markup, math);
|
|
819
|
+
}
|
|
820
|
+
const hit = cache.get(markup);
|
|
821
|
+
return hit ? hit.cloneNode(true) : null;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/video/CaptionLayer.tsx
|
|
825
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
826
|
+
var INK = "var(--color-palette-ink-900)";
|
|
827
|
+
var INK_MUTE = "var(--color-palette-ink-500)";
|
|
828
|
+
var PAPER = "var(--color-palette-stone-50)";
|
|
829
|
+
var MathSpan = ({ markup, tex }) => {
|
|
830
|
+
const ref = (0, import_react.useRef)(null);
|
|
831
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
832
|
+
const host = ref.current;
|
|
833
|
+
if (!host) return;
|
|
834
|
+
const math = safeMathML(markup, host.ownerDocument);
|
|
835
|
+
if (!math) {
|
|
836
|
+
host.textContent = tex;
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
math.style.fontFamily = "var(--font-family-math)";
|
|
840
|
+
math.style.fontStyle = "normal";
|
|
841
|
+
host.replaceChildren(math);
|
|
842
|
+
}, [markup, tex]);
|
|
843
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { ref, style: { whiteSpace: "nowrap" } });
|
|
844
|
+
};
|
|
845
|
+
var CaptionLayer = ({ track, time, width, reducedMotion = true, clearance = 0, page: given }) => {
|
|
846
|
+
const page = given === void 0 ? pageAt(track.pages, time) : given;
|
|
847
|
+
const scale = width / track.frame.width;
|
|
848
|
+
const size = track.frame.fontSize * scale;
|
|
849
|
+
const { safe } = track.frame;
|
|
850
|
+
const c = track.style.colors;
|
|
851
|
+
const ink = (c && paletteVar(c.ink)) ?? INK, inkMute = (c && paletteVar(c.inkMute)) ?? INK_MUTE, paper = (c && paletteVar(c.paper)) ?? PAPER;
|
|
852
|
+
const look = captionLook(track.style.treatment, {
|
|
853
|
+
size,
|
|
854
|
+
scale,
|
|
855
|
+
weight: track.style.weight,
|
|
856
|
+
lineHeight: track.style.lineHeight,
|
|
857
|
+
family: "var(--font-family-sans)",
|
|
858
|
+
ink,
|
|
859
|
+
paper
|
|
860
|
+
});
|
|
861
|
+
const toned = look.toned && !reducedMotion && given === void 0;
|
|
862
|
+
const lines = page ? page.lines.map((_, li) => page.words.filter((w) => w.line === li)) : [];
|
|
863
|
+
const plain = page ? page.lines.map((l) => l.replace(/\$([^$\s]+)\$/g, "$1")).join(" ") : "";
|
|
864
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
865
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { "data-caption-text": "", lang: track.lang || void 0, style: { position: "absolute", width: 1, height: 1, margin: -1, padding: 0, overflow: "hidden", clip: "rect(0 0 0 0)", clipPath: "inset(50%)", whiteSpace: "nowrap", border: 0 }, children: plain }),
|
|
866
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { "aria-hidden": "true", style: { position: "absolute", inset: 0, pointerEvents: "none", overflow: "hidden" }, children: page ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: {
|
|
867
|
+
position: "absolute",
|
|
868
|
+
left: safe.left * scale,
|
|
869
|
+
right: safe.right * scale,
|
|
870
|
+
bottom: Math.max(safe.bottom * scale + look.lift, clearance),
|
|
871
|
+
display: "flex",
|
|
872
|
+
justifyContent: "center"
|
|
873
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { ...look.panel, ...look.text }, children: lines.map((ws, li) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { children: ws.map((w, wi) => {
|
|
874
|
+
const m = splitMath(w.text);
|
|
875
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { style: look.toned ? { color: toned ? toneColor(wordTone(w.at, time, track.style.toneSeconds), ink, inkMute) : ink } : void 0, children: [
|
|
876
|
+
m && w.math ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
877
|
+
m.before,
|
|
878
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MathSpan, { markup: w.math, tex: m.tex }),
|
|
879
|
+
m.after
|
|
880
|
+
] }) : w.text,
|
|
881
|
+
wi < ws.length - 1 ? " " : ""
|
|
882
|
+
] }, wi);
|
|
883
|
+
}) }, li)) }) }) : null })
|
|
884
|
+
] });
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
// src/video/CaptionedVideo.tsx
|
|
888
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
889
|
+
var CAPTIONS_STORAGE_KEY = "dataimago:video-captions";
|
|
890
|
+
var readCaptionsChoice = () => {
|
|
891
|
+
try {
|
|
892
|
+
return window.localStorage.getItem(CAPTIONS_STORAGE_KEY) !== "off";
|
|
893
|
+
} catch {
|
|
894
|
+
return true;
|
|
895
|
+
}
|
|
896
|
+
};
|
|
897
|
+
var writeCaptionsChoice = (on) => {
|
|
898
|
+
try {
|
|
899
|
+
window.localStorage.setItem(CAPTIONS_STORAGE_KEY, on ? "on" : "off");
|
|
900
|
+
} catch {
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
var num = (x) => typeof x === "number" && Number.isFinite(x);
|
|
904
|
+
var strings = (x) => Array.isArray(x) && x.every((y) => typeof y === "string");
|
|
905
|
+
function isCaptionTrack(x) {
|
|
906
|
+
const t = x;
|
|
907
|
+
if (!t || typeof t !== "object" || t.schema !== CAPTIONS_SCHEMA) return false;
|
|
908
|
+
const f = t.frame, st = t.style;
|
|
909
|
+
if (!f || !num(f.width) || f.width <= 0 || !num(f.height) || f.height <= 0 || !num(f.fontSize) || f.fontSize <= 0 || !f.safe || !["top", "right", "bottom", "left"].every((k) => num(f.safe[k]))) return false;
|
|
910
|
+
if (!st || !["editorial", "frosted", "boxed"].includes(st.treatment) || !num(st.weight) || !num(st.toneSeconds) || !num(st.lineHeight)) return false;
|
|
911
|
+
if (st.colors !== void 0 && !(st.colors && [st.colors.paper, st.colors.ink, st.colors.inkMute].every((x2) => typeof x2 === "string" && paletteVar(x2)))) return false;
|
|
912
|
+
return Array.isArray(t.pages) && t.pages.length > 0 && t.pages.every((p, i) => p && num(p.start) && num(p.end) && p.start < p.end && (i === 0 || p.start >= t.pages[i - 1].end) && strings(p.lines) && p.lines.length > 0 && Array.isArray(p.words) && p.words.length > 0 && p.words.every((w) => w && typeof w.text === "string" && num(w.at) && Number.isInteger(w.line) && w.line >= 0 && w.line < p.lines.length && (w.math === void 0 || typeof w.math === "string")));
|
|
913
|
+
}
|
|
914
|
+
function cueLines(cue) {
|
|
915
|
+
let text = "";
|
|
916
|
+
const walk = (n) => {
|
|
917
|
+
if (n.nodeType === Node.TEXT_NODE) text += n.textContent ?? "";
|
|
918
|
+
else if (n.localName === "br") text += "\n";
|
|
919
|
+
else n.childNodes.forEach(walk);
|
|
920
|
+
};
|
|
921
|
+
walk(cue.getCueAsHTML());
|
|
922
|
+
return text.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
923
|
+
}
|
|
924
|
+
var CONTROLS_HOLD_MS = 2500;
|
|
925
|
+
var CONTROLS_HEIGHT = 56;
|
|
926
|
+
var usePrefersReducedMotion3 = () => {
|
|
927
|
+
const [reduced, setReduced] = (0, import_react2.useState)(null);
|
|
928
|
+
(0, import_react2.useEffect)(() => {
|
|
929
|
+
const q = window.matchMedia?.("(prefers-reduced-motion: reduce)");
|
|
930
|
+
if (!q) {
|
|
931
|
+
setReduced(false);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
setReduced(q.matches);
|
|
935
|
+
const on = () => setReduced(q.matches);
|
|
936
|
+
q.addEventListener("change", on);
|
|
937
|
+
return () => q.removeEventListener("change", on);
|
|
938
|
+
}, []);
|
|
939
|
+
return reduced;
|
|
940
|
+
};
|
|
941
|
+
var linkStyle = {
|
|
942
|
+
display: "inline-flex",
|
|
943
|
+
alignItems: "center",
|
|
944
|
+
justifyContent: "center",
|
|
945
|
+
minWidth: 48,
|
|
946
|
+
minHeight: 48,
|
|
947
|
+
padding: "0 8px",
|
|
948
|
+
color: "var(--color-palette-ink-900)",
|
|
949
|
+
textUnderlineOffset: "0.2em"
|
|
950
|
+
};
|
|
951
|
+
var videoOnlyFullscreen = () => typeof navigator !== "undefined" && /iPhone|iPod/.test(navigator.userAgent);
|
|
952
|
+
var gateStyle = {
|
|
953
|
+
position: "absolute",
|
|
954
|
+
left: "50%",
|
|
955
|
+
top: "50%",
|
|
956
|
+
transform: "translate(-50%, -50%)",
|
|
957
|
+
maxWidth: "min(90%, 26rem)",
|
|
958
|
+
display: "flex",
|
|
959
|
+
flexDirection: "column",
|
|
960
|
+
alignItems: "center",
|
|
961
|
+
gap: 10,
|
|
962
|
+
padding: "14px 18px",
|
|
963
|
+
textAlign: "center",
|
|
964
|
+
fontFamily: "var(--font-family-sans)",
|
|
965
|
+
fontSize: 15,
|
|
966
|
+
lineHeight: 1.4,
|
|
967
|
+
color: "var(--color-palette-ink-900)",
|
|
968
|
+
background: "var(--color-palette-stone-50)",
|
|
969
|
+
border: "1px solid var(--color-palette-ink-200)",
|
|
970
|
+
borderRadius: 6
|
|
971
|
+
};
|
|
972
|
+
var buttonStyle = {
|
|
973
|
+
font: "inherit",
|
|
974
|
+
fontFamily: "var(--font-family-sans)",
|
|
975
|
+
fontWeight: 600,
|
|
976
|
+
fontSize: 13,
|
|
977
|
+
lineHeight: 1,
|
|
978
|
+
color: "var(--color-palette-paper-50)",
|
|
979
|
+
background: "color-mix(in srgb, var(--color-palette-ink-900) 72%, transparent)",
|
|
980
|
+
border: "1px solid color-mix(in srgb, var(--color-palette-paper-50) 35%, transparent)",
|
|
981
|
+
borderRadius: 4,
|
|
982
|
+
padding: "6px 9px",
|
|
983
|
+
cursor: "pointer",
|
|
984
|
+
pointerEvents: "auto",
|
|
985
|
+
// the house floor for touch targets ([[accessibility-beyond-compliance]]), as ThemeToggle
|
|
986
|
+
minWidth: 48,
|
|
987
|
+
minHeight: 48
|
|
988
|
+
};
|
|
989
|
+
var NATIVE_FULLSCREEN_RULE = [
|
|
990
|
+
"video[data-dataimago-captioned-video]::-webkit-media-controls-fullscreen-button { display: none; }",
|
|
991
|
+
"video[data-dataimago-captioned-video]::-webkit-media-controls-toggle-closed-captions-button { display: none; }"
|
|
992
|
+
].join("\n");
|
|
993
|
+
var nativeRuleAdded = false;
|
|
994
|
+
function hideNativeFullscreenButton() {
|
|
995
|
+
if (nativeRuleAdded || typeof document === "undefined" || !("adoptedStyleSheets" in document)) return;
|
|
996
|
+
try {
|
|
997
|
+
const sheet = new CSSStyleSheet();
|
|
998
|
+
sheet.replaceSync(NATIVE_FULLSCREEN_RULE);
|
|
999
|
+
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
|
1000
|
+
nativeRuleAdded = true;
|
|
1001
|
+
} catch {
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
var CaptionedVideo = ({
|
|
1005
|
+
src,
|
|
1006
|
+
poster,
|
|
1007
|
+
captions,
|
|
1008
|
+
fallbackCaptions,
|
|
1009
|
+
chapters,
|
|
1010
|
+
reducedSrc,
|
|
1011
|
+
storyboard,
|
|
1012
|
+
title,
|
|
1013
|
+
lang = "en",
|
|
1014
|
+
className,
|
|
1015
|
+
style
|
|
1016
|
+
}) => {
|
|
1017
|
+
const wrapRef = (0, import_react2.useRef)(null);
|
|
1018
|
+
const stageRef = (0, import_react2.useRef)(null);
|
|
1019
|
+
const videoRef = (0, import_react2.useRef)(null);
|
|
1020
|
+
const trackRef = (0, import_react2.useRef)(null);
|
|
1021
|
+
const chaptersRef = (0, import_react2.useRef)(null);
|
|
1022
|
+
const [track, setTrack] = (0, import_react2.useState)(null);
|
|
1023
|
+
const [failed, setFailed] = (0, import_react2.useState)(false);
|
|
1024
|
+
const [cue, setCue] = (0, import_react2.useState)(null);
|
|
1025
|
+
const [time, setTime] = (0, import_react2.useState)(0);
|
|
1026
|
+
const [width, setWidth] = (0, import_react2.useState)(0);
|
|
1027
|
+
const [on, setOn] = (0, import_react2.useState)(true);
|
|
1028
|
+
const [full, setFull] = (0, import_react2.useState)(false);
|
|
1029
|
+
const [paused, setPaused] = (0, import_react2.useState)(true);
|
|
1030
|
+
const [active, setActive] = (0, import_react2.useState)(false);
|
|
1031
|
+
const [focusWithin, setFocusWithin] = (0, import_react2.useState)(false);
|
|
1032
|
+
const prefersReduced = usePrefersReducedMotion3();
|
|
1033
|
+
const reducedMotion = prefersReduced !== false;
|
|
1034
|
+
const [chosen, setChosen] = (0, import_react2.useState)(false);
|
|
1035
|
+
const noticeId = (0, import_react2.useId)();
|
|
1036
|
+
const gated = prefersReduced === true && !reducedSrc && !chosen;
|
|
1037
|
+
const videoSrc = prefersReduced && reducedSrc ? reducedSrc : prefersReduced === null && reducedSrc ? void 0 : src;
|
|
1038
|
+
(0, import_react2.useEffect)(() => setOn(readCaptionsChoice()), []);
|
|
1039
|
+
(0, import_react2.useEffect)(() => {
|
|
1040
|
+
let live = true;
|
|
1041
|
+
setTrack(null);
|
|
1042
|
+
setFailed(false);
|
|
1043
|
+
fetch(captions, { credentials: "same-origin" }).then((r) => r.ok ? r.json() : Promise.reject(new Error(String(r.status)))).then((j) => {
|
|
1044
|
+
if (!live) return;
|
|
1045
|
+
if (isCaptionTrack(j)) setTrack(j);
|
|
1046
|
+
else setFailed(true);
|
|
1047
|
+
}).catch(() => {
|
|
1048
|
+
if (live) setFailed(true);
|
|
1049
|
+
});
|
|
1050
|
+
return () => {
|
|
1051
|
+
live = false;
|
|
1052
|
+
};
|
|
1053
|
+
}, [captions]);
|
|
1054
|
+
(0, import_react2.useEffect)(() => {
|
|
1055
|
+
const el = trackRef.current, t = el?.track;
|
|
1056
|
+
if (!el || !t) return;
|
|
1057
|
+
const change = () => {
|
|
1058
|
+
const c = t.activeCues?.[0];
|
|
1059
|
+
const lines = c ? cueLines(c) : [];
|
|
1060
|
+
setCue(c ? { start: c.startTime, end: c.endTime, lines, words: lines.flatMap((l, line) => l.split(" ").map((w) => ({ text: w, at: c.startTime, line }))) } : null);
|
|
1061
|
+
};
|
|
1062
|
+
t.addEventListener("cuechange", change);
|
|
1063
|
+
el.addEventListener("load", change);
|
|
1064
|
+
t.mode = nativeViewRef.current && onRef.current ? "showing" : "hidden";
|
|
1065
|
+
change();
|
|
1066
|
+
return () => {
|
|
1067
|
+
t.removeEventListener("cuechange", change);
|
|
1068
|
+
el.removeEventListener("load", change);
|
|
1069
|
+
};
|
|
1070
|
+
}, [fallbackCaptions]);
|
|
1071
|
+
(0, import_react2.useEffect)(() => {
|
|
1072
|
+
const v = videoRef.current;
|
|
1073
|
+
if (!v) return;
|
|
1074
|
+
let raf = 0;
|
|
1075
|
+
const tick = () => {
|
|
1076
|
+
setTime(v.currentTime);
|
|
1077
|
+
raf = requestAnimationFrame(tick);
|
|
1078
|
+
};
|
|
1079
|
+
const sync = () => {
|
|
1080
|
+
setPaused(v.paused);
|
|
1081
|
+
setTime(v.currentTime);
|
|
1082
|
+
cancelAnimationFrame(raf);
|
|
1083
|
+
if (!v.paused && !v.ended) raf = requestAnimationFrame(tick);
|
|
1084
|
+
};
|
|
1085
|
+
const events = ["play", "playing", "pause", "seeking", "seeked", "timeupdate", "ended", "emptied", "loadstart", "loadedmetadata"];
|
|
1086
|
+
for (const e of events) v.addEventListener(e, sync);
|
|
1087
|
+
sync();
|
|
1088
|
+
return () => {
|
|
1089
|
+
cancelAnimationFrame(raf);
|
|
1090
|
+
for (const e of events) v.removeEventListener(e, sync);
|
|
1091
|
+
};
|
|
1092
|
+
}, [videoSrc]);
|
|
1093
|
+
(0, import_react2.useEffect)(() => {
|
|
1094
|
+
const el = stageRef.current;
|
|
1095
|
+
if (!el || typeof ResizeObserver === "undefined") return;
|
|
1096
|
+
const ro = new ResizeObserver(([e]) => setWidth(e.contentRect.width));
|
|
1097
|
+
ro.observe(el);
|
|
1098
|
+
return () => ro.disconnect();
|
|
1099
|
+
}, []);
|
|
1100
|
+
const [nativeView, setNativeView] = (0, import_react2.useState)(false);
|
|
1101
|
+
const nativeViewRef = (0, import_react2.useRef)(false), onRef = (0, import_react2.useRef)(true);
|
|
1102
|
+
nativeViewRef.current = nativeView;
|
|
1103
|
+
onRef.current = on;
|
|
1104
|
+
(0, import_react2.useEffect)(() => {
|
|
1105
|
+
const v = videoRef.current;
|
|
1106
|
+
if (!v) return;
|
|
1107
|
+
let fs = false, pip = false;
|
|
1108
|
+
const update = () => setNativeView(fs || pip);
|
|
1109
|
+
const change = () => {
|
|
1110
|
+
setFull(document.fullscreenElement === wrapRef.current);
|
|
1111
|
+
fs = document.fullscreenElement === v;
|
|
1112
|
+
update();
|
|
1113
|
+
};
|
|
1114
|
+
const flag = (set, x) => () => {
|
|
1115
|
+
set(x);
|
|
1116
|
+
update();
|
|
1117
|
+
};
|
|
1118
|
+
const handlers = [
|
|
1119
|
+
[document, "fullscreenchange", change],
|
|
1120
|
+
[v, "webkitbeginfullscreen", flag((x) => {
|
|
1121
|
+
fs = x;
|
|
1122
|
+
}, true)],
|
|
1123
|
+
[v, "webkitendfullscreen", flag((x) => {
|
|
1124
|
+
fs = x;
|
|
1125
|
+
}, false)],
|
|
1126
|
+
[v, "enterpictureinpicture", flag((x) => {
|
|
1127
|
+
pip = x;
|
|
1128
|
+
}, true)],
|
|
1129
|
+
[v, "leavepictureinpicture", flag((x) => {
|
|
1130
|
+
pip = x;
|
|
1131
|
+
}, false)]
|
|
1132
|
+
];
|
|
1133
|
+
for (const [t, e, h] of handlers) t.addEventListener(e, h);
|
|
1134
|
+
return () => {
|
|
1135
|
+
for (const [t, e, h] of handlers) t.removeEventListener(e, h);
|
|
1136
|
+
};
|
|
1137
|
+
}, []);
|
|
1138
|
+
(0, import_react2.useEffect)(() => {
|
|
1139
|
+
const v = videoRef.current;
|
|
1140
|
+
if (!v) return;
|
|
1141
|
+
const want = nativeView && on ? "showing" : "hidden";
|
|
1142
|
+
const hold = () => {
|
|
1143
|
+
const t = trackRef.current?.track;
|
|
1144
|
+
if (t && t.mode !== want) t.mode = want;
|
|
1145
|
+
};
|
|
1146
|
+
hold();
|
|
1147
|
+
v.textTracks.addEventListener("change", hold);
|
|
1148
|
+
trackRef.current?.addEventListener("load", hold);
|
|
1149
|
+
const el = trackRef.current;
|
|
1150
|
+
return () => {
|
|
1151
|
+
v.textTracks.removeEventListener("change", hold);
|
|
1152
|
+
el?.removeEventListener("load", hold);
|
|
1153
|
+
};
|
|
1154
|
+
}, [nativeView, on, fallbackCaptions]);
|
|
1155
|
+
(0, import_react2.useEffect)(hideNativeFullscreenButton, []);
|
|
1156
|
+
(0, import_react2.useEffect)(() => {
|
|
1157
|
+
const t = chaptersRef.current?.track;
|
|
1158
|
+
if (t) t.mode = "hidden";
|
|
1159
|
+
}, [chapters]);
|
|
1160
|
+
const holdRef = (0, import_react2.useRef)(0);
|
|
1161
|
+
const wake = (0, import_react2.useCallback)(() => {
|
|
1162
|
+
setActive(true);
|
|
1163
|
+
window.clearTimeout(holdRef.current);
|
|
1164
|
+
holdRef.current = window.setTimeout(() => setActive(false), CONTROLS_HOLD_MS);
|
|
1165
|
+
}, []);
|
|
1166
|
+
(0, import_react2.useEffect)(() => () => window.clearTimeout(holdRef.current), []);
|
|
1167
|
+
(0, import_react2.useEffect)(() => {
|
|
1168
|
+
const v = videoRef.current;
|
|
1169
|
+
if (!v) return;
|
|
1170
|
+
v.addEventListener("play", wake);
|
|
1171
|
+
return () => v.removeEventListener("play", wake);
|
|
1172
|
+
}, [wake]);
|
|
1173
|
+
const toggleCaptions = (0, import_react2.useCallback)(() => setOn((x) => {
|
|
1174
|
+
writeCaptionsChoice(!x);
|
|
1175
|
+
return !x;
|
|
1176
|
+
}), []);
|
|
1177
|
+
const toggleFullscreen = (0, import_react2.useCallback)(() => {
|
|
1178
|
+
const wrap = wrapRef.current;
|
|
1179
|
+
const v = videoRef.current;
|
|
1180
|
+
if (document.fullscreenElement === wrap) {
|
|
1181
|
+
void document.exitFullscreen();
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
if (v?.webkitDisplayingFullscreen) {
|
|
1185
|
+
v.webkitExitFullscreen?.();
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
if (!wrap?.requestFullscreen || !document.fullscreenEnabled || videoOnlyFullscreen()) {
|
|
1189
|
+
v?.webkitEnterFullscreen?.();
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
wrap.requestFullscreen().catch(() => v?.webkitEnterFullscreen?.());
|
|
1193
|
+
}, []);
|
|
1194
|
+
const onKey = (e) => {
|
|
1195
|
+
const t = e.target;
|
|
1196
|
+
if (e.altKey || e.ctrlKey || e.metaKey || t.isContentEditable || t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement || t instanceof HTMLSelectElement) return;
|
|
1197
|
+
if (e.key === "c" || e.key === "C") {
|
|
1198
|
+
e.preventDefault();
|
|
1199
|
+
toggleCaptions();
|
|
1200
|
+
}
|
|
1201
|
+
if (e.key === "f" || e.key === "F") {
|
|
1202
|
+
e.preventDefault();
|
|
1203
|
+
toggleFullscreen();
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
1206
|
+
const controlsUp = paused || active || focusWithin;
|
|
1207
|
+
const showLayer = on && width > 0 && !nativeView && (track !== null || cue !== null || failed);
|
|
1208
|
+
const layerTrack = track ?? { ...FALLBACK_FRAME, lang };
|
|
1209
|
+
const frame = layerTrack.frame;
|
|
1210
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1211
|
+
"div",
|
|
1212
|
+
{
|
|
1213
|
+
ref: wrapRef,
|
|
1214
|
+
className,
|
|
1215
|
+
onPointerMove: wake,
|
|
1216
|
+
onPointerDown: wake,
|
|
1217
|
+
onFocus: () => {
|
|
1218
|
+
setFocusWithin(true);
|
|
1219
|
+
wake();
|
|
1220
|
+
},
|
|
1221
|
+
onBlur: (e) => {
|
|
1222
|
+
if (!e.currentTarget.contains(e.relatedTarget)) setFocusWithin(false);
|
|
1223
|
+
},
|
|
1224
|
+
onKeyDown: onKey,
|
|
1225
|
+
style: {
|
|
1226
|
+
position: "relative",
|
|
1227
|
+
display: full ? "flex" : "block",
|
|
1228
|
+
alignItems: "center",
|
|
1229
|
+
justifyContent: "center",
|
|
1230
|
+
background: full ? "var(--color-palette-ink-900)" : void 0,
|
|
1231
|
+
...style
|
|
1232
|
+
},
|
|
1233
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { ref: stageRef, style: { position: "relative", width: full ? `min(100vw, calc(100vh * ${frame.width / frame.height}))` : "100%", aspectRatio: `${frame.width} / ${frame.height}` }, children: [
|
|
1234
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1235
|
+
"video",
|
|
1236
|
+
{
|
|
1237
|
+
ref: videoRef,
|
|
1238
|
+
src: videoSrc,
|
|
1239
|
+
poster,
|
|
1240
|
+
controls: !gated,
|
|
1241
|
+
controlsList: "nofullscreen",
|
|
1242
|
+
"data-dataimago-captioned-video": "",
|
|
1243
|
+
playsInline: true,
|
|
1244
|
+
preload: "metadata",
|
|
1245
|
+
"aria-label": title,
|
|
1246
|
+
"aria-hidden": gated || void 0,
|
|
1247
|
+
style: { display: "block", width: "100%", height: "100%" },
|
|
1248
|
+
children: [
|
|
1249
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("track", { ref: trackRef, kind: "captions", src: fallbackCaptions, srcLang: lang, label: "Captions", default: true }),
|
|
1250
|
+
chapters ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("track", { ref: chaptersRef, kind: "chapters", src: chapters, srcLang: lang, label: "Chapters" }) : null
|
|
1251
|
+
]
|
|
1252
|
+
}
|
|
1253
|
+
),
|
|
1254
|
+
showLayer ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1255
|
+
CaptionLayer,
|
|
1256
|
+
{
|
|
1257
|
+
track: layerTrack,
|
|
1258
|
+
time,
|
|
1259
|
+
width,
|
|
1260
|
+
reducedMotion,
|
|
1261
|
+
clearance: controlsUp ? CONTROLS_HEIGHT : 0,
|
|
1262
|
+
page: track ? void 0 : cue
|
|
1263
|
+
}
|
|
1264
|
+
) : null,
|
|
1265
|
+
gated ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { role: "group", "aria-labelledby": noticeId, style: gateStyle, children: [
|
|
1266
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { id: noticeId, style: { margin: 0 }, children: "This video is animated throughout." }),
|
|
1267
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1268
|
+
"button",
|
|
1269
|
+
{
|
|
1270
|
+
type: "button",
|
|
1271
|
+
style: { ...buttonStyle, pointerEvents: "auto" },
|
|
1272
|
+
onClick: () => {
|
|
1273
|
+
setChosen(true);
|
|
1274
|
+
void videoRef.current?.play().catch(() => {
|
|
1275
|
+
});
|
|
1276
|
+
},
|
|
1277
|
+
children: "Play the video"
|
|
1278
|
+
}
|
|
1279
|
+
),
|
|
1280
|
+
storyboard ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("a", { href: storyboard, style: linkStyle, children: "View the storyboard instead" }) : null
|
|
1281
|
+
] }) : null,
|
|
1282
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: {
|
|
1283
|
+
position: "absolute",
|
|
1284
|
+
top: 8,
|
|
1285
|
+
right: 8,
|
|
1286
|
+
display: gated ? "none" : "flex",
|
|
1287
|
+
gap: 6,
|
|
1288
|
+
pointerEvents: "none",
|
|
1289
|
+
// hidden once faded: an invisible button must not take a click or a Tab stop
|
|
1290
|
+
opacity: controlsUp ? 1 : 0,
|
|
1291
|
+
visibility: controlsUp ? "visible" : "hidden",
|
|
1292
|
+
transition: reducedMotion ? void 0 : controlsUp ? "opacity 200ms" : "opacity 200ms, visibility 0s linear 200ms"
|
|
1293
|
+
}, children: [
|
|
1294
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", style: buttonStyle, "aria-pressed": on, "aria-label": "CC (captions)", title: "Captions (c)", onClick: toggleCaptions, onFocus: wake, children: "CC" }),
|
|
1295
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", style: buttonStyle, "aria-pressed": full, "aria-label": full ? "Exit full screen" : "Full screen", title: "Full screen (f)", onClick: toggleFullscreen, onFocus: wake, children: full ? "Exit" : "Full" })
|
|
1296
|
+
] })
|
|
1297
|
+
] })
|
|
1298
|
+
}
|
|
1299
|
+
);
|
|
1300
|
+
};
|
|
1301
|
+
var FALLBACK_FRAME = {
|
|
1302
|
+
schema: CAPTIONS_SCHEMA,
|
|
1303
|
+
work: "",
|
|
1304
|
+
version: "",
|
|
1305
|
+
lang: "en",
|
|
1306
|
+
pages: [],
|
|
1307
|
+
style: { profile: "dataimago-v1", treatment: "frosted", weight: 500, toneSeconds: 0, lineHeight: 1.28 },
|
|
1308
|
+
frame: { width: 1920, height: 1080, fontSize: 44, safe: { top: 72, right: 96, bottom: 72, left: 96 } }
|
|
1309
|
+
};
|
|
252
1310
|
// Annotate the CommonJS export names for ESM import in node:
|
|
253
1311
|
0 && (module.exports = {
|
|
1312
|
+
BrandAnimation,
|
|
254
1313
|
Button,
|
|
1314
|
+
CAPTIONS_SCHEMA,
|
|
1315
|
+
CAPTIONS_STORAGE_KEY,
|
|
1316
|
+
CaptionLayer,
|
|
1317
|
+
CaptionedVideo,
|
|
1318
|
+
CopulaLockup,
|
|
1319
|
+
Logo,
|
|
255
1320
|
LogoMonogram,
|
|
256
1321
|
LogoMonogramBg,
|
|
257
|
-
LogoWordmark
|
|
1322
|
+
LogoWordmark,
|
|
1323
|
+
NavbarLogo,
|
|
1324
|
+
THEME_ATTRIBUTE,
|
|
1325
|
+
THEME_STORAGE_KEY,
|
|
1326
|
+
ThemeScript,
|
|
1327
|
+
ThemeToggle,
|
|
1328
|
+
captionLook,
|
|
1329
|
+
isCaptionTrack,
|
|
1330
|
+
nextThemePreference,
|
|
1331
|
+
pageAt,
|
|
1332
|
+
paletteVar,
|
|
1333
|
+
readThemePreference,
|
|
1334
|
+
resolveTheme,
|
|
1335
|
+
setThemePreference,
|
|
1336
|
+
splitMath,
|
|
1337
|
+
themeInitScript,
|
|
1338
|
+
toneColor,
|
|
1339
|
+
useResolvedTheme,
|
|
1340
|
+
useThemePreference,
|
|
1341
|
+
wordTone
|
|
258
1342
|
});
|
|
259
1343
|
//# sourceMappingURL=index.cjs.map
|