@drawnagency/primitives 0.1.111 → 0.1.113

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.
Files changed (203) hide show
  1. package/dist/auth/types.d.ts +1 -0
  2. package/dist/auth/types.d.ts.map +1 -1
  3. package/dist/chunk-4I5OO5UB.js +785 -0
  4. package/dist/{chunk-7WQJ57XG.js → chunk-6BZ2KNTP.js} +68 -260
  5. package/dist/{chunk-UVIYI4J6.js → chunk-AHALSCQH.js} +19 -3
  6. package/dist/{chunk-7MI3HCOF.js → chunk-BDFTMGCL.js} +10 -6
  7. package/dist/chunk-CXZ5YJG4.js +85 -0
  8. package/dist/{chunk-RA5IOFPY.js → chunk-DNY72JZN.js} +16 -38
  9. package/dist/{chunk-3GAG6ZXB.js → chunk-DS4GCYV2.js} +8 -4
  10. package/dist/{chunk-XQMDWXD6.js → chunk-FRE2CCLV.js} +3 -2
  11. package/dist/chunk-FV7JQRCN.js +94 -0
  12. package/dist/chunk-IIDKW7AV.js +41 -0
  13. package/dist/chunk-IINIZBQB.js +204 -0
  14. package/dist/{chunk-ELQ4C677.js → chunk-JH762VUH.js} +9 -44
  15. package/dist/{chunk-OUYB2GCW.js → chunk-K7DYZXVU.js} +1 -57
  16. package/dist/{chunk-NXGIHSL5.js → chunk-KR2MHTNJ.js} +11 -91
  17. package/dist/chunk-MM7KZS5C.js +30 -0
  18. package/dist/{chunk-OTDA6KWL.js → chunk-NHN2LSIU.js} +1 -1
  19. package/dist/chunk-QQ3S3P5P.js +38 -0
  20. package/dist/chunk-VAYZJKVR.js +209 -0
  21. package/dist/{chunk-VCCNKTFD.js → chunk-VWWY5TL5.js} +3 -31
  22. package/dist/chunk-WUJ3XNFI.js +62 -0
  23. package/dist/{chunk-ALMEC3XN.js → chunk-Z7W3DQTR.js} +22 -18
  24. package/dist/collab/rebase.js +1 -1
  25. package/dist/components/chat/ChatComposer.d.ts +25 -0
  26. package/dist/components/chat/ChatComposer.d.ts.map +1 -0
  27. package/dist/components/chat/ChatPanel.d.ts +5 -4
  28. package/dist/components/chat/ChatPanel.d.ts.map +1 -1
  29. package/dist/components/chat/ChatWidget.d.ts.map +1 -1
  30. package/dist/components/chat/markdown.d.ts +14 -0
  31. package/dist/components/chat/markdown.d.ts.map +1 -0
  32. package/dist/components/editor/index.js +16 -11
  33. package/dist/components/primitives/EditablePlainText.d.ts.map +1 -1
  34. package/dist/components/primitives/index.js +15 -10
  35. package/dist/components/sections/brand-guide.js +11 -9
  36. package/dist/components/sections/register-schemas.js +19 -14
  37. package/dist/components/sections/register.js +19 -14
  38. package/dist/components/shared/Navigation.d.ts +1 -0
  39. package/dist/components/shared/Navigation.d.ts.map +1 -1
  40. package/dist/components/shell/AudienceChips.d.ts +10 -0
  41. package/dist/components/shell/AudienceChips.d.ts.map +1 -0
  42. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  43. package/dist/components/shell/SiteSettingsChatbot.d.ts.map +1 -1
  44. package/dist/components/shell/SiteSettingsDecks.d.ts +13 -0
  45. package/dist/components/shell/SiteSettingsDecks.d.ts.map +1 -0
  46. package/dist/components/shell/SiteSettingsDisplay.d.ts.map +1 -1
  47. package/dist/components/shell/SiteSettingsModal.d.ts.map +1 -1
  48. package/dist/components/shell/SiteSettingsUsers.d.ts.map +1 -1
  49. package/dist/deck/DeckComponentView.d.ts +13 -0
  50. package/dist/deck/DeckComponentView.d.ts.map +1 -0
  51. package/dist/deck/DeckPlayer.d.ts +26 -0
  52. package/dist/deck/DeckPlayer.d.ts.map +1 -0
  53. package/dist/deck/DeckSlideView.d.ts +17 -0
  54. package/dist/deck/DeckSlideView.d.ts.map +1 -0
  55. package/dist/deck/DeckStage.d.ts +16 -0
  56. package/dist/deck/DeckStage.d.ts.map +1 -0
  57. package/dist/deck/components/DeckImage.d.ts +14 -0
  58. package/dist/deck/components/DeckImage.d.ts.map +1 -0
  59. package/dist/deck/components/DeckImageGrid.d.ts +27 -0
  60. package/dist/deck/components/DeckImageGrid.d.ts.map +1 -0
  61. package/dist/deck/components/DeckRichText.d.ts +22 -0
  62. package/dist/deck/components/DeckRichText.d.ts.map +1 -0
  63. package/dist/deck/components/DeckVideo.d.ts +15 -0
  64. package/dist/deck/components/DeckVideo.d.ts.map +1 -0
  65. package/dist/deck/editing.d.ts +50 -0
  66. package/dist/deck/editing.d.ts.map +1 -0
  67. package/dist/deck/flip.d.ts +11 -0
  68. package/dist/deck/flip.d.ts.map +1 -0
  69. package/dist/deck/index.d.ts +24 -0
  70. package/dist/deck/index.d.ts.map +1 -0
  71. package/dist/deck/index.js +53 -0
  72. package/dist/deck/layouts/CenteredTextLayout.d.ts +12 -0
  73. package/dist/deck/layouts/CenteredTextLayout.d.ts.map +1 -0
  74. package/dist/deck/layouts/ColumnSplitLayout.d.ts +14 -0
  75. package/dist/deck/layouts/ColumnSplitLayout.d.ts.map +1 -0
  76. package/dist/deck/layouts/FullBleedImageLayout.d.ts +14 -0
  77. package/dist/deck/layouts/FullBleedImageLayout.d.ts.map +1 -0
  78. package/dist/deck/media.d.ts +33 -0
  79. package/dist/deck/media.d.ts.map +1 -0
  80. package/dist/deck/tokens.d.ts +22 -0
  81. package/dist/deck/tokens.d.ts.map +1 -0
  82. package/dist/deck/useStageScale.d.ts +10 -0
  83. package/dist/deck/useStageScale.d.ts.map +1 -0
  84. package/dist/deck-builder/DeckBuilderApp.d.ts +30 -0
  85. package/dist/deck-builder/DeckBuilderApp.d.ts.map +1 -0
  86. package/dist/deck-builder/DeckBuilderTopBar.d.ts +24 -0
  87. package/dist/deck-builder/DeckBuilderTopBar.d.ts.map +1 -0
  88. package/dist/deck-builder/DeckChatPanel.d.ts +45 -0
  89. package/dist/deck-builder/DeckChatPanel.d.ts.map +1 -0
  90. package/dist/deck-builder/DeckEditor.d.ts +37 -0
  91. package/dist/deck-builder/DeckEditor.d.ts.map +1 -0
  92. package/dist/deck-builder/DeckLibrary.d.ts +22 -0
  93. package/dist/deck-builder/DeckLibrary.d.ts.map +1 -0
  94. package/dist/deck-builder/DeckSlideSurface.d.ts +31 -0
  95. package/dist/deck-builder/DeckSlideSurface.d.ts.map +1 -0
  96. package/dist/deck-builder/deck-api.d.ts +76 -0
  97. package/dist/deck-builder/deck-api.d.ts.map +1 -0
  98. package/dist/deck-builder/deck-chat-client.d.ts +44 -0
  99. package/dist/deck-builder/deck-chat-client.d.ts.map +1 -0
  100. package/dist/deck-builder/deck-chat-history.d.ts +10 -0
  101. package/dist/deck-builder/deck-chat-history.d.ts.map +1 -0
  102. package/dist/deck-builder/index.d.ts +13 -0
  103. package/dist/deck-builder/index.d.ts.map +1 -0
  104. package/dist/deck-builder/index.js +1505 -0
  105. package/dist/deck-builder/starter-deck.d.ts +23 -0
  106. package/dist/deck-builder/starter-deck.d.ts.map +1 -0
  107. package/dist/deck-builder/types.d.ts +29 -0
  108. package/dist/deck-builder/types.d.ts.map +1 -0
  109. package/dist/deck-builder/useSortableSlide.d.ts +29 -0
  110. package/dist/deck-builder/useSortableSlide.d.ts.map +1 -0
  111. package/dist/index.js +70 -63
  112. package/dist/lib/deck-access.d.ts +86 -0
  113. package/dist/lib/deck-access.d.ts.map +1 -0
  114. package/dist/lib/deck-access.js +58 -0
  115. package/dist/lib/deck-dexie.d.ts +88 -0
  116. package/dist/lib/deck-dexie.d.ts.map +1 -0
  117. package/dist/lib/deck-dexie.js +22 -0
  118. package/dist/lib/deck-edit.d.ts +67 -0
  119. package/dist/lib/deck-edit.d.ts.map +1 -0
  120. package/dist/lib/deck-media-assets.d.ts +27 -0
  121. package/dist/lib/deck-media-assets.d.ts.map +1 -0
  122. package/dist/lib/deck-media-assets.js +25 -0
  123. package/dist/lib/deck-media-refs.d.ts +35 -0
  124. package/dist/lib/deck-media-refs.d.ts.map +1 -0
  125. package/dist/lib/deck-media-refs.js +36 -0
  126. package/dist/lib/deck-tool.d.ts +25 -0
  127. package/dist/lib/deck-tool.d.ts.map +1 -0
  128. package/dist/lib/deck-tool.js +58 -0
  129. package/dist/lib/index-ops.js +2 -2
  130. package/dist/lib/index.js +17 -12
  131. package/dist/lib/nav.d.ts.map +1 -1
  132. package/dist/media/index.js +3 -3
  133. package/dist/media/resolve.d.ts +2 -0
  134. package/dist/media/resolve.d.ts.map +1 -1
  135. package/dist/schemas/deck-meta.d.ts +85 -0
  136. package/dist/schemas/deck-meta.d.ts.map +1 -0
  137. package/dist/schemas/deck-meta.js +64 -0
  138. package/dist/schemas/deck.d.ts +954 -0
  139. package/dist/schemas/deck.d.ts.map +1 -0
  140. package/dist/schemas/deck.js +65 -0
  141. package/dist/schemas/index.js +39 -37
  142. package/dist/schemas/site-config.d.ts +14 -1
  143. package/dist/schemas/site-config.d.ts.map +1 -1
  144. package/dist/types/database.d.ts +8 -0
  145. package/dist/types/database.d.ts.map +1 -1
  146. package/package.json +37 -1
  147. package/src/auth/types.ts +1 -0
  148. package/src/components/chat/ChatComposer.tsx +100 -0
  149. package/src/components/chat/ChatPanel.tsx +20 -69
  150. package/src/components/chat/ChatWidget.tsx +5 -4
  151. package/src/components/chat/markdown.ts +32 -0
  152. package/src/components/primitives/EditablePlainText.tsx +11 -1
  153. package/src/components/shared/Navigation.tsx +26 -18
  154. package/src/components/shell/AudienceChips.tsx +30 -0
  155. package/src/components/shell/EditorShell.tsx +12 -0
  156. package/src/components/shell/SiteSettingsChatbot.tsx +1 -18
  157. package/src/components/shell/SiteSettingsDecks.tsx +41 -0
  158. package/src/components/shell/SiteSettingsDisplay.tsx +10 -1
  159. package/src/components/shell/SiteSettingsModal.tsx +11 -0
  160. package/src/components/shell/SiteSettingsUsers.tsx +49 -2
  161. package/src/deck/DeckComponentView.tsx +28 -0
  162. package/src/deck/DeckPlayer.tsx +166 -0
  163. package/src/deck/DeckSlideView.tsx +38 -0
  164. package/src/deck/DeckStage.tsx +39 -0
  165. package/src/deck/components/DeckImage.tsx +38 -0
  166. package/src/deck/components/DeckImageGrid.tsx +254 -0
  167. package/src/deck/components/DeckRichText.tsx +111 -0
  168. package/src/deck/components/DeckVideo.tsx +81 -0
  169. package/src/deck/editing.tsx +201 -0
  170. package/src/deck/flip.ts +70 -0
  171. package/src/deck/index.ts +30 -0
  172. package/src/deck/layouts/CenteredTextLayout.tsx +29 -0
  173. package/src/deck/layouts/ColumnSplitLayout.tsx +69 -0
  174. package/src/deck/layouts/FullBleedImageLayout.tsx +58 -0
  175. package/src/deck/media.tsx +46 -0
  176. package/src/deck/tokens.ts +57 -0
  177. package/src/deck/useStageScale.ts +44 -0
  178. package/src/deck-builder/DeckBuilderApp.tsx +572 -0
  179. package/src/deck-builder/DeckBuilderTopBar.tsx +62 -0
  180. package/src/deck-builder/DeckChatPanel.tsx +334 -0
  181. package/src/deck-builder/DeckEditor.tsx +258 -0
  182. package/src/deck-builder/DeckLibrary.tsx +195 -0
  183. package/src/deck-builder/DeckSlideSurface.tsx +55 -0
  184. package/src/deck-builder/deck-api.ts +177 -0
  185. package/src/deck-builder/deck-chat-client.ts +129 -0
  186. package/src/deck-builder/deck-chat-history.ts +62 -0
  187. package/src/deck-builder/index.ts +12 -0
  188. package/src/deck-builder/starter-deck.ts +86 -0
  189. package/src/deck-builder/types.ts +24 -0
  190. package/src/deck-builder/useSortableSlide.ts +102 -0
  191. package/src/lib/deck-access.ts +132 -0
  192. package/src/lib/deck-dexie.ts +190 -0
  193. package/src/lib/deck-edit.ts +196 -0
  194. package/src/lib/deck-media-assets.ts +57 -0
  195. package/src/lib/deck-media-refs.ts +67 -0
  196. package/src/lib/deck-tool.ts +95 -0
  197. package/src/lib/nav.ts +21 -1
  198. package/src/media/resolve.ts +3 -1
  199. package/src/schemas/deck-meta.ts +88 -0
  200. package/src/schemas/deck.ts +478 -0
  201. package/src/schemas/index.ts +4 -0
  202. package/src/schemas/site-config.ts +27 -1
  203. package/src/types/database.ts +4 -0
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  FormLabel,
3
3
  Input
4
- } from "./chunk-ELQ4C677.js";
4
+ } from "./chunk-JH762VUH.js";
5
5
  import {
6
6
  cn
7
7
  } from "./chunk-7EOSDFS3.js";
@@ -130,63 +130,7 @@ function LinkField({ label, value, onChange }) {
130
130
  ] });
131
131
  }
132
132
 
133
- // src/components/shared/Button.tsx
134
- import { forwardRef } from "react";
135
- import { jsx as jsx3 } from "react/jsx-runtime";
136
- var boxSize = {
137
- sm: "rounded px-3 py-1.5 text-xs font-medium",
138
- md: "rounded px-4 py-2 text-sm font-medium"
139
- };
140
- var ghostSize = {
141
- sm: "text-xs font-medium",
142
- md: "text-sm font-medium"
143
- };
144
- var variantClasses = {
145
- primary: "bg-base-contrast text-base-accent hover:bg-base-contrast/90",
146
- brand: "bg-primary text-primary-contrast hover:opacity-90",
147
- secondary: "border border-base-200 text-base-contrast hover:bg-base-accent",
148
- destructive: "border border-red-600 text-red-600 hover:bg-red-600 hover:text-white"
149
- };
150
- var ghostToneClasses = {
151
- neutral: "text-base-contrast-light hover:text-base-contrast",
152
- destructive: "text-red-600 hover:text-red-800"
153
- };
154
- var Button = forwardRef(function Button2({
155
- variant = "primary",
156
- size = "sm",
157
- tone = "neutral",
158
- fullWidth = false,
159
- isLoading = false,
160
- loadingLabel,
161
- disabled,
162
- className,
163
- children,
164
- ...rest
165
- }, ref) {
166
- const isGhost = variant === "ghost";
167
- const resolvedDisabled = disabled || isLoading;
168
- const label = isLoading && loadingLabel ? loadingLabel : children;
169
- return /* @__PURE__ */ jsx3(
170
- "button",
171
- {
172
- ref,
173
- disabled: resolvedDisabled,
174
- className: cn(
175
- "inline-flex items-center justify-center transition-colors",
176
- isGhost ? ghostSize[size] : boxSize[size],
177
- isGhost ? ghostToneClasses[tone] : variantClasses[variant],
178
- fullWidth && "w-full",
179
- resolvedDisabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
180
- className
181
- ),
182
- ...rest,
183
- children: label
184
- }
185
- );
186
- });
187
-
188
133
  export {
189
- Button,
190
134
  PagesContext,
191
135
  usePages,
192
136
  Select,
@@ -7,23 +7,18 @@ import {
7
7
  MediaBlock,
8
8
  containerGridClass,
9
9
  useEditableCollection
10
- } from "./chunk-3GAG6ZXB.js";
10
+ } from "./chunk-DS4GCYV2.js";
11
11
  import {
12
12
  AddIcon,
13
13
  DeleteIcon,
14
14
  DragHandle,
15
15
  FormLabel,
16
- IconButton,
17
16
  Input,
18
17
  useMediaLibrary
19
- } from "./chunk-ELQ4C677.js";
18
+ } from "./chunk-JH762VUH.js";
20
19
  import {
21
- DEFAULT_LINK,
22
- LinkValueSchema
23
- } from "./chunk-3ZRQ4G5F.js";
24
- import {
25
- DOCUMENT_MIME_TO_EXT
26
- } from "./chunk-BS5AQHZG.js";
20
+ IconButton
21
+ } from "./chunk-IIDKW7AV.js";
27
22
  import {
28
23
  curatedIcons,
29
24
  getIcon
@@ -41,6 +36,13 @@ import {
41
36
  HexColorSchema,
42
37
  SingleMediaReferenceSchema
43
38
  } from "./chunk-GGXZZ77Z.js";
39
+ import {
40
+ DEFAULT_LINK,
41
+ LinkValueSchema
42
+ } from "./chunk-3ZRQ4G5F.js";
43
+ import {
44
+ DOCUMENT_MIME_TO_EXT
45
+ } from "./chunk-BS5AQHZG.js";
44
46
 
45
47
  // src/components/sections/Colors/index.tsx
46
48
  import { z as z2 } from "zod";
@@ -1407,82 +1409,6 @@ function DocumentDropZone({ onDocumentSelected, className }) {
1407
1409
  );
1408
1410
  }
1409
1411
 
1410
- // src/lib/insert-menu.ts
1411
- var PRESET_PREFIX = "__preset:";
1412
- var INSERT_MENU_ORDER = [
1413
- "container",
1414
- "link_heading",
1415
- "sub_heading",
1416
- "sub_sub_heading",
1417
- "prose",
1418
- "media",
1419
- "video",
1420
- "button",
1421
- "colors",
1422
- "document",
1423
- "icon_list",
1424
- `${PRESET_PREFIX}do_dont_list`,
1425
- "dodont_media"
1426
- ];
1427
-
1428
- // src/lib/container-ops.ts
1429
- function getContainerChildren(block) {
1430
- const content = block.content;
1431
- return Array.isArray(content?.children) ? content.children : [];
1432
- }
1433
- function withChildren(container, children) {
1434
- return { ...container, content: { ...container.content, children } };
1435
- }
1436
- function containerColumns(container) {
1437
- const n = container.content?.columns;
1438
- return typeof n === "number" && n >= 1 ? n : 1;
1439
- }
1440
- function removeChildAt(container, index) {
1441
- const children = getContainerChildren(container);
1442
- if (index < 0 || index >= children.length) return { container, removed: null };
1443
- const next = [...children];
1444
- const [removed] = next.splice(index, 1);
1445
- return { container: withChildren(container, next), removed };
1446
- }
1447
- function duplicateChildAt(container, index, newId) {
1448
- const children = getContainerChildren(container);
1449
- if (index < 0 || index >= children.length) return container;
1450
- const clone = { ...JSON.parse(JSON.stringify(children[index])), id: newId() };
1451
- const next = [...children];
1452
- next.splice(index + 1, 0, clone);
1453
- return withChildren(container, next);
1454
- }
1455
- function setChildColSpan(container, index, span) {
1456
- const children = getContainerChildren(container);
1457
- if (index < 0 || index >= children.length) return container;
1458
- const clamped = Math.max(1, Math.min(span, containerColumns(container)));
1459
- const next = children.map((child, i) => {
1460
- if (i !== index) return child;
1461
- if (clamped <= 1) {
1462
- const { layout: _drop, ...rest } = child;
1463
- return rest;
1464
- }
1465
- return { ...child, layout: { ...child.layout, colSpan: clamped } };
1466
- });
1467
- return withChildren(container, next);
1468
- }
1469
- function effectiveSpan(child, columns) {
1470
- return isSpacer(child) ? 1 : Math.min(child.layout?.colSpan ?? 1, columns);
1471
- }
1472
- function isSpacer(child) {
1473
- return child.type === "spacer";
1474
- }
1475
- function occupiedColumns(children, columns) {
1476
- return children.reduce((sum, c) => sum + effectiveSpan(c, columns), 0);
1477
- }
1478
- function trimTrailingSpacers(container) {
1479
- const children = getContainerChildren(container);
1480
- let end = children.length;
1481
- while (end > 0 && isSpacer(children[end - 1])) end--;
1482
- if (end === children.length) return container;
1483
- return withChildren(container, children.slice(0, end));
1484
- }
1485
-
1486
1412
  // src/lib/documents.ts
1487
1413
  function formatDocSize(bytes) {
1488
1414
  if (!Number.isFinite(bytes) || bytes < 0) return "";
@@ -1687,12 +1613,6 @@ export {
1687
1613
  stripHtmlToPlainText,
1688
1614
  truncate,
1689
1615
  pluralize,
1690
- removeChildAt,
1691
- duplicateChildAt,
1692
- setChildColSpan,
1693
- isSpacer,
1694
- occupiedColumns,
1695
- trimTrailingSpacers,
1696
1616
  ColorSpaceSchema,
1697
1617
  ColorItemSchema,
1698
1618
  brandGuideSectionDefs
@@ -0,0 +1,30 @@
1
+ // src/lib/timestamp.ts
2
+ function formatTimestamp(iso) {
3
+ const date = new Date(iso);
4
+ const now = /* @__PURE__ */ new Date();
5
+ const diffMs = now.getTime() - date.getTime();
6
+ const diffMinutes = Math.floor(diffMs / 6e4);
7
+ const diffHours = Math.floor(diffMs / 36e5);
8
+ const sameDay = date.getUTCFullYear() === now.getUTCFullYear() && date.getUTCMonth() === now.getUTCMonth() && date.getUTCDate() === now.getUTCDate();
9
+ if (diffMinutes < 1) return "just now";
10
+ if (diffMinutes < 60) {
11
+ return `${diffMinutes} ${diffMinutes === 1 ? "minute" : "minutes"} ago`;
12
+ }
13
+ if (sameDay) {
14
+ return `${diffHours} ${diffHours === 1 ? "hour" : "hours"} ago`;
15
+ }
16
+ return date.toLocaleDateString("en-US", {
17
+ month: "short",
18
+ day: "numeric",
19
+ timeZone: "UTC"
20
+ }) + " at " + date.toLocaleTimeString("en-US", {
21
+ hour: "numeric",
22
+ minute: "2-digit",
23
+ hour12: true,
24
+ timeZone: "UTC"
25
+ });
26
+ }
27
+
28
+ export {
29
+ formatTimestamp
30
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  RESERVED_SLUGS
3
- } from "./chunk-UVIYI4J6.js";
3
+ } from "./chunk-AHALSCQH.js";
4
4
 
5
5
  // src/lib/nav-tree.ts
6
6
  function reconcileNav(entries, pages) {
@@ -0,0 +1,38 @@
1
+ // src/lib/sanitize.ts
2
+ var purifierPromise = null;
3
+ var purifier = null;
4
+ if (typeof window !== "undefined") {
5
+ purifierPromise = import("dompurify").then((mod) => {
6
+ const DOMPurify = mod.default ?? mod;
7
+ purifier = (html) => DOMPurify.sanitize(html);
8
+ return mod;
9
+ });
10
+ }
11
+ var sanitizeCache = /* @__PURE__ */ new Map();
12
+ var SANITIZE_CACHE_LIMIT = 500;
13
+ function sanitizeHtml(html) {
14
+ if (!html) return "";
15
+ if (!purifier) return html;
16
+ const cached = sanitizeCache.get(html);
17
+ if (cached !== void 0) return cached;
18
+ const clean = purifier(html);
19
+ if (sanitizeCache.size >= SANITIZE_CACHE_LIMIT) {
20
+ const oldest = sanitizeCache.keys().next().value;
21
+ if (oldest !== void 0) sanitizeCache.delete(oldest);
22
+ }
23
+ sanitizeCache.set(html, clean);
24
+ return clean;
25
+ }
26
+ function isSanitizerReady() {
27
+ return typeof window === "undefined" || purifier !== null;
28
+ }
29
+ async function ensureSanitizer() {
30
+ if (typeof window === "undefined") return;
31
+ if (!purifier && purifierPromise) await purifierPromise;
32
+ }
33
+
34
+ export {
35
+ sanitizeHtml,
36
+ isSanitizerReady,
37
+ ensureSanitizer
38
+ };
@@ -0,0 +1,209 @@
1
+ // src/schemas/deck.ts
2
+ import { z } from "zod";
3
+ var DECK_STAGE_WIDTH = 1600;
4
+ var DECK_STAGE_HEIGHT = 900;
5
+ var DECK_SCHEMA_VERSION = 1;
6
+ var DECK_MAX_SLIDES = 40;
7
+ var DeckIdSchema = z.string().max(64, "must be 64 characters or fewer \u2014 a deck id is also a directory name").regex(/^[a-z0-9][a-z0-9-]*$/, "must be lowercase letters, digits and hyphens, starting with a letter or digit");
8
+ var DECK_LAYOUT_TYPES = ["centeredText", "fullBleedImage", "columnSplit"];
9
+ var DECK_COMPONENT_TYPES = ["richText", "image", "imageGrid", "video"];
10
+ var DECK_MEDIA_KINDS = ["portal"];
11
+ var DECK_SURFACES = ["base", "brand"];
12
+ var DECK_OVERLAY_PLACEMENTS = ["sideLeft", "sideRight", "bottomBand"];
13
+ var DECK_SPLIT_RATIOS = ["1-2", "2-1"];
14
+ function vocabularyError(kind, types, field = "type") {
15
+ return `unknown ${kind} ${field} \u2014 the deck runtime provides: ${types.map((t) => `"${t}"`).join(", ")}`;
16
+ }
17
+ var PortalMediaRefSchema = z.object({
18
+ kind: z.literal("portal"),
19
+ // Same charset as a deck id, for the same reason and one more: a media id
20
+ // becomes a key into a plain object at resolve time, and `"__proto__"` parses
21
+ // as a perfectly good string. Constrain it here rather than relying on every
22
+ // resolver to be careful.
23
+ id: z.string().max(120, "must be 120 characters or fewer").regex(/^[a-z0-9][a-z0-9-]*$/, "must be lowercase letters, digits and hyphens, starting with a letter or digit").describe("Media id from the portal's media library \u2014 the manifest key, not a filename, path or URL.")
24
+ });
25
+ var DeckMediaRefSchema = z.discriminatedUnion("kind", [PortalMediaRefSchema], {
26
+ error: vocabularyError("media reference", DECK_MEDIA_KINDS, "kind")
27
+ });
28
+ var PARAGRAPH_MAX = 400;
29
+ var BULLET_MAX = 160;
30
+ var MAX_SPANS = 12;
31
+ var DeckTextSpanSchema = z.strictObject({
32
+ text: z.string().min(1).max(PARAGRAPH_MAX).describe("The span's literal text. Never HTML or markdown \u2014 the marks below are the only formatting there is."),
33
+ bold: z.boolean().optional().describe("Renders the span at full ink weight. For the one phrase in a line that carries it."),
34
+ italic: z.boolean().optional().describe("Renders the span italic. For titles of works and genuine emphasis, not decoration.")
35
+ });
36
+ function inlineText(maxChars) {
37
+ return z.union([z.string().min(1).max(maxChars), z.array(DeckTextSpanSchema).min(1).max(MAX_SPANS)], {
38
+ error: "must be plain text, or an array of { text, bold?, italic? } spans \u2014 deck rich text is structured, never HTML or markdown"
39
+ });
40
+ }
41
+ function inlineLength(value) {
42
+ return typeof value === "string" ? value.length : value.reduce((total, span) => total + span.text.length, 0);
43
+ }
44
+ var DeckRichTextSchema = z.object({
45
+ type: z.literal("richText"),
46
+ kicker: z.string().min(1).max(60).optional().describe("Short label above the heading, e.g. a campaign or section name. Rendered small, tracked and in the brand accent."),
47
+ heading: z.string().min(1).max(120).optional().describe("The slide's primary line. Keep it to a phrase \u2014 it renders at display size on the 16:9 stage."),
48
+ subheading: z.string().min(1).max(200).optional().describe("Secondary line under the heading. Optional; omit rather than repeating the heading."),
49
+ paragraphs: z.array(inlineText(PARAGRAPH_MAX)).max(3).optional().describe(
50
+ `Body copy, one entry per paragraph. Each is either a plain string or an array of { text, bold?, italic? } spans \u2014 use spans only where a phrase genuinely needs a mark. Never HTML or markdown; ${PARAGRAPH_MAX} characters per paragraph.`
51
+ ),
52
+ bullets: z.array(inlineText(BULLET_MAX)).max(6).optional().describe(
53
+ `Short bullet lines, plain strings or span arrays like paragraphs. Prefer six or fewer so they stay legible at presentation size; ${BULLET_MAX} characters per line.`
54
+ )
55
+ }).superRefine((value, ctx) => {
56
+ const hasContent = value.kicker !== void 0 || value.heading !== void 0 || value.subheading !== void 0 || (value.paragraphs?.length ?? 0) > 0 || (value.bullets?.length ?? 0) > 0;
57
+ if (!hasContent) {
58
+ ctx.addIssue({
59
+ code: "custom",
60
+ message: "a richText component needs at least one of kicker, heading, subheading, paragraphs or bullets"
61
+ });
62
+ }
63
+ const capLines = (lines, max, field) => {
64
+ lines?.forEach((line, i) => {
65
+ const length = inlineLength(line);
66
+ if (length > max) {
67
+ ctx.addIssue({
68
+ code: "custom",
69
+ path: [field, i],
70
+ message: `spans total ${length} characters \u2014 keep a ${field === "bullets" ? "bullet" : "paragraph"} to ${max} or fewer`
71
+ });
72
+ }
73
+ });
74
+ };
75
+ capLines(value.paragraphs, PARAGRAPH_MAX, "paragraphs");
76
+ capLines(value.bullets, BULLET_MAX, "bullets");
77
+ });
78
+ var DeckImageSchema = z.object({
79
+ type: z.literal("image"),
80
+ media: DeckMediaRefSchema.describe("The image to show. It fills its slot and is cropped to fit."),
81
+ caption: z.string().min(1).max(120).optional().describe("Caption printed on a solid brand block along the image's bottom edge. Omit rather than restating the slide.")
82
+ });
83
+ var DeckImageGridItemSchema = z.object({
84
+ media: DeckMediaRefSchema.describe("The tile's image."),
85
+ caption: z.string().min(1).max(80).optional().describe("Shown on the tile on hover, and while the tile is expanded. A few words, not a sentence.")
86
+ });
87
+ var DeckImageGridSchema = z.object({
88
+ type: z.literal("imageGrid"),
89
+ items: z.array(DeckImageGridItemSchema).min(2, "an image grid needs at least two images \u2014 use the image component for one").max(12, "an image grid holds at most twelve images \u2014 past that a tile is too small to read at presentation size").describe(
90
+ "The tiles, in order of importance. Two to twelve. They pack into masonry columns at their own aspect ratios \u2014 nothing is cropped and nothing is resized to match its neighbours \u2014 and the columns clip at the bottom of the slot if the stack runs past it, so put the frames that must be seen first."
91
+ )
92
+ });
93
+ var DeckVideoSchema = z.object({
94
+ type: z.literal("video"),
95
+ media: DeckMediaRefSchema.describe("The video to play. Must reference portal-hosted media \u2014 a deck cannot embed an external player."),
96
+ title: z.string().min(1).max(120).optional().describe("Label on a brand block across the bottom of the poster, before playback starts.")
97
+ });
98
+ var DeckComponentSchema = z.discriminatedUnion(
99
+ "type",
100
+ [DeckRichTextSchema, DeckImageSchema, DeckImageGridSchema, DeckVideoSchema],
101
+ { error: vocabularyError("deck component", DECK_COMPONENT_TYPES) }
102
+ );
103
+ var CenteredTextLayoutSchema = z.object({
104
+ type: z.literal("centeredText"),
105
+ background: z.enum(DECK_SURFACES).default("base").describe("`base` puts the slide on the site's surface color; `brand` fills it with the brand primary and flips the ink."),
106
+ content: DeckComponentSchema.describe("The single Deck Component this Layout centers.")
107
+ });
108
+ var DeckOverlayPanelSchema = z.object({
109
+ placement: z.enum(DECK_OVERLAY_PLACEMENTS).default("sideLeft").describe(
110
+ "`sideLeft`/`sideRight` put the panel in a full-height column over that edge of the image; `bottomBand` runs it full width across the bottom. Pick the side the image is emptiest on."
111
+ ),
112
+ content: DeckRichTextSchema.describe("The panel's text. It sits on the brand color, so keep it short \u2014 a kicker and a line or two.")
113
+ });
114
+ var FullBleedImageLayoutSchema = z.object({
115
+ type: z.literal("fullBleedImage"),
116
+ media: DeckMediaRefSchema.describe("The image filling the whole 16:9 stage. It is cropped to fit, so choose one that survives a wide crop."),
117
+ overlay: DeckOverlayPanelSchema.optional().describe("Optional text panel over the image. Omit it for a slide that is purely the picture.")
118
+ });
119
+ var ColumnSplitLayoutSchema = z.object({
120
+ type: z.literal("columnSplit"),
121
+ ratio: z.enum(DECK_SPLIT_RATIOS).default("1-2").describe("`1-2` gives the left column a third of the stage and the right two thirds; `2-1` is the reverse. Give the media the larger side."),
122
+ left: DeckComponentSchema.describe("The left column's Deck Component."),
123
+ right: DeckComponentSchema.describe("The right column's Deck Component.")
124
+ });
125
+ var DeckLayoutSchema = z.discriminatedUnion(
126
+ "type",
127
+ [CenteredTextLayoutSchema, FullBleedImageLayoutSchema, ColumnSplitLayoutSchema],
128
+ { error: vocabularyError("layout", DECK_LAYOUT_TYPES) }
129
+ );
130
+ var DeckSlideSchema = z.object({
131
+ id: DeckIdSchema.describe("Stable slide id, unique within the deck. Reused across edits so revisions stay diffable."),
132
+ title: z.string().min(1).max(80).optional().describe("Short label for the slide, used by the builder filmstrip and by slide-navigation labels. Not rendered on the stage."),
133
+ layout: DeckLayoutSchema.describe("The full-slide arrangement. A slide must use exactly one Layout.")
134
+ });
135
+ var DeckSchema = z.object({
136
+ schemaVersion: z.literal(DECK_SCHEMA_VERSION).describe(`Deck schema version. Always ${DECK_SCHEMA_VERSION}.`),
137
+ id: DeckIdSchema.describe("Stable deck id \u2014 also the directory name on the decks branch."),
138
+ title: z.string().min(1).max(120).describe("The deck's name, shown in the builder and in the deck's chrome."),
139
+ slides: z.array(DeckSlideSchema).min(1, "a deck needs at least one slide").max(
140
+ DECK_MAX_SLIDES,
141
+ `a deck is capped at ${DECK_MAX_SLIDES} slides \u2014 split a longer story into several decks`
142
+ ).describe(
143
+ `Slides in presentation order, at most ${DECK_MAX_SLIDES}. The whole deck is re-emitted on every authoring turn and re-committed on every publish, so the cap bounds both the model's output and the size of a stored deck.`
144
+ )
145
+ }).superRefine((deck, ctx) => {
146
+ const seen = /* @__PURE__ */ new Set();
147
+ deck.slides.forEach((slide, i) => {
148
+ if (seen.has(slide.id)) {
149
+ ctx.addIssue({
150
+ code: "custom",
151
+ path: ["slides", i, "id"],
152
+ message: `duplicate slide id "${slide.id}" \u2014 slide ids must be unique within a deck`
153
+ });
154
+ }
155
+ seen.add(slide.id);
156
+ });
157
+ });
158
+ function formatPath(path) {
159
+ return path.reduce((acc, key) => {
160
+ if (typeof key === "number") return `${acc}[${key}]`;
161
+ return acc === "" ? String(key) : `${acc}.${String(key)}`;
162
+ }, "");
163
+ }
164
+ function formatDeckIssues(error) {
165
+ return error.issues.map((issue) => {
166
+ const path = formatPath(issue.path);
167
+ return path === "" ? issue.message : `${path}: ${issue.message}`;
168
+ });
169
+ }
170
+ function parseDeck(input) {
171
+ const result = DeckSchema.safeParse(input);
172
+ return result.success ? { ok: true, deck: result.data } : { ok: false, errors: formatDeckIssues(result.error) };
173
+ }
174
+ function serializeDeck(deck) {
175
+ return JSON.stringify(deck, null, 2) + "\n";
176
+ }
177
+
178
+ export {
179
+ DECK_STAGE_WIDTH,
180
+ DECK_STAGE_HEIGHT,
181
+ DECK_SCHEMA_VERSION,
182
+ DECK_MAX_SLIDES,
183
+ DeckIdSchema,
184
+ DECK_LAYOUT_TYPES,
185
+ DECK_COMPONENT_TYPES,
186
+ DECK_MEDIA_KINDS,
187
+ DECK_SURFACES,
188
+ DECK_OVERLAY_PLACEMENTS,
189
+ DECK_SPLIT_RATIOS,
190
+ PortalMediaRefSchema,
191
+ DeckMediaRefSchema,
192
+ DeckTextSpanSchema,
193
+ DeckRichTextSchema,
194
+ DeckImageSchema,
195
+ DeckImageGridItemSchema,
196
+ DeckImageGridSchema,
197
+ DeckVideoSchema,
198
+ DeckComponentSchema,
199
+ CenteredTextLayoutSchema,
200
+ DeckOverlayPanelSchema,
201
+ FullBleedImageLayoutSchema,
202
+ ColumnSplitLayoutSchema,
203
+ DeckLayoutSchema,
204
+ DeckSlideSchema,
205
+ DeckSchema,
206
+ formatDeckIssues,
207
+ parseDeck,
208
+ serializeDeck
209
+ };
@@ -10,12 +10,12 @@ import {
10
10
  import {
11
11
  getSectionSchemaForType
12
12
  } from "./chunk-DMCGN5AS.js";
13
+ import {
14
+ IndexSchema
15
+ } from "./chunk-AHALSCQH.js";
13
16
  import {
14
17
  getAllSchemas
15
18
  } from "./chunk-FTPETWXE.js";
16
- import {
17
- IndexSchema
18
- } from "./chunk-UVIYI4J6.js";
19
19
  import {
20
20
  MAX_BLOCK_DEPTH,
21
21
  blockDepth
@@ -115,33 +115,6 @@ function buildGoogleFontsUrl(headingFont, bodyFont) {
115
115
  return `https://fonts.googleapis.com/css2?${params}&display=swap`;
116
116
  }
117
117
 
118
- // src/lib/timestamp.ts
119
- function formatTimestamp(iso) {
120
- const date = new Date(iso);
121
- const now = /* @__PURE__ */ new Date();
122
- const diffMs = now.getTime() - date.getTime();
123
- const diffMinutes = Math.floor(diffMs / 6e4);
124
- const diffHours = Math.floor(diffMs / 36e5);
125
- const sameDay = date.getUTCFullYear() === now.getUTCFullYear() && date.getUTCMonth() === now.getUTCMonth() && date.getUTCDate() === now.getUTCDate();
126
- if (diffMinutes < 1) return "just now";
127
- if (diffMinutes < 60) {
128
- return `${diffMinutes} ${diffMinutes === 1 ? "minute" : "minutes"} ago`;
129
- }
130
- if (sameDay) {
131
- return `${diffHours} ${diffHours === 1 ? "hour" : "hours"} ago`;
132
- }
133
- return date.toLocaleDateString("en-US", {
134
- month: "short",
135
- day: "numeric",
136
- timeZone: "UTC"
137
- }) + " at " + date.toLocaleTimeString("en-US", {
138
- hour: "numeric",
139
- minute: "2-digit",
140
- hour12: true,
141
- timeZone: "UTC"
142
- });
143
- }
144
-
145
118
  // src/lib/safeRedirect.ts
146
119
  function safeRedirect(url, fallback) {
147
120
  if (!url) return fallback;
@@ -257,7 +230,6 @@ export {
257
230
  onPrimaryVarValue,
258
231
  gridColsClass,
259
232
  buildGoogleFontsUrl,
260
- formatTimestamp,
261
233
  safeRedirect,
262
234
  mergeSiteContent,
263
235
  loadStaticSiteContent,
@@ -0,0 +1,62 @@
1
+ import {
2
+ cn
3
+ } from "./chunk-7EOSDFS3.js";
4
+
5
+ // src/components/shared/Button.tsx
6
+ import { forwardRef } from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ var boxSize = {
9
+ sm: "rounded px-3 py-1.5 text-xs font-medium",
10
+ md: "rounded px-4 py-2 text-sm font-medium"
11
+ };
12
+ var ghostSize = {
13
+ sm: "text-xs font-medium",
14
+ md: "text-sm font-medium"
15
+ };
16
+ var variantClasses = {
17
+ primary: "bg-base-contrast text-base-accent hover:bg-base-contrast/90",
18
+ brand: "bg-primary text-primary-contrast hover:opacity-90",
19
+ secondary: "border border-base-200 text-base-contrast hover:bg-base-accent",
20
+ destructive: "border border-red-600 text-red-600 hover:bg-red-600 hover:text-white"
21
+ };
22
+ var ghostToneClasses = {
23
+ neutral: "text-base-contrast-light hover:text-base-contrast",
24
+ destructive: "text-red-600 hover:text-red-800"
25
+ };
26
+ var Button = forwardRef(function Button2({
27
+ variant = "primary",
28
+ size = "sm",
29
+ tone = "neutral",
30
+ fullWidth = false,
31
+ isLoading = false,
32
+ loadingLabel,
33
+ disabled,
34
+ className,
35
+ children,
36
+ ...rest
37
+ }, ref) {
38
+ const isGhost = variant === "ghost";
39
+ const resolvedDisabled = disabled || isLoading;
40
+ const label = isLoading && loadingLabel ? loadingLabel : children;
41
+ return /* @__PURE__ */ jsx(
42
+ "button",
43
+ {
44
+ ref,
45
+ disabled: resolvedDisabled,
46
+ className: cn(
47
+ "inline-flex items-center justify-center transition-colors",
48
+ isGhost ? ghostSize[size] : boxSize[size],
49
+ isGhost ? ghostToneClasses[tone] : variantClasses[variant],
50
+ fullWidth && "w-full",
51
+ resolvedDisabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
52
+ className
53
+ ),
54
+ ...rest,
55
+ children: label
56
+ }
57
+ );
58
+ });
59
+
60
+ export {
61
+ Button
62
+ };