@hraness/design-kit 0.31.1

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 (345) hide show
  1. package/ARTICLE_COPY.md +208 -0
  2. package/HERO_FIELDS.md +55 -0
  3. package/ICONS.md +56 -0
  4. package/LANTERN_MATERIAL.md +60 -0
  5. package/LICENSE +21 -0
  6. package/MARKETING_COPY.md +49 -0
  7. package/MARKETING_PRESET.md +108 -0
  8. package/PALETTES.md +90 -0
  9. package/PAPER_THEME.md +128 -0
  10. package/README.md +745 -0
  11. package/STATUS_PAGES.md +67 -0
  12. package/STYLE.md +245 -0
  13. package/WRITING.md +72 -0
  14. package/dist/browser/index.js +2345 -0
  15. package/dist/chunk-44djxk16.js +6872 -0
  16. package/dist/chunk-5gtx3pza.js +9 -0
  17. package/dist/chunk-77391vmq.js +454 -0
  18. package/dist/chunk-7b1586eb.js +1209 -0
  19. package/dist/chunk-8834fh4n.js +6 -0
  20. package/dist/chunk-9t8xyqte.js +870 -0
  21. package/dist/chunk-cejpzyfh.js +446 -0
  22. package/dist/chunk-eh71jz57.js +429 -0
  23. package/dist/chunk-evb02bc1.js +516 -0
  24. package/dist/chunk-h4k7yv6x.js +368 -0
  25. package/dist/chunk-he8eznb1.js +760 -0
  26. package/dist/chunk-mr1vdcjq.js +404 -0
  27. package/dist/chunk-wzvdn8ey.js +116 -0
  28. package/dist/fonts/nebula-sans/social-fonts.generated.js +53 -0
  29. package/dist/icons.js +48 -0
  30. package/dist/index.js +237 -0
  31. package/dist/launch.js +41 -0
  32. package/dist/mockups/client.js +422 -0
  33. package/dist/mockups/index.js +1850 -0
  34. package/dist/portfolio.js +1549 -0
  35. package/dist/provider-marks.js +19 -0
  36. package/dist/react/charts.js +11 -0
  37. package/dist/react/hero-backdrop.js +8 -0
  38. package/dist/react/index.js +7252 -0
  39. package/dist/react/platform-install.js +10 -0
  40. package/dist/react/server.js +137 -0
  41. package/dist/stylex-manifest.json +1 -0
  42. package/dist/stylex.css +5392 -0
  43. package/dist/syntax-highlighting.js +15 -0
  44. package/dist/testing.js +250 -0
  45. package/package.json +377 -0
  46. package/portfolio-inventory.json +31 -0
  47. package/scripts/check-lantern-material-snapshot.d.mts +26 -0
  48. package/scripts/check-lantern-material-snapshot.mjs +126 -0
  49. package/scripts/check-marketing-snapshot.d.mts +12 -0
  50. package/scripts/check-marketing-snapshot.mjs +58 -0
  51. package/scripts/lantern-material-snapshot.ts +101 -0
  52. package/scripts/marketing-textures.ts +48 -0
  53. package/scripts/paper-theme-snapshot.ts +97 -0
  54. package/scripts/product-marketing-snapshot.ts +149 -0
  55. package/src/appearance-menu.css +266 -0
  56. package/src/appearance.ts +32 -0
  57. package/src/article-html.ts +269 -0
  58. package/src/article.ts +686 -0
  59. package/src/browser/appearance-menu.ts +604 -0
  60. package/src/browser/artifact-share.ts +164 -0
  61. package/src/browser/design-palette.ts +197 -0
  62. package/src/browser/foil.ts +124 -0
  63. package/src/browser/hero-light.ts +11 -0
  64. package/src/browser/index.ts +29 -0
  65. package/src/browser/status-field.ts +432 -0
  66. package/src/browser/status-page.ts +100 -0
  67. package/src/browser/sticky-offset.ts +82 -0
  68. package/src/browser/theme-color-sync.ts +213 -0
  69. package/src/charts.css +7 -0
  70. package/src/compiler-components.css +34 -0
  71. package/src/compiler-foundation.css +18 -0
  72. package/src/compiler-palettes.css +7 -0
  73. package/src/compiler-tokens.css +342 -0
  74. package/src/components.css +3 -0
  75. package/src/design-gallery.css +431 -0
  76. package/src/effects.css +14 -0
  77. package/src/fonts/geist-mono/GeistMono[wght].woff2 +0 -0
  78. package/src/fonts/geist-mono/OFL.txt +93 -0
  79. package/src/fonts/geist-mono/PROVENANCE.md +12 -0
  80. package/src/fonts/instrument-serif/OFL.txt +93 -0
  81. package/src/fonts/instrument-serif/UPSTREAM.md +14 -0
  82. package/src/fonts/instrument-serif/instrument-serif-latin-400.woff2 +0 -0
  83. package/src/fonts/nebula-sans/LICENSE.txt +96 -0
  84. package/src/fonts/nebula-sans/NebulaSans-Black.woff2 +0 -0
  85. package/src/fonts/nebula-sans/NebulaSans-BlackItalic.woff2 +0 -0
  86. package/src/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
  87. package/src/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
  88. package/src/fonts/nebula-sans/NebulaSans-BoldItalic.woff2 +0 -0
  89. package/src/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
  90. package/src/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  91. package/src/fonts/nebula-sans/NebulaSans-BookItalic.woff2 +0 -0
  92. package/src/fonts/nebula-sans/NebulaSans-Light.woff2 +0 -0
  93. package/src/fonts/nebula-sans/NebulaSans-LightItalic.woff2 +0 -0
  94. package/src/fonts/nebula-sans/NebulaSans-Medium.woff2 +0 -0
  95. package/src/fonts/nebula-sans/NebulaSans-MediumItalic.woff2 +0 -0
  96. package/src/fonts/nebula-sans/NebulaSans-Semibold.woff2 +0 -0
  97. package/src/fonts/nebula-sans/NebulaSans-SemiboldItalic.woff2 +0 -0
  98. package/src/fonts/nebula-sans/PROVENANCE.md +14 -0
  99. package/src/fonts/nebula-sans/social-fonts.generated.ts +3873 -0
  100. package/src/fonts.css +106 -0
  101. package/src/icons/act60/act60.svg +5 -0
  102. package/src/icons/act60/application.svg +9 -0
  103. package/src/icons/act60/chapter-compare.svg +3 -0
  104. package/src/icons/act60/decree-residence.svg +6 -0
  105. package/src/icons/act60/estimate.svg +7 -0
  106. package/src/icons/act60/export-services.svg +4 -0
  107. package/src/icons/act60/investor.svg +13 -0
  108. package/src/icons/act60/presence.svg +7 -0
  109. package/src/icons/aicharts/aicharts.svg +6 -0
  110. package/src/icons/aicharts/cost-compare.svg +4 -0
  111. package/src/icons/aicharts/task-audio.svg +7 -0
  112. package/src/icons/aicharts/task-coding.svg +6 -0
  113. package/src/icons/aicharts/task-images.svg +3 -0
  114. package/src/icons/aicharts/task-reasoning.svg +9 -0
  115. package/src/icons/aicharts/task-research.svg +5 -0
  116. package/src/icons/aicharts/task-video.svg +4 -0
  117. package/src/icons/manifest.json +1371 -0
  118. package/src/icons/platonik/conversation.svg +3 -0
  119. package/src/icons/platonik/creature.svg +7 -0
  120. package/src/icons/platonik/economy.svg +4 -0
  121. package/src/icons/platonik/keep-light.svg +6 -0
  122. package/src/icons/platonik/platonik.svg +7 -0
  123. package/src/icons/platonik/spark-route.svg +15 -0
  124. package/src/icons/receipts/act60-retry.receipt.json +888 -0
  125. package/src/icons/receipts/act60.receipt.json +1366 -0
  126. package/src/icons/receipts/aicharts.receipt.json +1546 -0
  127. package/src/icons/receipts/platonik.receipt.json +1011 -0
  128. package/src/icons/receipts/roughday.receipt.json +770 -0
  129. package/src/icons/receipts/shared.receipt.json +1296 -0
  130. package/src/icons/receipts/slopcamera.receipt.json +1495 -0
  131. package/src/icons/receipts/soundfish-retry.receipt.json +460 -0
  132. package/src/icons/receipts/soundfish.receipt.json +1709 -0
  133. package/src/icons/receipts/sponge.receipt.json +1743 -0
  134. package/src/icons/receipts/stripe-history-retry.receipt.json +145 -0
  135. package/src/icons/receipts/stripe-history.receipt.json +1202 -0
  136. package/src/icons/receipts/wordcell.receipt.json +1924 -0
  137. package/src/icons/roughday/finance.svg +7 -0
  138. package/src/icons/roughday/roughday.svg +6 -0
  139. package/src/icons/roughday/technology.svg +4 -0
  140. package/src/icons/roughday/world.svg +4 -0
  141. package/src/icons/sets/act60.json +63 -0
  142. package/src/icons/sets/aicharts.json +63 -0
  143. package/src/icons/sets/platonik.json +39 -0
  144. package/src/icons/sets/roughday.json +25 -0
  145. package/src/icons/sets/shared.json +12 -0
  146. package/src/icons/sets/slopcamera.json +47 -0
  147. package/src/icons/sets/soundfish.json +43 -0
  148. package/src/icons/sets/sponge.json +51 -0
  149. package/src/icons/sets/stripe-history.json +39 -0
  150. package/src/icons/sets/wordcell.json +51 -0
  151. package/src/icons/shared/agent-skill.svg +4 -0
  152. package/src/icons/shared/cli.svg +5 -0
  153. package/src/icons/shared/privacy.svg +3 -0
  154. package/src/icons/shared/research.svg +7 -0
  155. package/src/icons/shared/sdk.svg +3 -0
  156. package/src/icons/slopcamera/diagram.svg +3 -0
  157. package/src/icons/slopcamera/direct-scene.svg +5 -0
  158. package/src/icons/slopcamera/edit-video.svg +17 -0
  159. package/src/icons/slopcamera/mcp.svg +3 -0
  160. package/src/icons/slopcamera/native-world.svg +3 -0
  161. package/src/icons/slopcamera/slopcamera.svg +11 -0
  162. package/src/icons/soundfish/agent-ui.svg +7 -0
  163. package/src/icons/soundfish/collaborate.svg +5 -0
  164. package/src/icons/soundfish/compose.svg +3 -0
  165. package/src/icons/soundfish/hear.svg +6 -0
  166. package/src/icons/soundfish/human-ui.svg +6 -0
  167. package/src/icons/soundfish/share.svg +3 -0
  168. package/src/icons/soundfish/soundfish.svg +6 -0
  169. package/src/icons/sponge/api.svg +3 -0
  170. package/src/icons/sponge/documents.svg +3 -0
  171. package/src/icons/sponge/library.svg +3 -0
  172. package/src/icons/sponge/one-question.svg +5 -0
  173. package/src/icons/sponge/people-direct.svg +5 -0
  174. package/src/icons/sponge/read.svg +3 -0
  175. package/src/icons/sponge/save.svg +4 -0
  176. package/src/icons/sponge/sponge.svg +7 -0
  177. package/src/icons/stripe-history/company-history.svg +3 -0
  178. package/src/icons/stripe-history/evidence.svg +4 -0
  179. package/src/icons/stripe-history/independence.svg +7 -0
  180. package/src/icons/stripe-history/publications.svg +11 -0
  181. package/src/icons/stripe-history/sources.svg +6 -0
  182. package/src/icons/stripe-history/stripe-history.svg +3 -0
  183. package/src/icons/wordcell/backlinks.svg +6 -0
  184. package/src/icons/wordcell/capture.svg +6 -0
  185. package/src/icons/wordcell/git-provenance.svg +4 -0
  186. package/src/icons/wordcell/kb.svg +4 -0
  187. package/src/icons/wordcell/markdown.svg +4 -0
  188. package/src/icons/wordcell/scopes.svg +3 -0
  189. package/src/icons/wordcell/search.svg +7 -0
  190. package/src/icons.generated.ts +734 -0
  191. package/src/icons.ts +51 -0
  192. package/src/index.ts +275 -0
  193. package/src/lantern-material.css +213 -0
  194. package/src/launch.ts +624 -0
  195. package/src/marketing-assets/UPSTREAM.md +18 -0
  196. package/src/marketing-assets/cells.svg +1 -0
  197. package/src/marketing-assets/grain.svg +1 -0
  198. package/src/marketing-forced-colors.css +12 -0
  199. package/src/mockups/client.tsx +415 -0
  200. package/src/mockups/core.tsx +318 -0
  201. package/src/mockups/frames.tsx +368 -0
  202. package/src/mockups/index.ts +14 -0
  203. package/src/mockups/surfaces.tsx +681 -0
  204. package/src/mockups.css +2607 -0
  205. package/src/palette-appearance.ts +45 -0
  206. package/src/palette-bridge.css +124 -0
  207. package/src/palette-color.ts +41 -0
  208. package/src/palette-system.css +721 -0
  209. package/src/palette-themes.ts +23 -0
  210. package/src/palette-tokens.stylex.ts +519 -0
  211. package/src/palettes.css +3 -0
  212. package/src/palettes.ts +161 -0
  213. package/src/paper-theme.css +244 -0
  214. package/src/plain-publication.css +1241 -0
  215. package/src/plain-site.css +397 -0
  216. package/src/platforms.ts +151 -0
  217. package/src/portfolio.generated.json +1595 -0
  218. package/src/portfolio.generated.ts +1596 -0
  219. package/src/portfolio.ts +226 -0
  220. package/src/product-marketing-foundation.css +433 -0
  221. package/src/product-marketing-preset.css +261 -0
  222. package/src/product-marketing.css +2186 -0
  223. package/src/provider-marks.generated.ts +112 -0
  224. package/src/provider-marks.ts +143 -0
  225. package/src/react/animated-rail-stage.stylex.ts +15 -0
  226. package/src/react/animated-rail-stage.tsx +51 -0
  227. package/src/react/app-shell.stylex.ts +104 -0
  228. package/src/react/app-shell.tsx +121 -0
  229. package/src/react/article.tsx +602 -0
  230. package/src/react/aurora-dots-background.tsx +34 -0
  231. package/src/react/charts.stylex.ts +563 -0
  232. package/src/react/charts.tsx +610 -0
  233. package/src/react/chat.stylex.ts +34 -0
  234. package/src/react/chat.tsx +155 -0
  235. package/src/react/design-gallery.tsx +943 -0
  236. package/src/react/design-palette.stylex.ts +124 -0
  237. package/src/react/design-palette.tsx +196 -0
  238. package/src/react/design-theme-context.tsx +41 -0
  239. package/src/react/effects.stylex.ts +325 -0
  240. package/src/react/fader.stylex.ts +71 -0
  241. package/src/react/fader.tsx +151 -0
  242. package/src/react/foil-card-math.ts +100 -0
  243. package/src/react/foil-card-surface.stylex.ts +326 -0
  244. package/src/react/foil-card-surface.tsx +742 -0
  245. package/src/react/foil-mark.tsx +35 -0
  246. package/src/react/foil.stylex.ts +163 -0
  247. package/src/react/haptics.ts +228 -0
  248. package/src/react/hero-backdrop.tsx +22 -0
  249. package/src/react/index.ts +38 -0
  250. package/src/react/keyboard-shortcuts.ts +228 -0
  251. package/src/react/lantern-material-gallery.tsx +166 -0
  252. package/src/react/lantern-material.stylex.ts +39 -0
  253. package/src/react/launch-beats.tsx +73 -0
  254. package/src/react/navigation-rail.stylex.ts +130 -0
  255. package/src/react/navigation-rail.tsx +228 -0
  256. package/src/react/particle-halo.tsx +115 -0
  257. package/src/react/phaser-dots.tsx +501 -0
  258. package/src/react/platform-icons.tsx +94 -0
  259. package/src/react/platform-install.stylex.ts +385 -0
  260. package/src/react/platform-install.tsx +389 -0
  261. package/src/react/playback-transport.stylex.ts +14 -0
  262. package/src/react/playback-transport.tsx +112 -0
  263. package/src/react/procedural-backdrop.tsx +192 -0
  264. package/src/react/procedural-recipe.ts +323 -0
  265. package/src/react/product-marketing.stylex.ts +4469 -0
  266. package/src/react/product-marketing.tsx +1611 -0
  267. package/src/react/production-data-preview-notice.stylex.ts +40 -0
  268. package/src/react/production-data-preview-notice.tsx +34 -0
  269. package/src/react/provider-mark.stylex.ts +132 -0
  270. package/src/react/provider-mark.tsx +120 -0
  271. package/src/react/relative-time.tsx +117 -0
  272. package/src/react/route-state.stylex.ts +39 -0
  273. package/src/react/route-state.tsx +350 -0
  274. package/src/react/server.ts +11 -0
  275. package/src/react/social-kit-panel.tsx +103 -0
  276. package/src/react/sticky-offset.tsx +33 -0
  277. package/src/react/surfaces.stylex.ts +179 -0
  278. package/src/react/surfaces.tsx +330 -0
  279. package/src/react/syntax-code.tsx +33 -0
  280. package/src/react/theme-color-sync.ts +1 -0
  281. package/src/react/theme-resolution.ts +11 -0
  282. package/src/react/theme.stylex.ts +276 -0
  283. package/src/react/theme.tsx +402 -0
  284. package/src/reading.css +72 -0
  285. package/src/relative-time.ts +185 -0
  286. package/src/reset.css +2 -0
  287. package/src/site-shell.css +16 -0
  288. package/src/status-page-html.ts +63 -0
  289. package/src/status-page.css +276 -0
  290. package/src/status-page.ts +323 -0
  291. package/src/styles.css +21 -0
  292. package/src/syntax-code-lexers.ts +303 -0
  293. package/src/syntax-highlighting.css +78 -0
  294. package/src/syntax-highlighting.ts +477 -0
  295. package/src/syntax-tokens.ts +33 -0
  296. package/src/testing.ts +307 -0
  297. package/src/tokens.css +3 -0
  298. package/src/typography.css +86 -0
  299. package/vendor/evilcharts/LICENSE +21 -0
  300. package/vendor/evilcharts/UPSTREAM.md +14 -0
  301. package/vendor/platform-marks/LICENSE +30 -0
  302. package/vendor/platform-marks/UPSTREAM.md +15 -0
  303. package/vendor/platform-marks/apple.svg +1 -0
  304. package/vendor/platform-marks/linux.svg +1 -0
  305. package/vendor/provider-marks/CRUSH-LICENSE.md +134 -0
  306. package/vendor/provider-marks/LICENSE +21 -0
  307. package/vendor/provider-marks/UPSTREAM.md +27 -0
  308. package/vendor/provider-marks/aider.svg +3 -0
  309. package/vendor/provider-marks/crush-heartbit.svg +8 -0
  310. package/vendor/provider-marks/lobehub/alibabacloud-color.svg +1 -0
  311. package/vendor/provider-marks/lobehub/alibabacloud.svg +1 -0
  312. package/vendor/provider-marks/lobehub/amp-color.svg +1 -0
  313. package/vendor/provider-marks/lobehub/amp.svg +1 -0
  314. package/vendor/provider-marks/lobehub/anthropic.svg +1 -0
  315. package/vendor/provider-marks/lobehub/claude-color.svg +1 -0
  316. package/vendor/provider-marks/lobehub/claude.svg +1 -0
  317. package/vendor/provider-marks/lobehub/claudecode-color.svg +1 -0
  318. package/vendor/provider-marks/lobehub/claudecode.svg +1 -0
  319. package/vendor/provider-marks/lobehub/codex-color.svg +1 -0
  320. package/vendor/provider-marks/lobehub/codex.svg +1 -0
  321. package/vendor/provider-marks/lobehub/cursor.svg +1 -0
  322. package/vendor/provider-marks/lobehub/deepseek-color.svg +1 -0
  323. package/vendor/provider-marks/lobehub/deepseek.svg +1 -0
  324. package/vendor/provider-marks/lobehub/devin-color.svg +1 -0
  325. package/vendor/provider-marks/lobehub/devin.svg +1 -0
  326. package/vendor/provider-marks/lobehub/gemini-color.svg +1 -0
  327. package/vendor/provider-marks/lobehub/gemini.svg +1 -0
  328. package/vendor/provider-marks/lobehub/geminicli-color.svg +1 -0
  329. package/vendor/provider-marks/lobehub/geminicli.svg +1 -0
  330. package/vendor/provider-marks/lobehub/goose.svg +1 -0
  331. package/vendor/provider-marks/lobehub/meta-color.svg +1 -0
  332. package/vendor/provider-marks/lobehub/meta.svg +1 -0
  333. package/vendor/provider-marks/lobehub/mistral-color.svg +1 -0
  334. package/vendor/provider-marks/lobehub/mistral.svg +1 -0
  335. package/vendor/provider-marks/lobehub/moonshot.svg +1 -0
  336. package/vendor/provider-marks/lobehub/nvidia-color.svg +1 -0
  337. package/vendor/provider-marks/lobehub/nvidia.svg +1 -0
  338. package/vendor/provider-marks/lobehub/openai.svg +1 -0
  339. package/vendor/provider-marks/lobehub/opencode.svg +1 -0
  340. package/vendor/provider-marks/lobehub/perplexity-color.svg +1 -0
  341. package/vendor/provider-marks/lobehub/perplexity.svg +1 -0
  342. package/vendor/provider-marks/lobehub/qwen-color.svg +1 -0
  343. package/vendor/provider-marks/lobehub/qwen.svg +1 -0
  344. package/vendor/provider-marks/lobehub/xai.svg +1 -0
  345. package/vendor/provider-marks/lobehub/zai.svg +1 -0
@@ -0,0 +1,9 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined")
5
+ return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ export { __require };
@@ -0,0 +1,454 @@
1
+ // src/article.ts
2
+ var ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/u;
3
+ var DAY_MS = 86400000;
4
+ var MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
5
+ function isoDateDay(value) {
6
+ const match = ISO_DATE.exec(value);
7
+ if (match === null)
8
+ return null;
9
+ const year = Number(match[1]);
10
+ const month = Number(match[2]);
11
+ const day = Number(match[3]);
12
+ if (year < 1 || month < 1 || month > 12 || day < 1)
13
+ return null;
14
+ const time = Date.UTC(year, month - 1, day);
15
+ const date = new Date(time);
16
+ if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) {
17
+ return null;
18
+ }
19
+ return Math.round(time / DAY_MS);
20
+ }
21
+ function isArticleIsoDate(value) {
22
+ return typeof value === "string" && isoDateDay(value) !== null;
23
+ }
24
+ function requireDay(value, label) {
25
+ const day = isoDateDay(value);
26
+ if (day === null)
27
+ throw new RangeError(`${label} must be a real calendar date written as YYYY-MM-DD.`);
28
+ return day;
29
+ }
30
+ function articleDaysBetween(from, to) {
31
+ return requireDay(to, "The end date") - requireDay(from, "The start date");
32
+ }
33
+ function formatArticleDate(value) {
34
+ requireDay(value, "An article date");
35
+ const [year, month, day] = value.split("-").map(Number);
36
+ return `${day} ${MONTHS[month - 1]} ${year}`;
37
+ }
38
+ function assertArticleDates(dates) {
39
+ const published = requireDay(dates.published, "The published date");
40
+ if (dates.updated === undefined)
41
+ return;
42
+ const updated = requireDay(dates.updated, "The updated date");
43
+ if (updated < published)
44
+ throw new RangeError("The updated date cannot precede the published date.");
45
+ }
46
+ var SAFE_HREF = /^(?:https?:\/\/|mailto:|\/(?![/\\])|#|\.{1,2}\/|\?)/iu;
47
+ function hasControlOrSpace(href) {
48
+ for (const character of href) {
49
+ const code = character.codePointAt(0) ?? 0;
50
+ if (code <= 31 || code >= 127 && code <= 159 || /\s/u.test(character))
51
+ return true;
52
+ }
53
+ return false;
54
+ }
55
+ function isSafeHref(href) {
56
+ return SAFE_HREF.test(href) && !hasControlOrSpace(href);
57
+ }
58
+ function assertArticleHref(href) {
59
+ if (!isSafeHref(href)) {
60
+ throw new RangeError(`Unsupported article link: ${JSON.stringify(href)}.`);
61
+ }
62
+ }
63
+ var articleCalloutTones = ["note", "limit", "warning"];
64
+ function assertArticleCalloutTone(tone) {
65
+ if (!articleCalloutTones.includes(tone))
66
+ throw new RangeError("Unknown article callout tone.");
67
+ }
68
+ var ARTICLE_BYLINE_PREFIX = "By";
69
+ var ARTICLE_TOC_LABEL = "On this page";
70
+ var ARTICLE_SOURCES_HEADING = "Sources";
71
+ function assertArticleAuthor(author) {
72
+ if (author.kind !== "organization" && author.kind !== "person")
73
+ throw new RangeError("An article author is an organization or a person.");
74
+ if (!nonBlank(author.name))
75
+ throw new RangeError("An article author needs a name.");
76
+ if (author.href !== undefined)
77
+ assertArticleHref(author.href);
78
+ }
79
+ var articleReviewerTypes = ["author", "human-editor", "subject-expert", "ai"];
80
+ var articleDraftingKinds = ["ai-from-source", "ai", "ai-assisted", "author"];
81
+ var DRAFTING_PHRASES = {
82
+ "ai-from-source": "Drafted with AI from the source code",
83
+ ai: "Drafted with AI",
84
+ "ai-assisted": "Written with AI assistance",
85
+ author: "Written by the author"
86
+ };
87
+ var REVIEWER_SUFFIXES = {
88
+ ai: "",
89
+ author: ", the author",
90
+ "human-editor": ", a human editor",
91
+ "subject-expert": ", a subject expert"
92
+ };
93
+ var HUMAN_WORD = /human/iu;
94
+ var AI_WORD = /\b(?:ai|llm|model|claude|gpt|gemini|codex)\b/iu;
95
+ function articleReviewerNameDisclosesAi(reviewer) {
96
+ return AI_WORD.test(reviewer);
97
+ }
98
+ function isOneOf(values, value) {
99
+ return typeof value === "string" && values.includes(value);
100
+ }
101
+ function withoutTrailingPeriods(value) {
102
+ let end = value.length;
103
+ while (end > 0 && value.charCodeAt(end - 1) === 46)
104
+ end -= 1;
105
+ return value.slice(0, end);
106
+ }
107
+ function nonBlank(value) {
108
+ return typeof value === "string" && value.trim().length > 0;
109
+ }
110
+ function articleProvenanceSentence(provenance) {
111
+ if (!isOneOf(articleDraftingKinds, provenance.drafting))
112
+ throw new RangeError("Unknown article drafting kind.");
113
+ const drafted = DRAFTING_PHRASES[provenance.drafting];
114
+ const {
115
+ review
116
+ } = provenance;
117
+ if (review === null)
118
+ return `${drafted}. It has not been reviewed yet.`;
119
+ if (!isOneOf(articleReviewerTypes, review.reviewerType))
120
+ throw new RangeError("Unknown article reviewer type.");
121
+ if (!nonBlank(review.reviewer))
122
+ throw new RangeError("An article review must name its reviewer.");
123
+ if (review.reviewerType !== "human-editor" && HUMAN_WORD.test(review.reviewer)) {
124
+ throw new RangeError('Only a human-editor review may use the word "human" in its reviewer name.');
125
+ }
126
+ if (review.reviewerType === "ai" && !AI_WORD.test(review.reviewer)) {
127
+ throw new RangeError('An AI review must name a reviewer that says it is AI, for example "Claude Opus 5.5 (claude-opus-5-5) editorial review".');
128
+ }
129
+ const reviewer = withoutTrailingPeriods(review.reviewer.trim());
130
+ return `${drafted} and reviewed by ${reviewer}${REVIEWER_SUFFIXES[review.reviewerType]}.`;
131
+ }
132
+ var articleScoreKeys = ["readerUtility", "originalEvidence", "factualConfidence", "hostFit", "voiceIntegrity", "maintenanceValue"];
133
+ var ARTICLE_ADMISSION_MINIMUM = 9;
134
+ var ARTICLE_REASSESS_WINDOW = {
135
+ minimumDays: 28,
136
+ maximumDays: 56
137
+ };
138
+ var articleLifecycles = ["quarantined", "indexable", "archived"];
139
+ function articleAdmissionScore(scores) {
140
+ return articleScoreKeys.reduce((total, key) => total + scores[key], 0);
141
+ }
142
+ function articleAdmissionPasses(scores) {
143
+ return articleScoreKeys.every((key) => scores[key] > 0) && articleAdmissionScore(scores) >= ARTICLE_ADMISSION_MINIMUM;
144
+ }
145
+ function isArticleIndexable(admission) {
146
+ return admission.lifecycle === "indexable";
147
+ }
148
+ function articleProvenanceFromAdmission(admission) {
149
+ return {
150
+ drafting: admission.drafting,
151
+ review: admission.review === null ? null : {
152
+ reviewer: admission.review.reviewer,
153
+ reviewerType: admission.review.reviewerType
154
+ }
155
+ };
156
+ }
157
+ function articleAdmissionsDue(admissions, today) {
158
+ const day = requireDay(today, "Today");
159
+ return admissions.filter((admission) => admission.lifecycle !== "archived" && requireDay(admission.reassessOn, "reassessOn") <= day);
160
+ }
161
+
162
+ class ArticleAdmissionError extends Error {
163
+ issues;
164
+ constructor(issues) {
165
+ super(`Article admission failed:
166
+ ${issues.map((issue) => `- ${issue}`).join(`
167
+ `)}`);
168
+ this.name = "ArticleAdmissionError";
169
+ this.issues = issues;
170
+ }
171
+ }
172
+ function record(value) {
173
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
174
+ }
175
+ function text(issues, where, value, field) {
176
+ if (!nonBlank(value)) {
177
+ issues.push(`${where}: ${field} must be nonblank text.`);
178
+ return "";
179
+ }
180
+ return value;
181
+ }
182
+ function date(issues, where, value, field) {
183
+ if (!isArticleIsoDate(value)) {
184
+ issues.push(`${where}: ${field} must be a real calendar date written as YYYY-MM-DD.`);
185
+ return null;
186
+ }
187
+ return value;
188
+ }
189
+ function list(issues, where, value, field) {
190
+ if (!Array.isArray(value)) {
191
+ issues.push(`${where}: ${field} must be a list.`);
192
+ return [];
193
+ }
194
+ return value;
195
+ }
196
+ function parseReview(issues, where, value, field) {
197
+ if (value === null)
198
+ return null;
199
+ const review = record(value);
200
+ if (review === null) {
201
+ issues.push(`${where}: ${field} must be a review record or null.`);
202
+ return null;
203
+ }
204
+ const reviewer = text(issues, where, review.reviewer, `${field}.reviewer`);
205
+ if (!isOneOf(articleReviewerTypes, review.reviewerType)) {
206
+ issues.push(`${where}: ${field}.reviewerType must be one of ${articleReviewerTypes.join(", ")}.`);
207
+ }
208
+ const reviewedOn = date(issues, where, review.reviewedOn, `${field}.reviewedOn`);
209
+ if (!isOneOf(articleReviewerTypes, review.reviewerType) || reviewedOn === null)
210
+ return null;
211
+ if (review.reviewerType !== "human-editor" && HUMAN_WORD.test(reviewer)) {
212
+ issues.push(`${where}: ${field}.reviewer may use the word "human" only when reviewerType is human-editor.`);
213
+ }
214
+ if (review.reviewerType === "ai" && !AI_WORD.test(reviewer)) {
215
+ issues.push(`${where}: ${field}.reviewer must say it is AI when reviewerType is ai, for example by naming the model.`);
216
+ }
217
+ return {
218
+ reviewer,
219
+ reviewerType: review.reviewerType,
220
+ reviewedOn
221
+ };
222
+ }
223
+ function parseScores(issues, where, value) {
224
+ const scores = record(value);
225
+ if (scores === null) {
226
+ issues.push(`${where}: scores must be a record of six 0-2 scores.`);
227
+ return null;
228
+ }
229
+ const extra = Object.keys(scores).filter((key) => !isOneOf(articleScoreKeys, key));
230
+ if (extra.length > 0)
231
+ issues.push(`${where}: unknown score ${extra.sort().join(", ")}.`);
232
+ let complete = true;
233
+ for (const key of articleScoreKeys) {
234
+ const score = scores[key];
235
+ if (score !== 0 && score !== 1 && score !== 2) {
236
+ issues.push(`${where}: scores.${key} must be 0, 1, or 2.`);
237
+ complete = false;
238
+ }
239
+ }
240
+ return complete ? scores : null;
241
+ }
242
+ function parseArticleAdmission(value, index, issues) {
243
+ const start = issues.length;
244
+ const input = record(value);
245
+ if (input === null) {
246
+ issues.push(`admission ${index}: must be a record.`);
247
+ return null;
248
+ }
249
+ const where = nonBlank(input.href) ? input.href : `admission ${index}`;
250
+ const href = text(issues, where, input.href, "href");
251
+ if (!isOneOf(articleLifecycles, input.lifecycle)) {
252
+ issues.push(`${where}: lifecycle must be one of ${articleLifecycles.join(", ")}.`);
253
+ }
254
+ const lifecycle = input.lifecycle;
255
+ const readerJob = text(issues, where, input.readerJob, "readerJob");
256
+ const nonObviousAnswer = text(issues, where, input.nonObviousAnswer, "nonObviousAnswer");
257
+ const originalContribution = text(issues, where, input.originalContribution, "originalContribution");
258
+ const hostFit = text(issues, where, input.hostFit, "hostFit");
259
+ const owner = text(issues, where, input.owner, "owner");
260
+ const harmIfWrong = text(issues, where, input.harmIfWrong, "harmIfWrong");
261
+ if (!isOneOf(articleDraftingKinds, input.drafting)) {
262
+ issues.push(`${where}: drafting must be one of ${articleDraftingKinds.join(", ")}.`);
263
+ }
264
+ const drafting = input.drafting;
265
+ const nearestUrls = list(issues, where, input.nearestUrls, "nearestUrls").map((item, position) => {
266
+ const neighbor = record(item);
267
+ if (neighbor === null) {
268
+ issues.push(`${where}: nearestUrls[${position}] must be a record.`);
269
+ return {
270
+ url: "",
271
+ distinction: ""
272
+ };
273
+ }
274
+ return {
275
+ url: text(issues, where, neighbor.url, `nearestUrls[${position}].url`),
276
+ distinction: text(issues, where, neighbor.distinction, `nearestUrls[${position}].distinction`)
277
+ };
278
+ });
279
+ if (nearestUrls.length > 3)
280
+ issues.push(`${where}: nearestUrls lists at most three pages.`);
281
+ const sources = list(issues, where, input.sources, "sources").map((item, position) => {
282
+ const source = record(item);
283
+ if (source === null) {
284
+ issues.push(`${where}: sources[${position}] must be a record.`);
285
+ return null;
286
+ }
287
+ const checkedOn = date(issues, where, source.checkedOn, `sources[${position}].checkedOn`);
288
+ const url = text(issues, where, source.url, `sources[${position}].url`);
289
+ if (url !== "" && !isSafeHref(url))
290
+ issues.push(`${where}: sources[${position}].url must be a web, mail, or relative link.`);
291
+ return {
292
+ title: text(issues, where, source.title, `sources[${position}].title`),
293
+ url,
294
+ checkedOn: checkedOn ?? "0000-00-00"
295
+ };
296
+ }).filter((source) => source !== null);
297
+ const observations = list(issues, where, input.observations, "observations").map((item, position) => text(issues, where, item, `observations[${position}]`));
298
+ const refreshTriggers = list(issues, where, input.refreshTriggers, "refreshTriggers").map((item, position) => text(issues, where, item, `refreshTriggers[${position}]`));
299
+ const scores = parseScores(issues, where, input.scores);
300
+ const review = parseReview(issues, where, input.review, "review");
301
+ const humanReviewValue = parseReview(issues, where, input.humanReview, "humanReview");
302
+ if (humanReviewValue !== null && humanReviewValue.reviewerType === "ai") {
303
+ issues.push(`${where}: humanReview cannot record an AI reviewer; keep AI review in review.`);
304
+ }
305
+ const reassessOn = date(issues, where, input.reassessOn, "reassessOn");
306
+ if (issues.length > start || scores === null || reassessOn === null)
307
+ return null;
308
+ return {
309
+ href,
310
+ lifecycle,
311
+ readerJob,
312
+ nonObviousAnswer,
313
+ originalContribution,
314
+ hostFit,
315
+ nearestUrls,
316
+ sources,
317
+ observations,
318
+ scores,
319
+ owner,
320
+ drafting,
321
+ review,
322
+ humanReview: humanReviewValue,
323
+ reassessOn,
324
+ harmIfWrong,
325
+ refreshTriggers
326
+ };
327
+ }
328
+ function admissionRuleIssues(admission) {
329
+ const issues = [];
330
+ const where = admission.href;
331
+ const {
332
+ review,
333
+ humanReview
334
+ } = admission;
335
+ if (review !== null) {
336
+ const days = articleDaysBetween(review.reviewedOn, admission.reassessOn);
337
+ if (days < ARTICLE_REASSESS_WINDOW.minimumDays || days > ARTICLE_REASSESS_WINDOW.maximumDays) {
338
+ issues.push(`${where}: reassessOn must fall ${ARTICLE_REASSESS_WINDOW.minimumDays} to ${ARTICLE_REASSESS_WINDOW.maximumDays} days after review.reviewedOn (found ${days}).`);
339
+ }
340
+ for (const source of admission.sources) {
341
+ if (articleDaysBetween(source.checkedOn, review.reviewedOn) < 0) {
342
+ issues.push(`${where}: source "${source.title}" was checked after the review; review it again.`);
343
+ }
344
+ }
345
+ }
346
+ if (humanReview !== null && review === null) {
347
+ issues.push(`${where}: record the editorial review in review before adding humanReview.`);
348
+ }
349
+ if (admission.lifecycle === "indexable") {
350
+ if (!articleAdmissionPasses(admission.scores)) {
351
+ issues.push(`${where}: indexable articles need a score of at least ${ARTICLE_ADMISSION_MINIMUM}/12 with no zero (found ${articleAdmissionScore(admission.scores)}).`);
352
+ }
353
+ if (review === null)
354
+ issues.push(`${where}: indexable articles need a review with reviewer, reviewerType, and reviewedOn.`);
355
+ else if (review.reviewerType === "author")
356
+ issues.push(`${where}: indexable articles need an independent review; the author cannot admit their own post.`);
357
+ if (admission.sources.length === 0)
358
+ issues.push(`${where}: indexable articles need at least one source with a check date.`);
359
+ if (admission.observations.length < 2)
360
+ issues.push(`${where}: indexable articles need two observations that are not paraphrases of the sources.`);
361
+ if (admission.refreshTriggers.length === 0)
362
+ issues.push(`${where}: indexable articles need at least one refresh trigger.`);
363
+ }
364
+ return issues;
365
+ }
366
+ function assertArticleAdmissions(value) {
367
+ const issues = [];
368
+ if (!Array.isArray(value))
369
+ throw new ArticleAdmissionError(["The admission registry must be a list."]);
370
+ const seen = new Set;
371
+ value.forEach((item, index) => {
372
+ const admission = parseArticleAdmission(item, index, issues);
373
+ if (admission === null)
374
+ return;
375
+ if (seen.has(admission.href))
376
+ issues.push(`${admission.href}: appears more than once.`);
377
+ seen.add(admission.href);
378
+ issues.push(...admissionRuleIssues(admission));
379
+ });
380
+ if (issues.length > 0)
381
+ throw new ArticleAdmissionError(issues);
382
+ }
383
+ function parseArticleAdmissions(value) {
384
+ assertArticleAdmissions(value);
385
+ return value;
386
+ }
387
+ var VIDEO_TYPE_ORDER = {
388
+ "video/webm": 0,
389
+ "video/mp4": 1
390
+ };
391
+ function assertArticleVideo(video) {
392
+ if (video.name.trim() === "")
393
+ throw new RangeError("Article video needs a name.");
394
+ if (video.description.trim() === "")
395
+ throw new RangeError("Article video needs a description.");
396
+ if (video.sources.length === 0)
397
+ throw new RangeError("Article video needs at least one source.");
398
+ const types = new Set;
399
+ for (const source of video.sources) {
400
+ assertArticleHref(source.src);
401
+ if (!Object.hasOwn(VIDEO_TYPE_ORDER, source.type))
402
+ throw new RangeError(`Unsupported article video type: ${String(source.type)}.`);
403
+ if (types.has(source.type))
404
+ throw new RangeError(`Article video lists ${source.type} twice.`);
405
+ types.add(source.type);
406
+ }
407
+ assertArticleHref(video.poster);
408
+ assertArticleHref(video.captions);
409
+ if (!/\.vtt(?:[?#]|$)/u.test(video.captions))
410
+ throw new RangeError("Article video captions must be a WebVTT file.");
411
+ for (const [key, value] of [["width", video.width], ["height", video.height]]) {
412
+ if (!Number.isInteger(value) || value <= 0)
413
+ throw new RangeError(`Article video ${key} must be a positive integer.`);
414
+ }
415
+ if (!/^PT(?:\d+H)?(?:\d+M)?(?:\d+(?:\.\d+)?S)?$/u.test(video.duration) || video.duration === "PT") {
416
+ throw new RangeError(`Article video duration must be an ISO 8601 time duration such as PT48S; received ${video.duration}.`);
417
+ }
418
+ if (!isArticleIsoDate(video.uploadDate))
419
+ throw new RangeError("Article video uploadDate must be an ISO date.");
420
+ }
421
+ function orderedArticleVideoSources(video) {
422
+ return video.sources.toSorted((a, b) => VIDEO_TYPE_ORDER[a.type] - VIDEO_TYPE_ORDER[b.type]);
423
+ }
424
+ var HTTP_ORIGIN = /^https?:\/\/[^/?#\s]+$/iu;
425
+ var ABSOLUTE_HTTP = /^https?:\/\/[^/?#\s]+/iu;
426
+ function absoluteArticleUrl(href, origin) {
427
+ if (ABSOLUTE_HTTP.test(href))
428
+ return href;
429
+ if (href.startsWith("//"))
430
+ throw new RangeError(`Article video paths must not be protocol-relative: ${href}`);
431
+ return href.startsWith("/") ? `${origin}${href}` : `${origin}/${href}`;
432
+ }
433
+ function articleVideoJsonLd(video, origin) {
434
+ assertArticleVideo(video);
435
+ if (!HTTP_ORIGIN.test(origin.replace(/\/$/u, "")))
436
+ throw new RangeError("articleVideoJsonLd needs an http or https origin.");
437
+ origin = origin.replace(/\/$/u, "");
438
+ const sources = orderedArticleVideoSources(video);
439
+ return Object.freeze({
440
+ "@type": "VideoObject",
441
+ name: video.name,
442
+ description: video.description,
443
+ thumbnailUrl: absoluteArticleUrl(video.poster, origin),
444
+ contentUrl: absoluteArticleUrl(sources.at(-1)?.src ?? "", origin),
445
+ encodingFormat: sources.at(-1)?.type,
446
+ uploadDate: video.uploadDate,
447
+ duration: video.duration,
448
+ width: video.width,
449
+ height: video.height,
450
+ caption: absoluteArticleUrl(video.captions, origin)
451
+ });
452
+ }
453
+
454
+ export { isArticleIsoDate, articleDaysBetween, formatArticleDate, assertArticleDates, assertArticleHref, articleCalloutTones, assertArticleCalloutTone, ARTICLE_BYLINE_PREFIX, ARTICLE_TOC_LABEL, ARTICLE_SOURCES_HEADING, assertArticleAuthor, articleReviewerTypes, articleDraftingKinds, articleReviewerNameDisclosesAi, articleProvenanceSentence, articleScoreKeys, ARTICLE_ADMISSION_MINIMUM, ARTICLE_REASSESS_WINDOW, articleLifecycles, articleAdmissionScore, articleAdmissionPasses, isArticleIndexable, articleProvenanceFromAdmission, articleAdmissionsDue, ArticleAdmissionError, assertArticleAdmissions, parseArticleAdmissions, assertArticleVideo, orderedArticleVideoSources, articleVideoJsonLd };