@bendyline/squisq-react 2.7.2 → 2.9.0

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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  BlockRenderer,
3
3
  LinearDocView
4
- } from "./chunk-IQGW6SA6.js";
4
+ } from "./chunk-NGQQKH7J.js";
5
5
  import {
6
6
  useAudioSync,
7
7
  useDocPlayback,
@@ -11,6 +11,9 @@ import {
11
11
  import {
12
12
  useAutoSurface
13
13
  } from "./chunk-TT6ENR6T.js";
14
+ import {
15
+ MarkdownRenderer
16
+ } from "./chunk-ESI3P77P.js";
14
17
  import {
15
18
  useMediaProvider,
16
19
  useMediaUrl,
@@ -1242,10 +1245,797 @@ function DocControlsSlideshow({
1242
1245
  );
1243
1246
  }
1244
1247
 
1248
+ // src/DashboardView.tsx
1249
+ import { Fragment as Fragment2, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef4 } from "react";
1250
+ import { materializeDashboard, resolveThemeForDoc, VIEWPORT_PRESETS } from "@bendyline/squisq/doc";
1251
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1252
+ async function waitForDashboardAssets(root) {
1253
+ if (typeof document !== "undefined" && document.fonts?.ready) {
1254
+ try {
1255
+ await document.fonts.ready;
1256
+ } catch {
1257
+ }
1258
+ }
1259
+ const images = Array.from(root.querySelectorAll("img"));
1260
+ await Promise.allSettled(
1261
+ images.map((img) => {
1262
+ if (img.complete) return Promise.resolve();
1263
+ if (typeof img.decode === "function") return img.decode().catch(() => void 0);
1264
+ return new Promise((resolve) => {
1265
+ img.addEventListener("load", () => resolve(), { once: true });
1266
+ img.addEventListener("error", () => resolve(), { once: true });
1267
+ });
1268
+ })
1269
+ );
1270
+ await new Promise((resolve) => {
1271
+ if (typeof requestAnimationFrame === "function") {
1272
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
1273
+ } else {
1274
+ setTimeout(resolve, 0);
1275
+ }
1276
+ });
1277
+ }
1278
+ function syntheticBlock(id, layers) {
1279
+ return { id, startTime: 0, duration: 0, audioSegment: 0, layers };
1280
+ }
1281
+ function DashboardView({
1282
+ doc,
1283
+ theme,
1284
+ viewport,
1285
+ layout,
1286
+ showTitle,
1287
+ style,
1288
+ documentTitle,
1289
+ basePath = ".",
1290
+ animationsEnabled = true,
1291
+ muted = true,
1292
+ renderMode = false,
1293
+ onRenderAPIReady,
1294
+ className
1295
+ }) {
1296
+ const rootRef = useRef4(null);
1297
+ const activeTheme = useMemo3(() => theme ?? resolveThemeForDoc(doc), [theme, doc]);
1298
+ const activeViewport = viewport ?? VIEWPORT_PRESETS.landscape;
1299
+ const materialization = useMemo3(
1300
+ () => materializeDashboard(doc, {
1301
+ theme: activeTheme,
1302
+ viewport: activeViewport,
1303
+ layout,
1304
+ showTitle,
1305
+ style,
1306
+ documentTitle
1307
+ }),
1308
+ [doc, activeTheme, activeViewport, layout, showTitle, style, documentTitle]
1309
+ );
1310
+ const backdropBottom = useMemo3(
1311
+ () => syntheticBlock("dashboard-backdrop-bottom", materialization.backdrop.bottomLayers),
1312
+ [materialization]
1313
+ );
1314
+ const backdropTop = useMemo3(
1315
+ () => materialization.backdrop.topLayers.length > 0 ? syntheticBlock("dashboard-backdrop-top", materialization.backdrop.topLayers) : null,
1316
+ [materialization]
1317
+ );
1318
+ const titleBlock = useMemo3(
1319
+ () => materialization.title ? syntheticBlock("dashboard-title-band", materialization.title.layers) : null,
1320
+ [materialization]
1321
+ );
1322
+ useEffect4(() => {
1323
+ if (!renderMode || !onRenderAPIReady) return;
1324
+ const api = {
1325
+ seekTo: async () => {
1326
+ const root = rootRef.current;
1327
+ if (root) await waitForDashboardAssets(root);
1328
+ },
1329
+ getRenderedTime: () => 0,
1330
+ getDuration: () => 0,
1331
+ getBlocks: () => materialization.cells.map((cell) => ({
1332
+ id: String(cell.block.id),
1333
+ template: cell.block.template ?? "content",
1334
+ startTime: 0,
1335
+ duration: 0
1336
+ })),
1337
+ getAudioSegments: () => [],
1338
+ getCaptions: () => [],
1339
+ getChapters: () => [],
1340
+ showCover: async () => {
1341
+ },
1342
+ hideCover: async () => {
1343
+ },
1344
+ hasCoverBlock: () => false
1345
+ };
1346
+ onRenderAPIReady(api);
1347
+ return () => onRenderAPIReady(null);
1348
+ }, [renderMode, onRenderAPIReady, materialization]);
1349
+ const canvasDimensions = { width: activeViewport.width, height: activeViewport.height };
1350
+ const coverStyle = { position: "absolute", inset: 0 };
1351
+ const canvasStyle = {
1352
+ position: "relative",
1353
+ width: "var(--squisq-dashboard-fit-width, 100%)",
1354
+ aspectRatio: `${activeViewport.width} / ${activeViewport.height}`,
1355
+ overflow: "hidden",
1356
+ // The style variant owns the canvas fill: card styles tint it away
1357
+ // from the card surface so cards read as raised.
1358
+ background: materialization.backdrop.fill,
1359
+ margin: "0 auto",
1360
+ "--squisq-dashboard-aspect": (activeViewport.width / activeViewport.height).toFixed(6)
1361
+ };
1362
+ return /* @__PURE__ */ jsxs4(
1363
+ "div",
1364
+ {
1365
+ ref: rootRef,
1366
+ className: className ? `squisq-dashboard ${className}` : "squisq-dashboard",
1367
+ "data-dashboard-layout": materialization.layout.name,
1368
+ "data-dashboard-style": materialization.style,
1369
+ style: canvasStyle,
1370
+ children: [
1371
+ /* @__PURE__ */ jsx7("div", { style: coverStyle, "aria-hidden": "true", children: /* @__PURE__ */ jsx7(
1372
+ BlockRenderer,
1373
+ {
1374
+ block: backdropBottom,
1375
+ blockTime: 0,
1376
+ basePath,
1377
+ viewport: canvasDimensions,
1378
+ isPlaying: false,
1379
+ muted: true,
1380
+ animationsEnabled,
1381
+ theme: activeTheme
1382
+ }
1383
+ ) }),
1384
+ materialization.cells.map((cell) => /* @__PURE__ */ jsxs4(Fragment2, { children: [
1385
+ cell.frame && /* @__PURE__ */ jsx7(
1386
+ "div",
1387
+ {
1388
+ className: "squisq-dashboard__frame",
1389
+ "data-cell-index": cell.index,
1390
+ "aria-hidden": "true",
1391
+ style: {
1392
+ position: "absolute",
1393
+ left: cell.frame.rectPct.left,
1394
+ top: cell.frame.rectPct.top,
1395
+ width: cell.frame.rectPct.width,
1396
+ height: cell.frame.rectPct.height,
1397
+ pointerEvents: "none"
1398
+ },
1399
+ children: /* @__PURE__ */ jsx7(
1400
+ BlockRenderer,
1401
+ {
1402
+ block: syntheticBlock(`dashboard-frame-${cell.index}`, cell.frame.layers),
1403
+ blockTime: 0,
1404
+ basePath,
1405
+ viewport: {
1406
+ width: cell.frame.viewport.width,
1407
+ height: cell.frame.viewport.height
1408
+ },
1409
+ isPlaying: false,
1410
+ muted: true,
1411
+ animationsEnabled,
1412
+ theme: activeTheme
1413
+ }
1414
+ )
1415
+ }
1416
+ ),
1417
+ /* @__PURE__ */ jsxs4(
1418
+ "div",
1419
+ {
1420
+ className: "squisq-dashboard__cell",
1421
+ "data-cell-index": cell.index,
1422
+ style: {
1423
+ position: "absolute",
1424
+ left: cell.rectPct.left,
1425
+ top: cell.rectPct.top,
1426
+ width: cell.rectPct.width,
1427
+ height: cell.rectPct.height,
1428
+ // Percentage radii scale with the rendered canvas, so a card
1429
+ // cell's full-bleed art stays clipped to its corners at any
1430
+ // export resolution.
1431
+ ...cell.frame?.contentRadiusPct ? { borderRadius: cell.frame.contentRadiusPct, overflow: "hidden" } : {}
1432
+ },
1433
+ children: [
1434
+ /* @__PURE__ */ jsx7(
1435
+ BlockRenderer,
1436
+ {
1437
+ block: { ...cell.block, layers: cell.layers },
1438
+ blockTime: 0,
1439
+ basePath,
1440
+ viewport: { width: cell.viewport.width, height: cell.viewport.height },
1441
+ isPlaying: false,
1442
+ muted,
1443
+ animationsEnabled,
1444
+ theme: activeTheme
1445
+ }
1446
+ ),
1447
+ cell.frame && cell.frame.overlayLayers.length > 0 && // Borders and accents ride above the block: a template that
1448
+ // paints its own opaque surface would bury them. Same box, so
1449
+ // the cell's clip rounds them to the card's corners.
1450
+ /* @__PURE__ */ jsx7("div", { style: { position: "absolute", inset: 0, pointerEvents: "none" }, children: /* @__PURE__ */ jsx7(
1451
+ BlockRenderer,
1452
+ {
1453
+ block: syntheticBlock(
1454
+ `dashboard-frame-overlay-${cell.index}`,
1455
+ cell.frame.overlayLayers
1456
+ ),
1457
+ blockTime: 0,
1458
+ basePath,
1459
+ viewport: {
1460
+ width: cell.frame.overlayViewport.width,
1461
+ height: cell.frame.overlayViewport.height
1462
+ },
1463
+ isPlaying: false,
1464
+ muted: true,
1465
+ animationsEnabled,
1466
+ theme: activeTheme
1467
+ }
1468
+ ) })
1469
+ ]
1470
+ }
1471
+ )
1472
+ ] }, `${cell.index}-${cell.block.id}`)),
1473
+ materialization.title && titleBlock && /* @__PURE__ */ jsx7(
1474
+ "div",
1475
+ {
1476
+ className: "squisq-dashboard__title",
1477
+ style: {
1478
+ position: "absolute",
1479
+ left: materialization.title.rectPct.left,
1480
+ top: materialization.title.rectPct.top,
1481
+ width: materialization.title.rectPct.width,
1482
+ height: materialization.title.rectPct.height
1483
+ },
1484
+ children: /* @__PURE__ */ jsx7(
1485
+ BlockRenderer,
1486
+ {
1487
+ block: titleBlock,
1488
+ blockTime: 0,
1489
+ basePath,
1490
+ viewport: {
1491
+ width: materialization.title.viewport.width,
1492
+ height: materialization.title.viewport.height
1493
+ },
1494
+ isPlaying: false,
1495
+ muted: true,
1496
+ animationsEnabled,
1497
+ theme: activeTheme
1498
+ }
1499
+ )
1500
+ }
1501
+ ),
1502
+ backdropTop && /* @__PURE__ */ jsx7("div", { style: { ...coverStyle, pointerEvents: "none" }, "aria-hidden": "true", children: /* @__PURE__ */ jsx7(
1503
+ BlockRenderer,
1504
+ {
1505
+ block: backdropTop,
1506
+ blockTime: 0,
1507
+ basePath,
1508
+ viewport: canvasDimensions,
1509
+ isPlaying: false,
1510
+ muted: true,
1511
+ animationsEnabled,
1512
+ theme: activeTheme
1513
+ }
1514
+ ) })
1515
+ ]
1516
+ }
1517
+ );
1518
+ }
1519
+
1520
+ // src/FlashcardView.tsx
1521
+ import {
1522
+ useCallback as useCallback3,
1523
+ useEffect as useEffect5,
1524
+ useMemo as useMemo4,
1525
+ useRef as useRef5,
1526
+ useState as useState4
1527
+ } from "react";
1528
+ import { resolveFontFamily as resolveFontFamily2, VIEWPORT_PRESETS as VIEWPORT_PRESETS2 } from "@bendyline/squisq/schemas";
1529
+ import {
1530
+ materializeBlockLayers,
1531
+ materializeFlashcards,
1532
+ resolvePageBlock,
1533
+ resolveThemeForDoc as resolveThemeForDoc2
1534
+ } from "@bendyline/squisq/doc";
1535
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1536
+ var VISUAL_TEMPLATES = /* @__PURE__ */ new Set([
1537
+ "diagram",
1538
+ "tree",
1539
+ "timeline",
1540
+ "map",
1541
+ "drawing",
1542
+ "layout",
1543
+ "barChart",
1544
+ "columnChart",
1545
+ "pieChart",
1546
+ "donutChart",
1547
+ "lineChart",
1548
+ "areaChart",
1549
+ "scatterChart"
1550
+ ]);
1551
+ function resolveVisualBlock(block, theme, viewport, customTemplates) {
1552
+ const resolved = resolvePageBlock(block);
1553
+ const visualTemplate = !!resolved.templateName && (VISUAL_TEMPLATES.has(resolved.templateName) || !!customTemplates?.some((definition) => definition.name === resolved.templateName));
1554
+ if (!visualTemplate) return null;
1555
+ const materializationBlock = resolved.templateBlock ?? block;
1556
+ const { layers, source } = materializeBlockLayers(materializationBlock, {
1557
+ theme,
1558
+ viewport,
1559
+ customTemplates,
1560
+ persistentLayers: false
1561
+ });
1562
+ if (layers.length === 0 || source === "fallback") return null;
1563
+ return { ...materializationBlock, layers };
1564
+ }
1565
+ function FaceBlock({
1566
+ block,
1567
+ depth,
1568
+ theme,
1569
+ basePath,
1570
+ showCodeCopyButton,
1571
+ onCopyCode,
1572
+ fenceRenderers,
1573
+ viewport,
1574
+ customTemplates
1575
+ }) {
1576
+ const Heading = depth === 0 ? "h2" : depth === 1 ? "h3" : "h4";
1577
+ const visualBlock = useMemo4(
1578
+ () => resolveVisualBlock(block, theme, viewport, customTemplates),
1579
+ [block, customTemplates, theme, viewport]
1580
+ );
1581
+ return /* @__PURE__ */ jsxs5("section", { className: "squisq-flashcards__content-block", "data-source-block-id": block.id, children: [
1582
+ !visualBlock && block.title && /* @__PURE__ */ jsx8(Heading, { className: "squisq-flashcards__content-title", children: block.title }),
1583
+ !visualBlock && block.contents && block.contents.length > 0 && /* @__PURE__ */ jsx8(
1584
+ MarkdownRenderer,
1585
+ {
1586
+ nodes: block.contents,
1587
+ theme,
1588
+ showCodeCopyButton,
1589
+ onCopyCode,
1590
+ fenceRenderers
1591
+ }
1592
+ ),
1593
+ (visualBlock || block.layers && block.layers.length > 0) && /* @__PURE__ */ jsx8(
1594
+ "div",
1595
+ {
1596
+ className: "squisq-flashcards__canvas",
1597
+ style: { aspectRatio: `${viewport.width} / ${viewport.height}` },
1598
+ children: /* @__PURE__ */ jsx8(
1599
+ BlockRenderer,
1600
+ {
1601
+ block: visualBlock ?? block,
1602
+ blockTime: Math.max(0, block.duration ?? 0),
1603
+ basePath,
1604
+ viewport,
1605
+ animationsEnabled: false,
1606
+ theme,
1607
+ muted: true
1608
+ }
1609
+ )
1610
+ }
1611
+ ),
1612
+ !visualBlock && block.children?.map((child) => /* @__PURE__ */ jsx8(
1613
+ FaceBlock,
1614
+ {
1615
+ block: child,
1616
+ depth: depth + 1,
1617
+ theme,
1618
+ basePath,
1619
+ showCodeCopyButton,
1620
+ onCopyCode,
1621
+ fenceRenderers,
1622
+ viewport,
1623
+ customTemplates
1624
+ },
1625
+ child.id
1626
+ ))
1627
+ ] });
1628
+ }
1629
+ function FaceContent(props) {
1630
+ return /* @__PURE__ */ jsx8("div", { className: "squisq-flashcards__face-content", children: props.face.blocks.map((block) => /* @__PURE__ */ jsx8(FaceBlock, { block, depth: 0, ...props }, block.id)) });
1631
+ }
1632
+ function FlashcardFaceView(props) {
1633
+ return /* @__PURE__ */ jsx8(FaceContent, { ...props, viewport: props.viewport ?? VIEWPORT_PRESETS2.landscape });
1634
+ }
1635
+ function hashSeed(value) {
1636
+ let hash = 2166136261;
1637
+ for (let index = 0; index < value.length; index++) {
1638
+ hash ^= value.charCodeAt(index);
1639
+ hash = Math.imul(hash, 16777619);
1640
+ }
1641
+ return hash >>> 0;
1642
+ }
1643
+ function shuffled(values, seed) {
1644
+ const result = [...values];
1645
+ let state = hashSeed(seed) || 1;
1646
+ const random = () => {
1647
+ state += 1831565813;
1648
+ let next = state;
1649
+ next = Math.imul(next ^ next >>> 15, next | 1);
1650
+ next ^= next + Math.imul(next ^ next >>> 7, next | 61);
1651
+ return ((next ^ next >>> 14) >>> 0) / 4294967296;
1652
+ };
1653
+ for (let index = result.length - 1; index > 0; index--) {
1654
+ const other = Math.floor(random() * (index + 1));
1655
+ [result[index], result[other]] = [result[other], result[index]];
1656
+ }
1657
+ return result;
1658
+ }
1659
+ function ChoiceContent(props) {
1660
+ return /* @__PURE__ */ jsx8(FaceContent, { ...props, face: props.choice.content });
1661
+ }
1662
+ function isInteractiveTarget(target) {
1663
+ return target instanceof Element && !!target.closest('button, a, input, select, textarea, [contenteditable="true"]');
1664
+ }
1665
+ function FlashcardView({
1666
+ doc,
1667
+ theme,
1668
+ basePath = ".",
1669
+ source = "auto",
1670
+ shuffle: initialShuffle = false,
1671
+ globalKeyboardShortcuts = false,
1672
+ showCodeCopyButton = false,
1673
+ onCopyCode,
1674
+ fenceRenderers,
1675
+ viewport = VIEWPORT_PRESETS2.landscape,
1676
+ className
1677
+ }) {
1678
+ const activeTheme = useMemo4(() => theme ?? resolveThemeForDoc2(doc), [doc, theme]);
1679
+ const deck = useMemo4(() => materializeFlashcards(doc, { source }), [doc, source]);
1680
+ const [shuffleEnabled, setShuffleEnabled] = useState4(initialShuffle);
1681
+ const [sessionSeed, setSessionSeed] = useState4(1);
1682
+ const [cardIndex, setCardIndex] = useState4(0);
1683
+ const [revealed, setRevealed] = useState4(false);
1684
+ const [selectedChoiceId, setSelectedChoiceId] = useState4(null);
1685
+ const [ratings, setRatings] = useState4({});
1686
+ const [retryIds, setRetryIds] = useState4(null);
1687
+ const cardViewportRef = useRef5(null);
1688
+ const orderedCards = useMemo4(
1689
+ () => shuffleEnabled ? shuffled(deck.cards, `${doc.articleId}:${sessionSeed}`) : [...deck.cards],
1690
+ [deck.cards, doc.articleId, sessionSeed, shuffleEnabled]
1691
+ );
1692
+ const sessionCards = useMemo4(
1693
+ () => retryIds ? retryIds.map((id) => orderedCards.find((card) => card.id === id)).filter((card) => !!card) : orderedCards,
1694
+ [orderedCards, retryIds]
1695
+ );
1696
+ const currentCard = sessionCards[cardIndex];
1697
+ const finished = sessionCards.length > 0 && cardIndex >= sessionCards.length;
1698
+ const resetPosition = useCallback3(() => {
1699
+ setCardIndex(0);
1700
+ setRevealed(false);
1701
+ setSelectedChoiceId(null);
1702
+ }, []);
1703
+ const restart = useCallback3(() => {
1704
+ setRatings({});
1705
+ setRetryIds(null);
1706
+ resetPosition();
1707
+ setSessionSeed((seed) => seed + 1);
1708
+ }, [resetPosition]);
1709
+ useEffect5(() => {
1710
+ setRatings({});
1711
+ setRetryIds(null);
1712
+ resetPosition();
1713
+ }, [deck, resetPosition]);
1714
+ useEffect5(() => {
1715
+ if (cardViewportRef.current) cardViewportRef.current.scrollTop = 0;
1716
+ }, [cardIndex, retryIds]);
1717
+ const revealAnswer = useCallback3(() => {
1718
+ if (!currentCard) return;
1719
+ setRevealed(true);
1720
+ if (currentCard.kind === "multiple-choice" && selectedChoiceId === null) {
1721
+ setRatings((current) => ({ ...current, [currentCard.id]: "again" }));
1722
+ }
1723
+ }, [currentCard, selectedChoiceId]);
1724
+ const nextCard = useCallback3(() => {
1725
+ if (!currentCard) return;
1726
+ if (!revealed) {
1727
+ revealAnswer();
1728
+ return;
1729
+ }
1730
+ setCardIndex((index) => index + 1);
1731
+ setRevealed(false);
1732
+ setSelectedChoiceId(null);
1733
+ }, [currentCard, revealAnswer, revealed]);
1734
+ const previousCard = useCallback3(() => {
1735
+ if (finished) {
1736
+ setCardIndex(Math.max(0, sessionCards.length - 1));
1737
+ setRevealed(true);
1738
+ return;
1739
+ }
1740
+ if (revealed) {
1741
+ setRevealed(false);
1742
+ setSelectedChoiceId(null);
1743
+ return;
1744
+ }
1745
+ if (cardIndex > 0) {
1746
+ setCardIndex((index) => index - 1);
1747
+ setRevealed(true);
1748
+ setSelectedChoiceId(null);
1749
+ }
1750
+ }, [cardIndex, finished, revealed, sessionCards.length]);
1751
+ const choose = useCallback3(
1752
+ (choice) => {
1753
+ if (!currentCard || revealed) return;
1754
+ setSelectedChoiceId(choice.id);
1755
+ setRevealed(true);
1756
+ setRatings((current) => ({
1757
+ ...current,
1758
+ [currentCard.id]: choice.correct ? "got-it" : "again"
1759
+ }));
1760
+ },
1761
+ [currentCard, revealed]
1762
+ );
1763
+ const rate = useCallback3(
1764
+ (rating) => {
1765
+ if (!currentCard) return;
1766
+ setRatings((current) => ({ ...current, [currentCard.id]: rating }));
1767
+ setCardIndex((index) => index + 1);
1768
+ setRevealed(false);
1769
+ setSelectedChoiceId(null);
1770
+ },
1771
+ [currentCard]
1772
+ );
1773
+ const handleShortcut = useCallback3(
1774
+ (event) => {
1775
+ if (event.defaultPrevented || isInteractiveTarget(event.target)) return;
1776
+ if (/^[1-9]$/.test(event.key) && currentCard?.kind === "multiple-choice" && !revealed) {
1777
+ const choice = currentCard.choices?.[Number(event.key) - 1];
1778
+ if (choice) {
1779
+ event.preventDefault();
1780
+ choose(choice);
1781
+ }
1782
+ return;
1783
+ }
1784
+ if (event.key === "ArrowLeft") {
1785
+ event.preventDefault();
1786
+ previousCard();
1787
+ } else if (event.key === "ArrowRight" || event.key === " " || event.key === "Enter") {
1788
+ event.preventDefault();
1789
+ nextCard();
1790
+ }
1791
+ },
1792
+ [choose, currentCard, nextCard, previousCard, revealed]
1793
+ );
1794
+ useEffect5(() => {
1795
+ if (!globalKeyboardShortcuts) return;
1796
+ const listener = (event) => handleShortcut(event);
1797
+ document.addEventListener("keydown", listener);
1798
+ return () => document.removeEventListener("keydown", listener);
1799
+ }, [globalKeyboardShortcuts, handleShortcut]);
1800
+ const faceProps = {
1801
+ theme: activeTheme,
1802
+ basePath,
1803
+ showCodeCopyButton,
1804
+ onCopyCode,
1805
+ fenceRenderers,
1806
+ viewport,
1807
+ customTemplates: doc.customTemplates
1808
+ };
1809
+ const titleFont = resolveFontFamily2(activeTheme.typography.titleFont, "Georgia, serif");
1810
+ const bodyFont = resolveFontFamily2(activeTheme.typography.bodyFont, "system-ui, sans-serif");
1811
+ const rootStyle = {
1812
+ "--squisq-flashcards-bg": activeTheme.colors.background,
1813
+ "--squisq-flashcards-surface": activeTheme.colors.backgroundLight,
1814
+ "--squisq-flashcards-text": activeTheme.colors.text,
1815
+ "--squisq-flashcards-muted": activeTheme.colors.textMuted,
1816
+ "--squisq-flashcards-primary": activeTheme.colors.primary,
1817
+ "--squisq-flashcards-secondary": activeTheme.colors.secondary,
1818
+ "--squisq-flashcards-highlight": activeTheme.colors.highlight,
1819
+ "--squisq-flashcards-warning": activeTheme.colors.warning,
1820
+ "--squisq-flashcards-radius": `${activeTheme.style.borderRadius ?? 18}px`,
1821
+ "--squisq-flashcards-title-font": titleFont,
1822
+ "--squisq-flashcards-body-font": bodyFont
1823
+ };
1824
+ const missedIds = deck.cards.filter((card) => ratings[card.id] === "again").map((card) => card.id);
1825
+ const gotItCount = deck.cards.filter((card) => ratings[card.id] === "got-it").length;
1826
+ if (deck.cards.length === 0) {
1827
+ return /* @__PURE__ */ jsx8(
1828
+ "div",
1829
+ {
1830
+ className: `squisq-flashcards squisq-flashcards--empty${className ? ` ${className}` : ""}`,
1831
+ style: rootStyle,
1832
+ role: "region",
1833
+ "aria-label": "Flashcards",
1834
+ children: /* @__PURE__ */ jsxs5("div", { className: "squisq-flashcards__empty-card", children: [
1835
+ /* @__PURE__ */ jsx8("h2", { children: "No complete flashcards yet" }),
1836
+ /* @__PURE__ */ jsx8("p", { children: "Add an answer beneath a heading, or nest an answer block under a question." }),
1837
+ deck.diagnostics.length > 0 && /* @__PURE__ */ jsx8("ul", { children: deck.diagnostics.map((diagnostic, index) => /* @__PURE__ */ jsx8("li", { children: diagnostic.message }, `${diagnostic.blockId}-${diagnostic.code}-${index}`)) })
1838
+ ] })
1839
+ }
1840
+ );
1841
+ }
1842
+ if (finished) {
1843
+ return /* @__PURE__ */ jsx8(
1844
+ "div",
1845
+ {
1846
+ className: `squisq-flashcards${className ? ` ${className}` : ""}`,
1847
+ style: rootStyle,
1848
+ role: "region",
1849
+ "aria-label": "Flashcard session summary",
1850
+ tabIndex: 0,
1851
+ onKeyDown: globalKeyboardShortcuts ? void 0 : handleShortcut,
1852
+ children: /* @__PURE__ */ jsxs5("div", { className: "squisq-flashcards__summary", children: [
1853
+ /* @__PURE__ */ jsx8("span", { className: "squisq-flashcards__eyebrow", children: "Session complete" }),
1854
+ /* @__PURE__ */ jsxs5("h2", { children: [
1855
+ gotItCount,
1856
+ " remembered"
1857
+ ] }),
1858
+ /* @__PURE__ */ jsx8("p", { children: missedIds.length === 0 ? `You completed all ${deck.cards.length} cards.` : `${missedIds.length} ${missedIds.length === 1 ? "card needs" : "cards need"} another look.` }),
1859
+ /* @__PURE__ */ jsxs5("div", { className: "squisq-flashcards__summary-actions", children: [
1860
+ missedIds.length > 0 && /* @__PURE__ */ jsx8(
1861
+ "button",
1862
+ {
1863
+ type: "button",
1864
+ className: "squisq-flashcards__button squisq-flashcards__button--primary",
1865
+ onClick: () => {
1866
+ setRetryIds(missedIds);
1867
+ resetPosition();
1868
+ },
1869
+ children: "Retry missed"
1870
+ }
1871
+ ),
1872
+ /* @__PURE__ */ jsx8("button", { type: "button", className: "squisq-flashcards__button", onClick: restart, children: "Study again" }),
1873
+ /* @__PURE__ */ jsx8("button", { type: "button", className: "squisq-flashcards__button", onClick: previousCard, children: "Previous card" })
1874
+ ] })
1875
+ ] })
1876
+ }
1877
+ );
1878
+ }
1879
+ const choices = currentCard?.choices ? shuffled(currentCard.choices, `${currentCard.id}:${sessionSeed}`) : [];
1880
+ const selectedChoice = choices.find((choice) => choice.id === selectedChoiceId);
1881
+ return /* @__PURE__ */ jsxs5(
1882
+ "div",
1883
+ {
1884
+ className: `squisq-flashcards${className ? ` ${className}` : ""}`,
1885
+ style: rootStyle,
1886
+ role: "region",
1887
+ "aria-label": "Flashcards",
1888
+ tabIndex: 0,
1889
+ onKeyDown: globalKeyboardShortcuts ? void 0 : handleShortcut,
1890
+ children: [
1891
+ /* @__PURE__ */ jsxs5("header", { className: "squisq-flashcards__header", children: [
1892
+ /* @__PURE__ */ jsxs5("div", { className: "squisq-flashcards__deck-copy", children: [
1893
+ /* @__PURE__ */ jsx8("span", { className: "squisq-flashcards__eyebrow", children: "Flashcards" }),
1894
+ deck.title && /* @__PURE__ */ jsx8("h1", { children: deck.title })
1895
+ ] }),
1896
+ /* @__PURE__ */ jsxs5("div", { className: "squisq-flashcards__tools", children: [
1897
+ /* @__PURE__ */ jsx8(
1898
+ "button",
1899
+ {
1900
+ type: "button",
1901
+ className: "squisq-flashcards__tool",
1902
+ "aria-pressed": shuffleEnabled,
1903
+ onClick: () => {
1904
+ setShuffleEnabled((enabled) => !enabled);
1905
+ restart();
1906
+ },
1907
+ children: "Shuffle"
1908
+ }
1909
+ ),
1910
+ /* @__PURE__ */ jsx8("button", { type: "button", className: "squisq-flashcards__tool", onClick: restart, children: "Restart" })
1911
+ ] })
1912
+ ] }),
1913
+ /* @__PURE__ */ jsxs5("div", { className: "squisq-flashcards__progress-row", children: [
1914
+ /* @__PURE__ */ jsxs5("span", { children: [
1915
+ "Card ",
1916
+ cardIndex + 1,
1917
+ " of ",
1918
+ sessionCards.length
1919
+ ] }),
1920
+ /* @__PURE__ */ jsx8("span", { children: revealed ? "Answer revealed" : "Question" })
1921
+ ] }),
1922
+ /* @__PURE__ */ jsx8("div", { className: "squisq-flashcards__progress", "aria-hidden": "true", children: /* @__PURE__ */ jsx8(
1923
+ "span",
1924
+ {
1925
+ style: { width: `${(cardIndex + (revealed ? 1 : 0)) / sessionCards.length * 100}%` }
1926
+ }
1927
+ ) }),
1928
+ /* @__PURE__ */ jsxs5(
1929
+ "main",
1930
+ {
1931
+ ref: cardViewportRef,
1932
+ className: "squisq-flashcards__card",
1933
+ "data-card-kind": currentCard.kind,
1934
+ "aria-label": "Scrollable flashcard content",
1935
+ tabIndex: 0,
1936
+ children: [
1937
+ currentCard.label && /* @__PURE__ */ jsx8("div", { className: "squisq-flashcards__label", children: currentCard.label }),
1938
+ /* @__PURE__ */ jsx8("section", { className: "squisq-flashcards__front", "aria-label": "Question", children: /* @__PURE__ */ jsx8(FaceContent, { ...faceProps, face: currentCard.front }) }),
1939
+ currentCard.kind === "multiple-choice" && /* @__PURE__ */ jsx8("div", { className: "squisq-flashcards__choices", role: "group", "aria-label": "Answer choices", children: choices.map((choice, index) => {
1940
+ const selected = selectedChoiceId === choice.id;
1941
+ const status = revealed ? choice.correct ? "correct" : selected ? "incorrect" : "idle" : "idle";
1942
+ return /* @__PURE__ */ jsxs5(
1943
+ "button",
1944
+ {
1945
+ type: "button",
1946
+ className: "squisq-flashcards__choice",
1947
+ "data-choice-status": status,
1948
+ "aria-pressed": selected,
1949
+ disabled: revealed,
1950
+ onClick: () => choose(choice),
1951
+ children: [
1952
+ /* @__PURE__ */ jsx8("span", { className: "squisq-flashcards__choice-key", "aria-hidden": "true", children: index + 1 }),
1953
+ /* @__PURE__ */ jsx8(ChoiceContent, { ...faceProps, choice })
1954
+ ]
1955
+ },
1956
+ choice.id
1957
+ );
1958
+ }) }),
1959
+ revealed && currentCard.kind === "basic" && /* @__PURE__ */ jsxs5("section", { className: "squisq-flashcards__answer", "aria-label": "Answer", children: [
1960
+ /* @__PURE__ */ jsx8("span", { className: "squisq-flashcards__answer-label", children: "Answer" }),
1961
+ /* @__PURE__ */ jsx8(FaceContent, { ...faceProps, face: currentCard.back })
1962
+ ] }),
1963
+ revealed && currentCard.kind === "multiple-choice" && /* @__PURE__ */ jsxs5(
1964
+ "div",
1965
+ {
1966
+ className: "squisq-flashcards__feedback",
1967
+ "data-correct": selectedChoice?.correct === true,
1968
+ "aria-live": "polite",
1969
+ children: [
1970
+ /* @__PURE__ */ jsx8("strong", { children: selectedChoice === void 0 ? "Answer revealed" : selectedChoice.correct ? "Correct" : "Not quite" }),
1971
+ !selectedChoice?.correct && /* @__PURE__ */ jsxs5("div", { className: "squisq-flashcards__correct-answer", children: [
1972
+ /* @__PURE__ */ jsx8("span", { children: "The correct answer is" }),
1973
+ /* @__PURE__ */ jsx8(FaceContent, { ...faceProps, face: currentCard.back })
1974
+ ] })
1975
+ ]
1976
+ }
1977
+ ),
1978
+ revealed && currentCard.explanation && /* @__PURE__ */ jsxs5("section", { className: "squisq-flashcards__explanation", "aria-label": "Explanation", children: [
1979
+ /* @__PURE__ */ jsx8("span", { className: "squisq-flashcards__answer-label", children: "Explanation" }),
1980
+ /* @__PURE__ */ jsx8(FaceContent, { ...faceProps, face: currentCard.explanation })
1981
+ ] })
1982
+ ]
1983
+ }
1984
+ ),
1985
+ /* @__PURE__ */ jsxs5("footer", { className: "squisq-flashcards__footer", children: [
1986
+ /* @__PURE__ */ jsx8(
1987
+ "button",
1988
+ {
1989
+ type: "button",
1990
+ className: "squisq-flashcards__button",
1991
+ disabled: cardIndex === 0 && !revealed,
1992
+ onClick: previousCard,
1993
+ children: "Previous"
1994
+ }
1995
+ ),
1996
+ /* @__PURE__ */ jsx8("div", { className: "squisq-flashcards__primary-actions", children: revealed && currentCard.kind === "basic" ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
1997
+ /* @__PURE__ */ jsx8(
1998
+ "button",
1999
+ {
2000
+ type: "button",
2001
+ className: "squisq-flashcards__button",
2002
+ onClick: () => rate("again"),
2003
+ children: "Again"
2004
+ }
2005
+ ),
2006
+ /* @__PURE__ */ jsx8(
2007
+ "button",
2008
+ {
2009
+ type: "button",
2010
+ className: "squisq-flashcards__button squisq-flashcards__button--primary",
2011
+ onClick: () => rate("got-it"),
2012
+ children: "Got it"
2013
+ }
2014
+ )
2015
+ ] }) : /* @__PURE__ */ jsx8(
2016
+ "button",
2017
+ {
2018
+ type: "button",
2019
+ className: "squisq-flashcards__button squisq-flashcards__button--primary",
2020
+ onClick: nextCard,
2021
+ children: revealed ? cardIndex === sessionCards.length - 1 ? "Finish" : "Next card" : "Reveal answer"
2022
+ }
2023
+ ) })
2024
+ ] }),
2025
+ /* @__PURE__ */ jsxs5("p", { className: "squisq-flashcards__shortcut-hint", children: [
2026
+ "Use \u2190 and \u2192 to navigate, Space to reveal",
2027
+ currentCard.kind === "multiple-choice" ? ", or 1\u20139 to answer" : "",
2028
+ "."
2029
+ ] })
2030
+ ]
2031
+ }
2032
+ );
2033
+ }
2034
+
1245
2035
  // src/docPlayer/playerAppearance.ts
1246
2036
  import {
1247
2037
  resolveCoverSlideSettings,
1248
- resolveThemeForDoc
2038
+ resolveThemeForDoc as resolveThemeForDoc3
1249
2039
  } from "@bendyline/squisq/doc";
1250
2040
  function readFrontmatterSetting(frontmatter, canonical, legacy) {
1251
2041
  if (!frontmatter) return void 0;
@@ -1303,7 +2093,7 @@ function resolveDocPlayerAppearance(doc, overrides = {}) {
1303
2093
  ...overrides.coverSlidePlayback !== void 0 ? { playback: overrides.coverSlidePlayback } : {}
1304
2094
  });
1305
2095
  return {
1306
- theme: overrides.theme ?? resolveThemeForDoc(doc),
2096
+ theme: overrides.theme ?? resolveThemeForDoc3(doc),
1307
2097
  videoPresentation: overrides.videoPresentation ?? resolveVideoPresentation(
1308
2098
  readFrontmatterSetting(frontmatter, "squisq-video-presentation", "video-presentation")
1309
2099
  ) ?? "background",
@@ -1321,14 +2111,14 @@ function resolveDocPlayerAppearance(doc, overrides = {}) {
1321
2111
 
1322
2112
  // src/DocPlayer.tsx
1323
2113
  import {
1324
- Fragment as Fragment2,
2114
+ Fragment as Fragment4,
1325
2115
  useId as useId2,
1326
- useRef as useRef5,
1327
- useState as useState5,
1328
- useEffect as useEffect5,
2116
+ useRef as useRef7,
2117
+ useState as useState6,
2118
+ useEffect as useEffect7,
1329
2119
  useLayoutEffect as useLayoutEffect2,
1330
- useCallback as useCallback4,
1331
- useMemo as useMemo3
2120
+ useCallback as useCallback5,
2121
+ useMemo as useMemo5
1332
2122
  } from "react";
1333
2123
  import { flushSync } from "react-dom";
1334
2124
  import {
@@ -1340,7 +2130,7 @@ import {
1340
2130
  import { applySurface, pipStyleVars } from "@bendyline/squisq/schemas";
1341
2131
 
1342
2132
  // src/hooks/useSlideSwipe.ts
1343
- import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
2133
+ import { useCallback as useCallback4, useEffect as useEffect6, useRef as useRef6, useState as useState5 } from "react";
1344
2134
  var DISTANCE_RATIO = 0.3;
1345
2135
  var FLICK_VELOCITY = 0.5;
1346
2136
  var MIN_FLICK_DISTANCE = 12;
@@ -1364,16 +2154,16 @@ function decideSwipe({
1364
2154
  }
1365
2155
  function useSlideSwipe(opts) {
1366
2156
  const settleMs = opts.settleMs ?? DEFAULT_SETTLE_MS;
1367
- const [offsetPx, setOffsetPx] = useState4(0);
1368
- const [phase, setPhase] = useState4("idle");
1369
- const optsRef = useRef4(opts);
2157
+ const [offsetPx, setOffsetPx] = useState5(0);
2158
+ const [phase, setPhase] = useState5("idle");
2159
+ const optsRef = useRef6(opts);
1370
2160
  optsRef.current = opts;
1371
- const dragRef = useRef4(null);
1372
- const phaseRef = useRef4("idle");
2161
+ const dragRef = useRef6(null);
2162
+ const phaseRef = useRef6("idle");
1373
2163
  phaseRef.current = phase;
1374
- const settleTimer = useRef4(null);
1375
- const settleRaf = useRef4(null);
1376
- const clearPending = useCallback3(() => {
2164
+ const settleTimer = useRef6(null);
2165
+ const settleRaf = useRef6(null);
2166
+ const clearPending = useCallback4(() => {
1377
2167
  if (settleTimer.current != null) {
1378
2168
  clearTimeout(settleTimer.current);
1379
2169
  settleTimer.current = null;
@@ -1383,7 +2173,7 @@ function useSlideSwipe(opts) {
1383
2173
  settleRaf.current = null;
1384
2174
  }
1385
2175
  }, []);
1386
- const onPointerDown = useCallback3((e) => {
2176
+ const onPointerDown = useCallback4((e) => {
1387
2177
  const o = optsRef.current;
1388
2178
  if (!o.enabled) return;
1389
2179
  if (phaseRef.current === "settling") return;
@@ -1403,7 +2193,7 @@ function useSlideSwipe(opts) {
1403
2193
  setPhase("dragging");
1404
2194
  setOffsetPx(0);
1405
2195
  }, []);
1406
- useEffect4(() => {
2196
+ useEffect6(() => {
1407
2197
  function currentWidth() {
1408
2198
  return optsRef.current.containerRef.current?.getBoundingClientRect().width ?? 0;
1409
2199
  }
@@ -1473,7 +2263,7 @@ function useSlideSwipe(opts) {
1473
2263
  window.removeEventListener("pointercancel", onCancel);
1474
2264
  };
1475
2265
  }, [settleMs]);
1476
- useEffect4(() => {
2266
+ useEffect6(() => {
1477
2267
  if (!opts.enabled) {
1478
2268
  dragRef.current = null;
1479
2269
  clearPending();
@@ -1481,7 +2271,7 @@ function useSlideSwipe(opts) {
1481
2271
  setOffsetPx(0);
1482
2272
  }
1483
2273
  }, [opts.enabled, clearPending]);
1484
- useEffect4(() => clearPending, [clearPending]);
2274
+ useEffect6(() => clearPending, [clearPending]);
1485
2275
  return { offsetPx, phase, onPointerDown };
1486
2276
  }
1487
2277
 
@@ -1490,7 +2280,7 @@ import {
1490
2280
  expandCoverBlock,
1491
2281
  createTemplateContext,
1492
2282
  markdownToDoc,
1493
- VIEWPORT_PRESETS
2283
+ VIEWPORT_PRESETS as VIEWPORT_PRESETS3
1494
2284
  } from "@bendyline/squisq/doc";
1495
2285
  import { parseMarkdown } from "@bendyline/squisq/markdown";
1496
2286
 
@@ -1780,7 +2570,7 @@ function seekVideoToFrame(video, targetTime, timeoutMs = DEFAULT_VIDEO_FRAME_TIM
1780
2570
  }
1781
2571
 
1782
2572
  // src/DocPlayer.tsx
1783
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
2573
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1784
2574
  function isDevEnvironment() {
1785
2575
  try {
1786
2576
  return typeof process !== "undefined" && process.env.NODE_ENV !== "production";
@@ -1812,15 +2602,30 @@ function waitForVisualUpdate() {
1812
2602
  }
1813
2603
  function DocPlayer(props) {
1814
2604
  const { doc, markdown } = props;
1815
- const markdownDoc = useMemo3(
2605
+ const markdownDoc = useMemo5(
1816
2606
  () => !doc && markdown !== void 0 ? markdownToDoc(parseMarkdown(markdown)) : void 0,
1817
2607
  [doc, markdown]
1818
2608
  );
1819
2609
  const resolvedDoc = doc ?? markdownDoc;
1820
2610
  if (!resolvedDoc) {
1821
- return /* @__PURE__ */ jsx7("div", { className: "doc-player doc-player--empty" });
2611
+ return /* @__PURE__ */ jsx9("div", { className: "doc-player doc-player--empty" });
1822
2612
  }
1823
- return /* @__PURE__ */ jsx7(DocPlayerContent, { ...props, doc: resolvedDoc });
2613
+ if (props.displayMode === "flashcards") {
2614
+ return /* @__PURE__ */ jsx9("div", { className: "doc-player doc-player--flashcards", children: /* @__PURE__ */ jsx9(
2615
+ FlashcardView,
2616
+ {
2617
+ doc: resolvedDoc,
2618
+ basePath: props.basePath,
2619
+ theme: props.theme,
2620
+ globalKeyboardShortcuts: props.globalKeyboardShortcuts,
2621
+ showCodeCopyButton: props.showCodeCopyButton,
2622
+ onCopyCode: props.onCopyCode,
2623
+ fenceRenderers: props.fenceRenderers,
2624
+ viewport: props.forceViewport
2625
+ }
2626
+ ) });
2627
+ }
2628
+ return /* @__PURE__ */ jsx9(DocPlayerContent, { ...props, doc: resolvedDoc });
1824
2629
  }
1825
2630
  function DocPlayerContent({
1826
2631
  doc,
@@ -1846,6 +2651,10 @@ function DocPlayerContent({
1846
2651
  onBlockMarkers,
1847
2652
  forceViewport,
1848
2653
  displayMode = "video",
2654
+ dashboardLayout,
2655
+ dashboardShowTitle,
2656
+ dashboardStyle,
2657
+ dashboardDocumentTitle,
1849
2658
  showCoverSlide,
1850
2659
  coverSlideTemplate,
1851
2660
  coverSlideDuration,
@@ -1866,20 +2675,21 @@ function DocPlayerContent({
1866
2675
  }) {
1867
2676
  const isSlideshowMode = displayMode === "slideshow";
1868
2677
  const isLinearMode = displayMode === "linear";
1869
- const audioRef = useRef5(null);
1870
- const containerRef = useRef5(null);
2678
+ const isDashboardMode = displayMode === "dashboard";
2679
+ const audioRef = useRef7(null);
2680
+ const containerRef = useRef7(null);
1871
2681
  const playerId = `squisq-player-${useId2().replace(/:/g, "")}`;
1872
- const [tapFeedback, setTapFeedback] = useState5(null);
1873
- const tapFeedbackTimer = useRef5();
2682
+ const [tapFeedback, setTapFeedback] = useState6(null);
2683
+ const tapFeedbackTimer = useRef7();
1874
2684
  const { viewport } = useViewportOrientation();
1875
- const activeViewport = forceViewport || (renderMode ? VIEWPORT_PRESETS.landscape : viewport);
2685
+ const activeViewport = forceViewport || (renderMode ? VIEWPORT_PRESETS3.landscape : viewport);
1876
2686
  const activeOrientation = activeViewport.height > activeViewport.width ? "portrait" : "landscape";
1877
- const isDebugMode = useMemo3(() => {
2687
+ const isDebugMode = useMemo5(() => {
1878
2688
  if (typeof window === "undefined") return false;
1879
2689
  const params = new URLSearchParams(window.location.search);
1880
2690
  return params.get("debug") === "true";
1881
2691
  }, []);
1882
- const syntheticDuration = useMemo3(
2692
+ const syntheticDuration = useMemo5(
1883
2693
  () => audioMode === "synthetic" ? getDocPlaybackDuration(doc) : 0,
1884
2694
  [audioMode, doc]
1885
2695
  );
@@ -1892,7 +2702,7 @@ function DocPlayerContent({
1892
2702
  syntheticDuration
1893
2703
  );
1894
2704
  const audio = externalAudioController || internalAudio;
1895
- useEffect5(() => {
2705
+ useEffect7(() => {
1896
2706
  if (warnedMissingStyles || !isDevEnvironment()) return;
1897
2707
  const el = containerRef.current;
1898
2708
  if (!el || typeof getComputedStyle !== "function") return;
@@ -1920,16 +2730,16 @@ function DocPlayerContent({
1920
2730
  skipToSegment: _skipToSegment,
1921
2731
  restart
1922
2732
  } = audio;
1923
- const [renderClock, setRenderClock] = useState5(null);
2733
+ const [renderClock, setRenderClock] = useState6(null);
1924
2734
  const renderTimeOverride = renderClock?.doc === doc ? renderClock.time : null;
1925
2735
  const currentTime = renderMode ? renderTimeOverride ?? audioCurrentTime : audioCurrentTime;
1926
- const rawSchedule = useMemo3(() => resolveMediaSchedule(doc), [doc]);
2736
+ const rawSchedule = useMemo5(() => resolveMediaSchedule(doc), [doc]);
1927
2737
  const clipDurations = useMediaClipDurations(rawSchedule, basePath);
1928
- const mediaSchedule = useMemo3(
2738
+ const mediaSchedule = useMemo5(
1929
2739
  () => resolveMediaSchedule(doc, { intrinsicDuration: (clip) => clipDurations.get(clip.src) }),
1930
2740
  [doc, clipDurations]
1931
2741
  );
1932
- const startActiveTimelineMedia = useCallback4(() => {
2742
+ const startActiveTimelineMedia = useCallback5(() => {
1933
2743
  if (renderMode) return;
1934
2744
  const root = containerRef.current;
1935
2745
  if (!root) return;
@@ -1942,21 +2752,21 @@ function DocPlayerContent({
1942
2752
  });
1943
2753
  });
1944
2754
  }, [renderMode]);
1945
- const playWithTimelineMedia = useCallback4(() => {
2755
+ const playWithTimelineMedia = useCallback5(() => {
1946
2756
  startActiveTimelineMedia();
1947
2757
  return play();
1948
2758
  }, [play, startActiveTimelineMedia]);
1949
- const toggleWithTimelineMedia = useCallback4(() => {
2759
+ const toggleWithTimelineMedia = useCallback5(() => {
1950
2760
  if (!isPlaying) startActiveTimelineMedia();
1951
2761
  return toggle();
1952
2762
  }, [isPlaying, startActiveTimelineMedia, toggle]);
1953
- const currentTimeRef = useRef5(currentTime);
2763
+ const currentTimeRef = useRef7(currentTime);
1954
2764
  currentTimeRef.current = currentTime;
1955
- const committedRenderTimeRef = useRef5(currentTime);
1956
- const renderCommitWaitersRef = useRef5([]);
1957
- const totalDurationRef = useRef5(totalDuration);
2765
+ const committedRenderTimeRef = useRef7(currentTime);
2766
+ const renderCommitWaitersRef = useRef7([]);
2767
+ const totalDurationRef = useRef7(totalDuration);
1958
2768
  totalDurationRef.current = totalDuration;
1959
- const expandedBlocksLenRef = useRef5(0);
2769
+ const expandedBlocksLenRef = useRef7(0);
1960
2770
  useLayoutEffect2(() => {
1961
2771
  committedRenderTimeRef.current = currentTime;
1962
2772
  const pending = renderCommitWaitersRef.current;
@@ -1966,14 +2776,14 @@ function DocPlayerContent({
1966
2776
  return false;
1967
2777
  });
1968
2778
  }, [currentTime]);
1969
- useEffect5(
2779
+ useEffect7(
1970
2780
  () => () => {
1971
2781
  const error = new Error("DocPlayer unmounted before the requested frame committed.");
1972
2782
  renderCommitWaitersRef.current.splice(0).forEach((waiter) => waiter.reject(error));
1973
2783
  },
1974
2784
  []
1975
2785
  );
1976
- const waitForRenderCommit = useCallback4((time) => {
2786
+ const waitForRenderCommit = useCallback5((time) => {
1977
2787
  if (Math.abs(committedRenderTimeRef.current - time) <= RENDER_TIME_EPSILON_SECONDS) {
1978
2788
  return Promise.resolve();
1979
2789
  }
@@ -1981,7 +2791,7 @@ function DocPlayerContent({
1981
2791
  renderCommitWaitersRef.current.push({ time, resolve, reject });
1982
2792
  });
1983
2793
  }, []);
1984
- const handleContainerClick = useCallback4(
2794
+ const handleContainerClick = useCallback5(
1985
2795
  (e) => {
1986
2796
  if (renderMode || isLinearMode) return;
1987
2797
  const target = e.target;
@@ -2006,7 +2816,7 @@ function DocPlayerContent({
2006
2816
  );
2007
2817
  const autoSurface = useAutoSurface(surface === "auto");
2008
2818
  const resolvedSurface = surface === "auto" ? autoSurface : surface;
2009
- const appearance = useMemo3(
2819
+ const appearance = useMemo5(
2010
2820
  () => resolveDocPlayerAppearance(doc, {
2011
2821
  theme,
2012
2822
  videoPresentation,
@@ -2031,13 +2841,13 @@ function DocPlayerContent({
2031
2841
  coverSlidePlayback
2032
2842
  ]
2033
2843
  );
2034
- const effectiveTheme = useMemo3(() => {
2844
+ const effectiveTheme = useMemo5(() => {
2035
2845
  const base = appearance.theme;
2036
2846
  return resolvedSurface ? applySurface(base, resolvedSurface) : base;
2037
2847
  }, [appearance.theme, resolvedSurface]);
2038
- const resolvedPipStyle = useMemo3(() => pipStyleVars(effectiveTheme), [effectiveTheme]);
2848
+ const resolvedPipStyle = useMemo5(() => pipStyleVars(effectiveTheme), [effectiveTheme]);
2039
2849
  const pipVars = resolvedPipStyle;
2040
- const pipFrameStyle = useMemo3(
2850
+ const pipFrameStyle = useMemo5(
2041
2851
  () => ({
2042
2852
  border: resolvedPipStyle["--squisq-pip-border"],
2043
2853
  borderRadius: resolvedPipStyle["--squisq-pip-radius"],
@@ -2067,7 +2877,7 @@ function DocPlayerContent({
2067
2877
  // remove authored slides from the default loss-averse projection.
2068
2878
  useAudioSegmentTiming: audioMode !== "synthetic"
2069
2879
  });
2070
- const coverBlock = useMemo3(() => {
2880
+ const coverBlock = useMemo5(() => {
2071
2881
  const startBlockConfig = doc.startBlock;
2072
2882
  if (!appearance.showCoverSlide) return null;
2073
2883
  if (!startBlockConfig) return null;
@@ -2090,13 +2900,13 @@ function DocPlayerContent({
2090
2900
  appearance.coverSlideTemplate
2091
2901
  ]);
2092
2902
  const hasManagedCover = !!coverBlock;
2093
- const [slideshowCoverVisible, setSlideshowCoverVisible] = useState5(false);
2094
- const [isSlideshowPickerOpen, setIsSlideshowPickerOpen] = useState5(false);
2095
- const slideshowCoverInitKeyRef = useRef5("");
2096
- useEffect5(() => {
2903
+ const [slideshowCoverVisible, setSlideshowCoverVisible] = useState6(false);
2904
+ const [isSlideshowPickerOpen, setIsSlideshowPickerOpen] = useState6(false);
2905
+ const slideshowCoverInitKeyRef = useRef7("");
2906
+ useEffect7(() => {
2097
2907
  slideshowCoverInitKeyRef.current = "";
2098
2908
  }, [doc]);
2099
- useEffect5(() => {
2909
+ useEffect7(() => {
2100
2910
  const initKey = `${isSlideshowMode}:${hasManagedCover}:${renderMode}`;
2101
2911
  if (slideshowCoverInitKeyRef.current === initKey) return;
2102
2912
  slideshowCoverInitKeyRef.current = initKey;
@@ -2107,12 +2917,12 @@ function DocPlayerContent({
2107
2917
  setSlideshowCoverVisible(false);
2108
2918
  }
2109
2919
  }, [isSlideshowMode, hasManagedCover, renderMode, pause]);
2110
- const [coverForced, setCoverForced] = useState5(false);
2111
- const [coverGraceActive, setCoverGraceActive] = useState5(false);
2112
- const coverGraceTimer = useRef5();
2113
- const coverWasShowing = useRef5(false);
2114
- const hasPlayedOnce = useRef5(false);
2115
- useEffect5(() => {
2920
+ const [coverForced, setCoverForced] = useState6(false);
2921
+ const [coverGraceActive, setCoverGraceActive] = useState6(false);
2922
+ const coverGraceTimer = useRef7();
2923
+ const coverWasShowing = useRef7(false);
2924
+ const hasPlayedOnce = useRef7(false);
2925
+ useEffect7(() => {
2116
2926
  hasPlayedOnce.current = false;
2117
2927
  coverWasShowing.current = false;
2118
2928
  clearTimeout(coverGraceTimer.current);
@@ -2121,7 +2931,7 @@ function DocPlayerContent({
2121
2931
  }, [doc]);
2122
2932
  const atRest = !!(coverBlock && !isSlideshowMode && !isPlaying && currentTime === 0 && !hasPlayedOnce.current && !renderMode && !autoPlay);
2123
2933
  if (atRest) coverWasShowing.current = true;
2124
- useEffect5(() => {
2934
+ useEffect7(() => {
2125
2935
  if (isPlaying && coverWasShowing.current && coverBlock && !renderMode && !isSlideshowMode) {
2126
2936
  coverWasShowing.current = false;
2127
2937
  hasPlayedOnce.current = true;
@@ -2132,8 +2942,8 @@ function DocPlayerContent({
2132
2942
  );
2133
2943
  }
2134
2944
  }, [isPlaying, coverBlock, renderMode, isSlideshowMode, appearance.coverSlideDuration]);
2135
- useEffect5(() => () => clearTimeout(coverGraceTimer.current), []);
2136
- useEffect5(() => () => clearTimeout(tapFeedbackTimer.current), []);
2945
+ useEffect7(() => () => clearTimeout(coverGraceTimer.current), []);
2946
+ useEffect7(() => () => clearTimeout(tapFeedbackTimer.current), []);
2137
2947
  const showVideoCoverBlock = !isSlideshowMode && !isLinearMode && !!coverBlock && (coverForced || coverGraceActive || !isPlaying && currentTime === 0 && !hasPlayedOnce.current && !renderMode && !autoPlay);
2138
2948
  const effectiveSlideshowCoverVisible = coverVisible ?? slideshowCoverVisible;
2139
2949
  const showSlideshowCover = !!(isSlideshowMode && !isLinearMode && !renderMode && coverBlock && effectiveSlideshowCoverVisible);
@@ -2141,29 +2951,29 @@ function DocPlayerContent({
2141
2951
  const slideshowHasCover = !!(isSlideshowMode && !renderMode && coverBlock);
2142
2952
  const slideshowSlideIndex = slideshowHasCover ? effectiveSlideshowCoverVisible ? 0 : currentBlockIndex + 1 : currentBlockIndex;
2143
2953
  const slideshowTotalSlides = slideshowHasCover ? expandedBlocks.length + 1 : expandedBlocks.length;
2144
- const hasAutoPlayed = useRef5(false);
2145
- useEffect5(() => {
2954
+ const hasAutoPlayed = useRef7(false);
2955
+ useEffect7(() => {
2146
2956
  hasAutoPlayed.current = false;
2147
2957
  }, [doc]);
2148
- useEffect5(() => {
2958
+ useEffect7(() => {
2149
2959
  if (isAudioReady && autoPlay && !hasAutoPlayed.current) {
2150
2960
  hasAutoPlayed.current = true;
2151
2961
  playWithTimelineMedia();
2152
2962
  }
2153
2963
  }, [isAudioReady, autoPlay, playWithTimelineMedia]);
2154
- useEffect5(() => {
2964
+ useEffect7(() => {
2155
2965
  onTimeUpdate?.(currentTime);
2156
2966
  }, [currentTime, onTimeUpdate]);
2157
- useEffect5(() => {
2967
+ useEffect7(() => {
2158
2968
  if (isEnded) {
2159
2969
  onEnded?.();
2160
- if (loop && !isSlideshowMode && !isLinearMode) {
2970
+ if (loop && !isSlideshowMode && !isLinearMode && !isDashboardMode) {
2161
2971
  void restart();
2162
2972
  }
2163
2973
  }
2164
- }, [isEnded, isLinearMode, isSlideshowMode, loop, onEnded, restart]);
2165
- const liveRenderAPIRef = useRef5(null);
2166
- const stableRenderAPIRef = useRef5(null);
2974
+ }, [isEnded, isDashboardMode, isLinearMode, isSlideshowMode, loop, onEnded, restart]);
2975
+ const liveRenderAPIRef = useRef7(null);
2976
+ const stableRenderAPIRef = useRef7(null);
2167
2977
  if (!stableRenderAPIRef.current) {
2168
2978
  const current = () => {
2169
2979
  const api = liveRenderAPIRef.current;
@@ -2184,8 +2994,8 @@ function DocPlayerContent({
2184
2994
  };
2185
2995
  }
2186
2996
  const stableRenderAPI = stableRenderAPIRef.current;
2187
- useEffect5(() => {
2188
- if (!renderMode && !isDebugMode) {
2997
+ useEffect7(() => {
2998
+ if (isDashboardMode || !renderMode && !isDebugMode) {
2189
2999
  liveRenderAPIRef.current = null;
2190
3000
  return;
2191
3001
  }
@@ -2311,31 +3121,33 @@ function DocPlayerContent({
2311
3121
  coverBlock,
2312
3122
  doc,
2313
3123
  externalAudioController,
2314
- waitForRenderCommit
3124
+ waitForRenderCommit,
3125
+ isDashboardMode
2315
3126
  ]);
2316
- useEffect5(() => {
3127
+ useEffect7(() => {
3128
+ if (isDashboardMode) return;
2317
3129
  if (!renderMode && !isDebugMode || !containerRef.current) {
2318
3130
  onRenderAPIReady?.(null);
2319
3131
  return;
2320
3132
  }
2321
3133
  onRenderAPIReady?.(stableRenderAPI);
2322
3134
  return () => onRenderAPIReady?.(null);
2323
- }, [renderMode, isDebugMode, onRenderAPIReady, stableRenderAPI]);
3135
+ }, [renderMode, isDebugMode, isDashboardMode, onRenderAPIReady, stableRenderAPI]);
2324
3136
  const defaultMode = captionsEnabledProp === false ? "off" : captionStyle || "standard";
2325
- const [captionMode, setCaptionMode] = useState5(defaultMode);
2326
- useEffect5(() => {
3137
+ const [captionMode, setCaptionMode] = useState6(defaultMode);
3138
+ useEffect7(() => {
2327
3139
  setCaptionMode(defaultMode);
2328
3140
  }, [defaultMode]);
2329
3141
  const captionsEnabled = captionMode !== "off";
2330
3142
  const activeCaptionStyle = captionMode === "social" ? "social" : "standard";
2331
- const setCaptionsEnabled = useCallback4(
3143
+ const setCaptionsEnabled = useCallback5(
2332
3144
  (enabled) => {
2333
3145
  setCaptionMode(enabled ? captionStyle || "standard" : "off");
2334
3146
  onCaptionsToggle?.(enabled);
2335
3147
  },
2336
3148
  [onCaptionsToggle, captionStyle]
2337
3149
  );
2338
- const cycleCaptionMode = useCallback4(() => {
3150
+ const cycleCaptionMode = useCallback5(() => {
2339
3151
  setCaptionMode((prev) => {
2340
3152
  const next = prev === "off" ? "standard" : prev === "standard" ? "social" : "off";
2341
3153
  onCaptionsToggle?.(next !== "off");
@@ -2343,8 +3155,8 @@ function DocPlayerContent({
2343
3155
  });
2344
3156
  }, [onCaptionsToggle]);
2345
3157
  const hasCaptions = doc.captions && doc.captions.phrases.length > 0;
2346
- const segmentTitleMap = useMemo3(() => buildSegmentTitleMap(doc), [doc]);
2347
- const playbackState = useMemo3(
3158
+ const segmentTitleMap = useMemo5(() => buildSegmentTitleMap(doc), [doc]);
3159
+ const playbackState = useMemo5(
2348
3160
  () => ({
2349
3161
  isPlaying,
2350
3162
  currentTime,
@@ -2387,7 +3199,7 @@ function DocPlayerContent({
2387
3199
  expandedBlocks.length
2388
3200
  ]
2389
3201
  );
2390
- const playbackActions = useMemo3(
3202
+ const playbackActions = useMemo5(
2391
3203
  () => ({
2392
3204
  toggle: toggleWithTimelineMedia,
2393
3205
  restart,
@@ -2405,7 +3217,7 @@ function DocPlayerContent({
2405
3217
  onFullscreenToggle
2406
3218
  ]
2407
3219
  );
2408
- const slideNavActions = useMemo3(
3220
+ const slideNavActions = useMemo5(
2409
3221
  () => ({
2410
3222
  nextSlide: () => {
2411
3223
  if (slideshowHasCover && slideshowCoverVisible) {
@@ -2470,7 +3282,7 @@ function DocPlayerContent({
2470
3282
  [currentBlockIndex, expandedBlocks, seekTo, pause, slideshowHasCover, slideshowCoverVisible]
2471
3283
  );
2472
3284
  const swipeEnabled = isSlideshowMode && !isLinearMode && !renderMode && enableSwipe;
2473
- const armContextFreeSwipeEntry = useCallback4(
3285
+ const armContextFreeSwipeEntry = useCallback5(
2474
3286
  (destinationSlideIndex) => {
2475
3287
  const destinationBlockIndex = destinationSlideIndex - (slideshowHasCover ? 1 : 0);
2476
3288
  const destinationBlock = expandedBlocks[destinationBlockIndex];
@@ -2478,11 +3290,11 @@ function DocPlayerContent({
2478
3290
  },
2479
3291
  [expandedBlocks, slideshowHasCover, suppressOutgoingForNextBlock]
2480
3292
  );
2481
- const handleSwipeNext = useCallback4(() => {
3293
+ const handleSwipeNext = useCallback5(() => {
2482
3294
  armContextFreeSwipeEntry(slideshowSlideIndex + 1);
2483
3295
  slideNavActions.nextSlide();
2484
3296
  }, [armContextFreeSwipeEntry, slideshowSlideIndex, slideNavActions]);
2485
- const handleSwipePrev = useCallback4(() => {
3297
+ const handleSwipePrev = useCallback5(() => {
2486
3298
  armContextFreeSwipeEntry(slideshowSlideIndex - 1);
2487
3299
  slideNavActions.prevSlide();
2488
3300
  }, [armContextFreeSwipeEntry, slideshowSlideIndex, slideNavActions]);
@@ -2494,13 +3306,13 @@ function DocPlayerContent({
2494
3306
  onNext: handleSwipeNext,
2495
3307
  onPrev: handleSwipePrev
2496
3308
  });
2497
- useEffect5(() => {
3309
+ useEffect7(() => {
2498
3310
  onPlaybackStateChange?.(playbackState);
2499
3311
  }, [playbackState, onPlaybackStateChange]);
2500
- useEffect5(() => {
3312
+ useEffect7(() => {
2501
3313
  onControlsReady?.({ play: playWithTimelineMedia, pause, ...playbackActions });
2502
3314
  }, [playWithTimelineMedia, pause, playbackActions, onControlsReady]);
2503
- const getBlockTitle = useCallback4((block) => {
3315
+ const getBlockTitle = useCallback5((block) => {
2504
3316
  const docBlock = block;
2505
3317
  if (isTemplateBlock2(docBlock)) {
2506
3318
  const props = docBlock;
@@ -2524,7 +3336,7 @@ function DocPlayerContent({
2524
3336
  }
2525
3337
  return block.id.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2526
3338
  }, []);
2527
- const slideshowPickerItems = useMemo3(() => {
3339
+ const slideshowPickerItems = useMemo5(() => {
2528
3340
  const blockItems = expandedBlocks.map((block, index) => ({
2529
3341
  id: block.id,
2530
3342
  label: String(index + 1),
@@ -2540,7 +3352,7 @@ function DocPlayerContent({
2540
3352
  ...blockItems
2541
3353
  ];
2542
3354
  }, [coverBlock, expandedBlocks, getBlockTitle, slideshowHasCover]);
2543
- const blockMarkers = useMemo3(() => {
3355
+ const blockMarkers = useMemo5(() => {
2544
3356
  if (!totalDuration || !expandedBlocks.length) return [];
2545
3357
  let prevSegment = -1;
2546
3358
  return expandedBlocks.map((block, index) => {
@@ -2555,13 +3367,13 @@ function DocPlayerContent({
2555
3367
  };
2556
3368
  });
2557
3369
  }, [expandedBlocks, totalDuration, getBlockTitle]);
2558
- useEffect5(() => {
3370
+ useEffect7(() => {
2559
3371
  if (blockMarkers.length > 0) {
2560
3372
  onBlockMarkers?.(blockMarkers);
2561
3373
  }
2562
3374
  }, [blockMarkers, onBlockMarkers]);
2563
3375
  expandedBlocksLenRef.current = isSlideshowMode ? slideshowTotalSlides : expandedBlocks.length;
2564
- const handleKeyboardShortcut = useCallback4(
3376
+ const handleKeyboardShortcut = useCallback5(
2565
3377
  (e, global) => {
2566
3378
  if (e.defaultPrevented || e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
2567
3379
  const target = e.target instanceof Element ? e.target : null;
@@ -2577,7 +3389,7 @@ function DocPlayerContent({
2577
3389
  ) && !isSlideshowToolbarTarget) {
2578
3390
  return;
2579
3391
  }
2580
- if (isLinearMode) return;
3392
+ if (isLinearMode || isDashboardMode) return;
2581
3393
  if (e.key === "f" || e.key === "F") {
2582
3394
  if (!onFullscreenToggle) return;
2583
3395
  e.preventDefault();
@@ -2629,26 +3441,27 @@ function DocPlayerContent({
2629
3441
  [
2630
3442
  isSlideshowMode,
2631
3443
  isLinearMode,
3444
+ isDashboardMode,
2632
3445
  toggleWithTimelineMedia,
2633
3446
  seekTo,
2634
3447
  slideNavActions,
2635
3448
  onFullscreenToggle
2636
3449
  ]
2637
3450
  );
2638
- const handleKeyDown = useCallback4(
3451
+ const handleKeyDown = useCallback5(
2639
3452
  (e) => handleKeyboardShortcut(e, false),
2640
3453
  [handleKeyboardShortcut]
2641
3454
  );
2642
- useEffect5(() => {
2643
- if (!globalKeyboardShortcuts || renderMode || isLinearMode) return;
3455
+ useEffect7(() => {
3456
+ if (!globalKeyboardShortcuts || renderMode || isLinearMode || isDashboardMode) return;
2644
3457
  const handleDocumentKeyDown = (event) => {
2645
3458
  handleKeyboardShortcut(event, true);
2646
3459
  };
2647
3460
  document.addEventListener("keydown", handleDocumentKeyDown);
2648
3461
  return () => document.removeEventListener("keydown", handleDocumentKeyDown);
2649
- }, [globalKeyboardShortcuts, handleKeyboardShortcut, isLinearMode, renderMode]);
3462
+ }, [globalKeyboardShortcuts, handleKeyboardShortcut, isDashboardMode, isLinearMode, renderMode]);
2650
3463
  if (isLinearMode) {
2651
- return /* @__PURE__ */ jsx7(
3464
+ return /* @__PURE__ */ jsx9(
2652
3465
  "div",
2653
3466
  {
2654
3467
  ref: containerRef,
@@ -2660,7 +3473,7 @@ function DocPlayerContent({
2660
3473
  height: "100%",
2661
3474
  overflow: "hidden"
2662
3475
  },
2663
- children: /* @__PURE__ */ jsx7(
3476
+ children: /* @__PURE__ */ jsx9(
2664
3477
  LinearDocView,
2665
3478
  {
2666
3479
  doc,
@@ -2677,7 +3490,41 @@ function DocPlayerContent({
2677
3490
  }
2678
3491
  );
2679
3492
  }
2680
- return /* @__PURE__ */ jsxs4(
3493
+ if (isDashboardMode) {
3494
+ return /* @__PURE__ */ jsx9(
3495
+ "div",
3496
+ {
3497
+ ref: containerRef,
3498
+ "data-player-id": playerId,
3499
+ className: "doc-player doc-player--dashboard",
3500
+ style: {
3501
+ position: "relative",
3502
+ width: "100%",
3503
+ aspectRatio: `${activeViewport.width} / ${activeViewport.height}`,
3504
+ margin: "0 auto",
3505
+ overflow: "hidden"
3506
+ },
3507
+ children: /* @__PURE__ */ jsx9(
3508
+ DashboardView,
3509
+ {
3510
+ doc,
3511
+ theme,
3512
+ viewport: activeViewport,
3513
+ layout: dashboardLayout,
3514
+ showTitle: dashboardShowTitle,
3515
+ style: dashboardStyle,
3516
+ documentTitle: dashboardDocumentTitle,
3517
+ basePath,
3518
+ animationsEnabled,
3519
+ muted: renderMode || muted,
3520
+ renderMode: renderMode || isDebugMode,
3521
+ onRenderAPIReady
3522
+ }
3523
+ )
3524
+ }
3525
+ );
3526
+ }
3527
+ return /* @__PURE__ */ jsxs6(
2681
3528
  "div",
2682
3529
  {
2683
3530
  ref: containerRef,
@@ -2705,8 +3552,8 @@ function DocPlayerContent({
2705
3552
  touchAction: swipeEnabled ? "pan-y" : void 0
2706
3553
  },
2707
3554
  children: [
2708
- /* @__PURE__ */ jsx7("audio", { ref: audioRef, preload: "auto", muted }),
2709
- /* @__PURE__ */ jsx7(
3555
+ /* @__PURE__ */ jsx9("audio", { ref: audioRef, preload: "auto", muted }),
3556
+ /* @__PURE__ */ jsx9(
2710
3557
  MediaClipLayer,
2711
3558
  {
2712
3559
  schedule: mediaSchedule,
@@ -2723,8 +3570,8 @@ function DocPlayerContent({
2723
3570
  pipFrameStyle
2724
3571
  }
2725
3572
  ),
2726
- /* @__PURE__ */ jsxs4("div", { className: "doc-player__viewport", children: [
2727
- showCoverBlock && coverBlock && /* @__PURE__ */ jsx7("div", { className: "doc-player__block doc-player__block--cover", children: /* @__PURE__ */ jsx7(
3573
+ /* @__PURE__ */ jsxs6("div", { className: "doc-player__viewport", children: [
3574
+ showCoverBlock && coverBlock && /* @__PURE__ */ jsx9("div", { className: "doc-player__block doc-player__block--cover", children: /* @__PURE__ */ jsx9(
2728
3575
  BlockRenderer,
2729
3576
  {
2730
3577
  block: coverBlock,
@@ -2741,7 +3588,7 @@ function DocPlayerContent({
2741
3588
  // reconciles one block's layers onto another's (templates reuse layer
2742
3589
  // ids like `title`/`background`), which would otherwise reuse stale
2743
3590
  // DOM / skip entrance animations mid-transition.
2744
- /* @__PURE__ */ jsx7("div", { className: "doc-player__block doc-player__block--previous", children: /* @__PURE__ */ jsx7(
3591
+ /* @__PURE__ */ jsx9("div", { className: "doc-player__block doc-player__block--previous", children: /* @__PURE__ */ jsx9(
2745
3592
  BlockRenderer,
2746
3593
  {
2747
3594
  block: previousBlock,
@@ -2755,7 +3602,7 @@ function DocPlayerContent({
2755
3602
  theme: effectiveTheme
2756
3603
  }
2757
3604
  ) }, previousBlock.id),
2758
- currentBlock && /* @__PURE__ */ jsx7(
3605
+ currentBlock && /* @__PURE__ */ jsx9(
2759
3606
  "div",
2760
3607
  {
2761
3608
  className: `doc-player__block doc-player__block--active${swipe.phase !== "idle" ? ` doc-player__block--${swipe.phase}` : ""}`,
@@ -2764,7 +3611,7 @@ function DocPlayerContent({
2764
3611
  ...swipe.phase !== "idle" ? { transform: `translateX(${swipe.offsetPx}px)` } : {},
2765
3612
  ...showCoverBlock ? { opacity: 0, pointerEvents: "none" } : {}
2766
3613
  } : void 0,
2767
- children: /* @__PURE__ */ jsx7(
3614
+ children: /* @__PURE__ */ jsx9(
2768
3615
  BlockRenderer,
2769
3616
  {
2770
3617
  block: currentBlock,
@@ -2781,7 +3628,7 @@ function DocPlayerContent({
2781
3628
  },
2782
3629
  currentBlock.id
2783
3630
  ),
2784
- hasCaptions && (renderMode ? captionsEnabled : true) && /* @__PURE__ */ jsx7(
3631
+ hasCaptions && (renderMode ? captionsEnabled : true) && /* @__PURE__ */ jsx9(
2785
3632
  CaptionOverlay,
2786
3633
  {
2787
3634
  captions: doc.captions,
@@ -2793,7 +3640,7 @@ function DocPlayerContent({
2793
3640
  viewport: activeViewport
2794
3641
  }
2795
3642
  ),
2796
- isDebugMode && /* @__PURE__ */ jsxs4(
3643
+ isDebugMode && /* @__PURE__ */ jsxs6(
2797
3644
  "div",
2798
3645
  {
2799
3646
  className: "doc-player__debug",
@@ -2814,27 +3661,27 @@ function DocPlayerContent({
2814
3661
  textAlign: "left"
2815
3662
  },
2816
3663
  children: [
2817
- /* @__PURE__ */ jsx7("div", { style: { color: "#ffcc00", fontWeight: "bold", marginBottom: "4px" }, children: "DEBUG MODE" }),
2818
- /* @__PURE__ */ jsxs4("div", { children: [
2819
- /* @__PURE__ */ jsx7("span", { style: { color: "#888" }, children: "template:" }),
3664
+ /* @__PURE__ */ jsx9("div", { style: { color: "#ffcc00", fontWeight: "bold", marginBottom: "4px" }, children: "DEBUG MODE" }),
3665
+ /* @__PURE__ */ jsxs6("div", { children: [
3666
+ /* @__PURE__ */ jsx9("span", { style: { color: "#888" }, children: "template:" }),
2820
3667
  " ",
2821
- /* @__PURE__ */ jsx7("span", { style: { color: "#ff6b6b" }, children: currentBlock?.template ?? "raw" })
3668
+ /* @__PURE__ */ jsx9("span", { style: { color: "#ff6b6b" }, children: currentBlock?.template ?? "raw" })
2822
3669
  ] }),
2823
- /* @__PURE__ */ jsxs4("div", { children: [
2824
- /* @__PURE__ */ jsx7("span", { style: { color: "#888" }, children: "block:" }),
3670
+ /* @__PURE__ */ jsxs6("div", { children: [
3671
+ /* @__PURE__ */ jsx9("span", { style: { color: "#888" }, children: "block:" }),
2825
3672
  " ",
2826
3673
  currentBlockIndex + 1,
2827
3674
  "/",
2828
3675
  expandedBlocks.length,
2829
3676
  " ",
2830
- /* @__PURE__ */ jsxs4("span", { style: { color: "#666" }, children: [
3677
+ /* @__PURE__ */ jsxs6("span", { style: { color: "#666" }, children: [
2831
3678
  "(",
2832
3679
  currentBlock?.id || "none",
2833
3680
  ")"
2834
3681
  ] })
2835
3682
  ] }),
2836
- /* @__PURE__ */ jsxs4("div", { children: [
2837
- /* @__PURE__ */ jsx7("span", { style: { color: "#888" }, children: "time:" }),
3683
+ /* @__PURE__ */ jsxs6("div", { children: [
3684
+ /* @__PURE__ */ jsx9("span", { style: { color: "#888" }, children: "time:" }),
2838
3685
  " ",
2839
3686
  currentTime.toFixed(2),
2840
3687
  "s /",
@@ -2842,7 +3689,7 @@ function DocPlayerContent({
2842
3689
  totalDuration.toFixed(1),
2843
3690
  "s",
2844
3691
  " ",
2845
- /* @__PURE__ */ jsxs4("span", { style: { color: "#666" }, children: [
3692
+ /* @__PURE__ */ jsxs6("span", { style: { color: "#666" }, children: [
2846
3693
  "(progress: ",
2847
3694
  (docProgress * 100).toFixed(1),
2848
3695
  "%, scriptDur: ",
@@ -2850,8 +3697,8 @@ function DocPlayerContent({
2850
3697
  ")"
2851
3698
  ] })
2852
3699
  ] }),
2853
- /* @__PURE__ */ jsxs4("div", { children: [
2854
- /* @__PURE__ */ jsx7("span", { style: { color: "#888" }, children: "blockTime:" }),
3700
+ /* @__PURE__ */ jsxs6("div", { children: [
3701
+ /* @__PURE__ */ jsx9("span", { style: { color: "#888" }, children: "blockTime:" }),
2855
3702
  " ",
2856
3703
  blockTime.toFixed(2),
2857
3704
  "s /",
@@ -2859,58 +3706,58 @@ function DocPlayerContent({
2859
3706
  (currentBlock?.duration || 0).toFixed(1),
2860
3707
  "s"
2861
3708
  ] }),
2862
- /* @__PURE__ */ jsxs4("div", { children: [
2863
- /* @__PURE__ */ jsx7("span", { style: { color: "#888" }, children: "segment:" }),
3709
+ /* @__PURE__ */ jsxs6("div", { children: [
3710
+ /* @__PURE__ */ jsx9("span", { style: { color: "#888" }, children: "segment:" }),
2864
3711
  " ",
2865
3712
  currentSegment,
2866
3713
  "/",
2867
3714
  doc.audio.segments.length - 1,
2868
3715
  " ",
2869
- /* @__PURE__ */ jsxs4("span", { style: { color: "#666" }, children: [
3716
+ /* @__PURE__ */ jsxs6("span", { style: { color: "#666" }, children: [
2870
3717
  "(",
2871
3718
  doc.audio.segments[currentSegment]?.name || "none",
2872
3719
  ")"
2873
3720
  ] })
2874
3721
  ] }),
2875
- /* @__PURE__ */ jsxs4("div", { children: [
2876
- /* @__PURE__ */ jsx7("span", { style: { color: "#888" }, children: "viewport:" }),
3722
+ /* @__PURE__ */ jsxs6("div", { children: [
3723
+ /* @__PURE__ */ jsx9("span", { style: { color: "#888" }, children: "viewport:" }),
2877
3724
  " ",
2878
3725
  activeViewport.name || `${activeViewport.width}x${activeViewport.height}`,
2879
3726
  " ",
2880
- /* @__PURE__ */ jsxs4("span", { style: { color: "#666" }, children: [
3727
+ /* @__PURE__ */ jsxs6("span", { style: { color: "#666" }, children: [
2881
3728
  "(",
2882
3729
  activeOrientation,
2883
3730
  ")"
2884
3731
  ] })
2885
3732
  ] }),
2886
- /* @__PURE__ */ jsxs4("div", { children: [
2887
- /* @__PURE__ */ jsx7("span", { style: { color: "#888" }, children: "playing:" }),
3733
+ /* @__PURE__ */ jsxs6("div", { children: [
3734
+ /* @__PURE__ */ jsx9("span", { style: { color: "#888" }, children: "playing:" }),
2888
3735
  " ",
2889
- /* @__PURE__ */ jsx7("span", { style: { color: isPlaying ? "#4ade80" : "#f87171" }, children: isPlaying ? "yes" : "no" }),
2890
- showCoverBlock && /* @__PURE__ */ jsx7("span", { style: { color: "#60a5fa" }, children: " (cover)" })
3736
+ /* @__PURE__ */ jsx9("span", { style: { color: isPlaying ? "#4ade80" : "#f87171" }, children: isPlaying ? "yes" : "no" }),
3737
+ showCoverBlock && /* @__PURE__ */ jsx9("span", { style: { color: "#60a5fa" }, children: " (cover)" })
2891
3738
  ] }),
2892
3739
  hasCaptions && (() => {
2893
3740
  const debugPhrase = getCaptionAtTime2(doc.captions, currentTime);
2894
3741
  const debugEnabled = captionsEnabled && (isPlaying || currentTime > 0);
2895
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
2896
- /* @__PURE__ */ jsxs4("div", { children: [
2897
- /* @__PURE__ */ jsx7("span", { style: { color: "#888" }, children: "captions:" }),
3742
+ return /* @__PURE__ */ jsxs6(Fragment4, { children: [
3743
+ /* @__PURE__ */ jsxs6("div", { children: [
3744
+ /* @__PURE__ */ jsx9("span", { style: { color: "#888" }, children: "captions:" }),
2898
3745
  " ",
2899
3746
  doc.captions?.phrases.length || 0,
2900
3747
  " phrases",
2901
3748
  " ",
2902
- /* @__PURE__ */ jsxs4("span", { style: { color: captionsEnabled ? "#4ade80" : "#666" }, children: [
3749
+ /* @__PURE__ */ jsxs6("span", { style: { color: captionsEnabled ? "#4ade80" : "#666" }, children: [
2903
3750
  "(",
2904
3751
  captionsEnabled ? "on" : "off",
2905
3752
  ")"
2906
3753
  ] })
2907
3754
  ] }),
2908
- /* @__PURE__ */ jsxs4("div", { children: [
2909
- /* @__PURE__ */ jsx7("span", { style: { color: "#888" }, children: "cc.enabled:" }),
3755
+ /* @__PURE__ */ jsxs6("div", { children: [
3756
+ /* @__PURE__ */ jsx9("span", { style: { color: "#888" }, children: "cc.enabled:" }),
2910
3757
  " ",
2911
- /* @__PURE__ */ jsx7("span", { style: { color: debugEnabled ? "#4ade80" : "#f87171" }, children: String(debugEnabled) }),
3758
+ /* @__PURE__ */ jsx9("span", { style: { color: debugEnabled ? "#4ade80" : "#f87171" }, children: String(debugEnabled) }),
2912
3759
  " ",
2913
- /* @__PURE__ */ jsxs4("span", { style: { color: "#666" }, children: [
3760
+ /* @__PURE__ */ jsxs6("span", { style: { color: "#666" }, children: [
2914
3761
  "(playing=",
2915
3762
  String(isPlaying),
2916
3763
  " t>0=",
@@ -2918,15 +3765,15 @@ function DocPlayerContent({
2918
3765
  ")"
2919
3766
  ] })
2920
3767
  ] }),
2921
- /* @__PURE__ */ jsxs4("div", { children: [
2922
- /* @__PURE__ */ jsx7("span", { style: { color: "#888" }, children: "cc.phrase:" }),
3768
+ /* @__PURE__ */ jsxs6("div", { children: [
3769
+ /* @__PURE__ */ jsx9("span", { style: { color: "#888" }, children: "cc.phrase:" }),
2923
3770
  " ",
2924
- /* @__PURE__ */ jsx7("span", { style: { color: debugPhrase ? "#4ade80" : "#f87171" }, children: debugPhrase ? `"${debugPhrase.text.slice(0, 30)}..."` : "null" })
3771
+ /* @__PURE__ */ jsx9("span", { style: { color: debugPhrase ? "#4ade80" : "#f87171" }, children: debugPhrase ? `"${debugPhrase.text.slice(0, 30)}..."` : "null" })
2925
3772
  ] }),
2926
- debugPhrase && /* @__PURE__ */ jsxs4("div", { children: [
2927
- /* @__PURE__ */ jsx7("span", { style: { color: "#888" }, children: "cc.range:" }),
3773
+ debugPhrase && /* @__PURE__ */ jsxs6("div", { children: [
3774
+ /* @__PURE__ */ jsx9("span", { style: { color: "#888" }, children: "cc.range:" }),
2928
3775
  " ",
2929
- /* @__PURE__ */ jsxs4("span", { style: { color: "#60a5fa" }, children: [
3776
+ /* @__PURE__ */ jsxs6("span", { style: { color: "#60a5fa" }, children: [
2930
3777
  debugPhrase.startTime.toFixed(2),
2931
3778
  "-",
2932
3779
  debugPhrase.endTime.toFixed(2)
@@ -2938,7 +3785,7 @@ function DocPlayerContent({
2938
3785
  }
2939
3786
  )
2940
3787
  ] }),
2941
- !isAvailable && unavailableMessage && /* @__PURE__ */ jsxs4(
3788
+ !isAvailable && unavailableMessage && /* @__PURE__ */ jsxs6(
2942
3789
  "div",
2943
3790
  {
2944
3791
  className: "doc-player__unavailable",
@@ -2959,12 +3806,12 @@ function DocPlayerContent({
2959
3806
  zIndex: 50
2960
3807
  },
2961
3808
  children: [
2962
- /* @__PURE__ */ jsx7("span", { style: { fontSize: "32px" }, children: "\u{1F50A}" }),
2963
- /* @__PURE__ */ jsx7("span", { children: unavailableMessage })
3809
+ /* @__PURE__ */ jsx9("span", { style: { fontSize: "32px" }, children: "\u{1F50A}" }),
3810
+ /* @__PURE__ */ jsx9("span", { children: unavailableMessage })
2964
3811
  ]
2965
3812
  }
2966
3813
  ),
2967
- !renderMode && !isSlideshowMode && showControls && /* @__PURE__ */ jsx7(
3814
+ !renderMode && !isSlideshowMode && showControls && /* @__PURE__ */ jsx9(
2968
3815
  DocControlsOverlay,
2969
3816
  {
2970
3817
  state: playbackState,
@@ -2974,7 +3821,7 @@ function DocPlayerContent({
2974
3821
  getBlockTitle
2975
3822
  }
2976
3823
  ),
2977
- !renderMode && !isSlideshowMode && !showControls && showScrubber && /* @__PURE__ */ jsx7(
3824
+ !renderMode && !isSlideshowMode && !showControls && showScrubber && /* @__PURE__ */ jsx9(
2978
3825
  "div",
2979
3826
  {
2980
3827
  className: "doc-player__scrubber",
@@ -2989,7 +3836,7 @@ function DocPlayerContent({
2989
3836
  alignItems: "center",
2990
3837
  zIndex: 100
2991
3838
  },
2992
- children: /* @__PURE__ */ jsx7(
3839
+ children: /* @__PURE__ */ jsx9(
2993
3840
  DocProgressBar,
2994
3841
  {
2995
3842
  state: playbackState,
@@ -3001,7 +3848,7 @@ function DocPlayerContent({
3001
3848
  )
3002
3849
  }
3003
3850
  ),
3004
- !renderMode && isSlideshowMode && showControls && /* @__PURE__ */ jsx7(
3851
+ !renderMode && isSlideshowMode && showControls && /* @__PURE__ */ jsx9(
3005
3852
  DocControlsSlideshow,
3006
3853
  {
3007
3854
  state: playbackState,
@@ -3011,14 +3858,14 @@ function DocPlayerContent({
3011
3858
  onPickerOpenChange: setIsSlideshowPickerOpen
3012
3859
  }
3013
3860
  ),
3014
- !isSlideshowMode && tapFeedback && /* @__PURE__ */ jsx7("div", { className: "doc-player__tap-feedback", children: /* @__PURE__ */ jsx7("svg", { viewBox: "0 0 24 24", fill: "white", width: "48", height: "48", children: tapFeedback === "pause" ? /* @__PURE__ */ jsx7("path", { d: "M6 19h4V5H6v14zm8-14v14h4V5h-4z" }) : /* @__PURE__ */ jsx7("path", { d: "M8 5v14l11-7z" }) }) }, tapFeedback)
3861
+ !isSlideshowMode && tapFeedback && /* @__PURE__ */ jsx9("div", { className: "doc-player__tap-feedback", children: /* @__PURE__ */ jsx9("svg", { viewBox: "0 0 24 24", fill: "white", width: "48", height: "48", children: tapFeedback === "pause" ? /* @__PURE__ */ jsx9("path", { d: "M6 19h4V5H6v14zm8-14v14h4V5h-4z" }) : /* @__PURE__ */ jsx9("path", { d: "M8 5v14l11-7z" }) }) }, tapFeedback)
3015
3862
  ]
3016
3863
  }
3017
3864
  );
3018
3865
  }
3019
3866
 
3020
3867
  // src/DocControlsBottom.tsx
3021
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
3868
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
3022
3869
  function DocControlsBottom({
3023
3870
  state,
3024
3871
  actions,
@@ -3026,32 +3873,32 @@ function DocControlsBottom({
3026
3873
  expandedBlocks,
3027
3874
  getBlockTitle
3028
3875
  }) {
3029
- return /* @__PURE__ */ jsxs5("div", { className: "doc-controls-bottom", children: [
3030
- /* @__PURE__ */ jsx8(
3876
+ return /* @__PURE__ */ jsxs7("div", { className: "doc-controls-bottom", children: [
3877
+ /* @__PURE__ */ jsx10(
3031
3878
  "button",
3032
3879
  {
3033
3880
  className: "bottom-ctrl-btn",
3034
3881
  onClick: actions.restart,
3035
3882
  title: "Restart",
3036
3883
  "aria-label": "Restart from beginning",
3037
- children: /* @__PURE__ */ jsx8("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsx8("path", { d: "M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z" }) })
3884
+ children: /* @__PURE__ */ jsx10("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsx10("path", { d: "M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z" }) })
3038
3885
  }
3039
3886
  ),
3040
- /* @__PURE__ */ jsx8(
3887
+ /* @__PURE__ */ jsx10(
3041
3888
  "button",
3042
3889
  {
3043
3890
  className: "bottom-ctrl-btn bottom-play-btn",
3044
3891
  onClick: actions.toggle,
3045
3892
  "aria-label": state.isPlaying ? "Pause" : "Play",
3046
- children: state.isPlaying ? /* @__PURE__ */ jsx8("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx8("path", { d: "M6 19h4V5H6v14zm8-14v14h4V5h-4z" }) }) : /* @__PURE__ */ jsx8("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx8("path", { d: "M8 5v14l11-7z" }) })
3893
+ children: state.isPlaying ? /* @__PURE__ */ jsx10("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx10("path", { d: "M6 19h4V5H6v14zm8-14v14h4V5h-4z" }) }) : /* @__PURE__ */ jsx10("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx10("path", { d: "M8 5v14l11-7z" }) })
3047
3894
  }
3048
3895
  ),
3049
- /* @__PURE__ */ jsxs5("span", { className: "bottom-time", children: [
3896
+ /* @__PURE__ */ jsxs7("span", { className: "bottom-time", children: [
3050
3897
  formatTime(state.currentTime),
3051
3898
  " / ",
3052
3899
  formatTime(state.totalDuration)
3053
3900
  ] }),
3054
- /* @__PURE__ */ jsx8(
3901
+ /* @__PURE__ */ jsx10(
3055
3902
  DocProgressBar,
3056
3903
  {
3057
3904
  state,
@@ -3061,92 +3908,92 @@ function DocControlsBottom({
3061
3908
  getBlockTitle
3062
3909
  }
3063
3910
  ),
3064
- /* @__PURE__ */ jsxs5("span", { className: "bottom-segment", children: [
3911
+ /* @__PURE__ */ jsxs7("span", { className: "bottom-segment", children: [
3065
3912
  state.currentBlockIndex + 1,
3066
3913
  "/",
3067
3914
  state.totalBlocks
3068
3915
  ] }),
3069
- state.hasCaptions && /* @__PURE__ */ jsx8(
3916
+ state.hasCaptions && /* @__PURE__ */ jsx10(
3070
3917
  "button",
3071
3918
  {
3072
3919
  className: `bottom-ctrl-btn ${state.captionMode !== "off" ? "bottom-ctrl-btn--active" : ""}`,
3073
3920
  onClick: () => actions.cycleCaptionMode(),
3074
3921
  title: state.captionMode === "off" ? "Captions: Off" : state.captionMode === "standard" ? "Captions: Standard" : "Captions: Social",
3075
3922
  "aria-label": "Cycle caption style",
3076
- children: /* @__PURE__ */ jsx8("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsx8("path", { d: "M19 4H5a2 2 0 00-2 2v12a2 2 0 002 2h14a2 2 0 002-2V6a2 2 0 00-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1a1 1 0 01-1 1h-3a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1z" }) })
3923
+ children: /* @__PURE__ */ jsx10("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsx10("path", { d: "M19 4H5a2 2 0 00-2 2v12a2 2 0 002 2h14a2 2 0 002-2V6a2 2 0 00-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1a1 1 0 01-1 1h-3a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1z" }) })
3077
3924
  }
3078
3925
  ),
3079
- actions.toggleFullscreen && /* @__PURE__ */ jsx8(
3926
+ actions.toggleFullscreen && /* @__PURE__ */ jsx10(
3080
3927
  "button",
3081
3928
  {
3082
3929
  className: `bottom-ctrl-btn ${state.isFullscreen ? "bottom-ctrl-btn--active" : ""}`,
3083
3930
  onClick: actions.toggleFullscreen,
3084
3931
  title: state.isFullscreen ? "Exit fullscreen (F)" : "Fullscreen (F)",
3085
3932
  "aria-label": state.isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
3086
- children: state.isFullscreen ? /* @__PURE__ */ jsx8("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsx8("path", { d: "M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" }) }) : /* @__PURE__ */ jsx8("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsx8("path", { d: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" }) })
3933
+ children: state.isFullscreen ? /* @__PURE__ */ jsx10("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsx10("path", { d: "M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" }) }) : /* @__PURE__ */ jsx10("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsx10("path", { d: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" }) })
3087
3934
  }
3088
3935
  )
3089
3936
  ] });
3090
3937
  }
3091
3938
 
3092
3939
  // src/DocControlsSidebar.tsx
3093
- import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
3940
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
3094
3941
  function DocControlsSidebar({ state, actions }) {
3095
- return /* @__PURE__ */ jsxs6("div", { className: "doc-controls-sidebar", children: [
3096
- /* @__PURE__ */ jsx9(
3942
+ return /* @__PURE__ */ jsxs8("div", { className: "doc-controls-sidebar", children: [
3943
+ /* @__PURE__ */ jsx11(
3097
3944
  "button",
3098
3945
  {
3099
3946
  className: "sidebar-ctrl-btn",
3100
3947
  onClick: actions.restart,
3101
3948
  title: "Restart",
3102
3949
  "aria-label": "Restart from beginning",
3103
- children: /* @__PURE__ */ jsx9("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx9("path", { d: "M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z" }) })
3950
+ children: /* @__PURE__ */ jsx11("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx11("path", { d: "M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z" }) })
3104
3951
  }
3105
3952
  ),
3106
- /* @__PURE__ */ jsx9(
3953
+ /* @__PURE__ */ jsx11(
3107
3954
  "button",
3108
3955
  {
3109
3956
  className: "sidebar-ctrl-btn sidebar-play-btn",
3110
3957
  onClick: actions.toggle,
3111
3958
  "aria-label": state.isPlaying ? "Pause" : "Play",
3112
- children: state.isPlaying ? /* @__PURE__ */ jsx9("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "22", height: "22", children: /* @__PURE__ */ jsx9("path", { d: "M6 19h4V5H6v14zm8-14v14h4V5h-4z" }) }) : /* @__PURE__ */ jsx9("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "22", height: "22", children: /* @__PURE__ */ jsx9("path", { d: "M8 5v14l11-7z" }) })
3959
+ children: state.isPlaying ? /* @__PURE__ */ jsx11("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "22", height: "22", children: /* @__PURE__ */ jsx11("path", { d: "M6 19h4V5H6v14zm8-14v14h4V5h-4z" }) }) : /* @__PURE__ */ jsx11("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "22", height: "22", children: /* @__PURE__ */ jsx11("path", { d: "M8 5v14l11-7z" }) })
3113
3960
  }
3114
3961
  ),
3115
- /* @__PURE__ */ jsxs6("div", { className: "sidebar-time", children: [
3116
- /* @__PURE__ */ jsx9("div", { children: formatTime(state.currentTime) }),
3117
- /* @__PURE__ */ jsx9("div", { className: "sidebar-time-total", children: formatTime(state.totalDuration) })
3962
+ /* @__PURE__ */ jsxs8("div", { className: "sidebar-time", children: [
3963
+ /* @__PURE__ */ jsx11("div", { children: formatTime(state.currentTime) }),
3964
+ /* @__PURE__ */ jsx11("div", { className: "sidebar-time-total", children: formatTime(state.totalDuration) })
3118
3965
  ] }),
3119
- /* @__PURE__ */ jsxs6("div", { className: "sidebar-segment", children: [
3966
+ /* @__PURE__ */ jsxs8("div", { className: "sidebar-segment", children: [
3120
3967
  state.currentBlockIndex + 1,
3121
3968
  "/",
3122
3969
  state.totalBlocks
3123
3970
  ] }),
3124
- state.hasCaptions && /* @__PURE__ */ jsx9(
3971
+ state.hasCaptions && /* @__PURE__ */ jsx11(
3125
3972
  "button",
3126
3973
  {
3127
3974
  className: `sidebar-ctrl-btn ${state.captionMode !== "off" ? "sidebar-ctrl-btn--active" : ""}`,
3128
3975
  onClick: () => actions.cycleCaptionMode(),
3129
3976
  title: state.captionMode === "off" ? "Captions: Off" : state.captionMode === "standard" ? "Captions: Standard" : "Captions: Social",
3130
3977
  "aria-label": "Cycle caption style",
3131
- children: /* @__PURE__ */ jsx9("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx9("path", { d: "M19 4H5a2 2 0 00-2 2v12a2 2 0 002 2h14a2 2 0 002-2V6a2 2 0 00-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1a1 1 0 01-1 1h-3a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1z" }) })
3978
+ children: /* @__PURE__ */ jsx11("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx11("path", { d: "M19 4H5a2 2 0 00-2 2v12a2 2 0 002 2h14a2 2 0 002-2V6a2 2 0 00-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1a1 1 0 01-1 1h-3a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1z" }) })
3132
3979
  }
3133
3980
  ),
3134
- actions.toggleFullscreen && /* @__PURE__ */ jsx9(
3981
+ actions.toggleFullscreen && /* @__PURE__ */ jsx11(
3135
3982
  "button",
3136
3983
  {
3137
3984
  className: `sidebar-ctrl-btn ${state.isFullscreen ? "sidebar-ctrl-btn--active" : ""}`,
3138
3985
  onClick: actions.toggleFullscreen,
3139
3986
  title: state.isFullscreen ? "Exit fullscreen (F)" : "Fullscreen (F)",
3140
3987
  "aria-label": state.isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
3141
- children: state.isFullscreen ? /* @__PURE__ */ jsx9("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx9("path", { d: "M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" }) }) : /* @__PURE__ */ jsx9("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx9("path", { d: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" }) })
3988
+ children: state.isFullscreen ? /* @__PURE__ */ jsx11("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx11("path", { d: "M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" }) }) : /* @__PURE__ */ jsx11("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx11("path", { d: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" }) })
3142
3989
  }
3143
3990
  )
3144
3991
  ] });
3145
3992
  }
3146
3993
 
3147
3994
  // src/DocPlayerWithSidebar.tsx
3148
- import { useRef as useRef6, useState as useState6, useCallback as useCallback5, useEffect as useEffect6 } from "react";
3149
- import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
3995
+ import { useRef as useRef8, useState as useState7, useCallback as useCallback6, useEffect as useEffect8 } from "react";
3996
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
3150
3997
  var DEFAULT_STATE = {
3151
3998
  isPlaying: false,
3152
3999
  currentTime: 0,
@@ -3178,11 +4025,11 @@ function DocPlayerWithSidebar({
3178
4025
  onPlayingChange,
3179
4026
  theme
3180
4027
  }) {
3181
- const stateRef = useRef6(DEFAULT_STATE);
3182
- const actionsRef = useRef6(null);
3183
- const wasPlayingRef = useRef6(false);
3184
- const [, setTick] = useState6(0);
3185
- const handleStateChange = useCallback5(
4028
+ const stateRef = useRef8(DEFAULT_STATE);
4029
+ const actionsRef = useRef8(null);
4030
+ const wasPlayingRef = useRef8(false);
4031
+ const [, setTick] = useState7(0);
4032
+ const handleStateChange = useCallback6(
3186
4033
  (state) => {
3187
4034
  stateRef.current = state;
3188
4035
  if (onPlayingChange && state.isPlaying !== wasPlayingRef.current) {
@@ -3193,7 +4040,7 @@ function DocPlayerWithSidebar({
3193
4040
  },
3194
4041
  [onPlayingChange]
3195
4042
  );
3196
- const handleControlsReady = useCallback5(
4043
+ const handleControlsReady = useCallback6(
3197
4044
  (controls) => {
3198
4045
  const isFirst = !actionsRef.current;
3199
4046
  actionsRef.current = controls;
@@ -3201,15 +4048,15 @@ function DocPlayerWithSidebar({
3201
4048
  },
3202
4049
  []
3203
4050
  );
3204
- useEffect6(() => {
4051
+ useEffect8(() => {
3205
4052
  const interval = setInterval(() => {
3206
4053
  if (!stateRef.current.isPlaying) return;
3207
4054
  setTick((t) => t + 1);
3208
4055
  }, 250);
3209
4056
  return () => clearInterval(interval);
3210
4057
  }, []);
3211
- return /* @__PURE__ */ jsxs7("div", { className: "doc-player-sidebar-layout", children: [
3212
- /* @__PURE__ */ jsx10("div", { className: "doc-player-sidebar-layout__video", children: /* @__PURE__ */ jsx10(
4058
+ return /* @__PURE__ */ jsxs9("div", { className: "doc-player-sidebar-layout", children: [
4059
+ /* @__PURE__ */ jsx12("div", { className: "doc-player-sidebar-layout__video", children: /* @__PURE__ */ jsx12(
3213
4060
  DocPlayer,
3214
4061
  {
3215
4062
  doc,
@@ -3232,7 +4079,7 @@ function DocPlayerWithSidebar({
3232
4079
  forceViewport
3233
4080
  }
3234
4081
  ) }),
3235
- actionsRef.current && /* @__PURE__ */ jsx10(DocControlsSidebar, { state: stateRef.current, actions: actionsRef.current })
4082
+ actionsRef.current && /* @__PURE__ */ jsx12(DocControlsSidebar, { state: stateRef.current, actions: actionsRef.current })
3236
4083
  ] });
3237
4084
  }
3238
4085
 
@@ -3245,6 +4092,9 @@ export {
3245
4092
  DocProgressBar,
3246
4093
  DocControlsOverlay,
3247
4094
  DocControlsSlideshow,
4095
+ DashboardView,
4096
+ FlashcardFaceView,
4097
+ FlashcardView,
3248
4098
  resolveDocPlayerAppearance,
3249
4099
  DocPlayer,
3250
4100
  DocControlsBottom,