astro-tractstack 2.0.0-rc.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.
Files changed (427) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +56 -0
  3. package/astro.d.ts +64 -0
  4. package/bin/create-tractstack.js +483 -0
  5. package/dist/config.js +80 -0
  6. package/dist/index.js +2129 -0
  7. package/package.json +89 -0
  8. package/templates/artpacks/kCz/captainBreakfast_1080px.webp +0 -0
  9. package/templates/artpacks/kCz/captainBreakfast_1920px.webp +0 -0
  10. package/templates/artpacks/kCz/captainBreakfast_600px.webp +0 -0
  11. package/templates/artpacks/kCz/cleanDrips_1080px.webp +0 -0
  12. package/templates/artpacks/kCz/cleanDrips_1920px.webp +0 -0
  13. package/templates/artpacks/kCz/cleanDrips_600px.webp +0 -0
  14. package/templates/artpacks/kCz/crispwaves_1080px.webp +0 -0
  15. package/templates/artpacks/kCz/crispwaves_1920px.webp +0 -0
  16. package/templates/artpacks/kCz/crispwaves_600px.webp +0 -0
  17. package/templates/artpacks/kCz/dragonSkin_1080px.webp +0 -0
  18. package/templates/artpacks/kCz/dragonSkin_1920px.webp +0 -0
  19. package/templates/artpacks/kCz/dragonSkin_600px.webp +0 -0
  20. package/templates/artpacks/kCz/dragon_1080px.webp +0 -0
  21. package/templates/artpacks/kCz/dragon_1920px.webp +0 -0
  22. package/templates/artpacks/kCz/dragon_600px.webp +0 -0
  23. package/templates/artpacks/kCz/nightcity_1080px.webp +0 -0
  24. package/templates/artpacks/kCz/nightcity_1920px.webp +0 -0
  25. package/templates/artpacks/kCz/nightcity_600px.webp +0 -0
  26. package/templates/artpacks/kCz/pattern1_1080px.webp +0 -0
  27. package/templates/artpacks/kCz/pattern1_1920px.webp +0 -0
  28. package/templates/artpacks/kCz/pattern1_600px.webp +0 -0
  29. package/templates/artpacks/kCz/pattern2_1080px.webp +0 -0
  30. package/templates/artpacks/kCz/pattern2_1920px.webp +0 -0
  31. package/templates/artpacks/kCz/pattern2_600px.webp +0 -0
  32. package/templates/artpacks/kCz/skindrips_1080px.webp +0 -0
  33. package/templates/artpacks/kCz/skindrips_1920px.webp +0 -0
  34. package/templates/artpacks/kCz/skindrips_600px.webp +0 -0
  35. package/templates/artpacks/kCz/slimetime_1080px.webp +0 -0
  36. package/templates/artpacks/kCz/slimetime_1920px.webp +0 -0
  37. package/templates/artpacks/kCz/slimetime_600px.webp +0 -0
  38. package/templates/artpacks/kCz/snake_1080px.webp +0 -0
  39. package/templates/artpacks/kCz/snake_1920px.webp +0 -0
  40. package/templates/artpacks/kCz/snake_600px.webp +0 -0
  41. package/templates/artpacks/kCz/toxicshock_1080px.webp +0 -0
  42. package/templates/artpacks/kCz/toxicshock_1920px.webp +0 -0
  43. package/templates/artpacks/kCz/toxicshock_600px.webp +0 -0
  44. package/templates/artpacks/kCz/tractstack_1080px.webp +0 -0
  45. package/templates/artpacks/kCz/tractstack_1920px.webp +0 -0
  46. package/templates/artpacks/kCz/tractstack_600px.webp +0 -0
  47. package/templates/artpacks/kCz/tripdrips_1080px.webp +0 -0
  48. package/templates/artpacks/kCz/tripdrips_1920px.webp +0 -0
  49. package/templates/artpacks/kCz/tripdrips_600px.webp +0 -0
  50. package/templates/artpacks/kCz/wavedrips_1080px.webp +0 -0
  51. package/templates/artpacks/kCz/wavedrips_1920px.webp +0 -0
  52. package/templates/artpacks/kCz/wavedrips_600px.webp +0 -0
  53. package/templates/artpacks/t8k/beach_1080px.webp +0 -0
  54. package/templates/artpacks/t8k/beach_1920px.webp +0 -0
  55. package/templates/artpacks/t8k/beach_600px.webp +0 -0
  56. package/templates/artpacks/t8k/blast_1080px.webp +0 -0
  57. package/templates/artpacks/t8k/blast_1920px.webp +0 -0
  58. package/templates/artpacks/t8k/blast_600px.webp +0 -0
  59. package/templates/artpacks/t8k/bokeh_1080px.webp +0 -0
  60. package/templates/artpacks/t8k/bokeh_1920px.webp +0 -0
  61. package/templates/artpacks/t8k/bokeh_600px.webp +0 -0
  62. package/templates/artpacks/t8k/cartoon_1080px.webp +0 -0
  63. package/templates/artpacks/t8k/cartoon_1920px.webp +0 -0
  64. package/templates/artpacks/t8k/cartoon_600px.webp +0 -0
  65. package/templates/artpacks/t8k/darkeggshell_1080px.webp +0 -0
  66. package/templates/artpacks/t8k/darkeggshell_1920px.webp +0 -0
  67. package/templates/artpacks/t8k/darkeggshell_600px.webp +0 -0
  68. package/templates/artpacks/t8k/explosion_1080px.webp +0 -0
  69. package/templates/artpacks/t8k/explosion_1920px.webp +0 -0
  70. package/templates/artpacks/t8k/explosion_600px.webp +0 -0
  71. package/templates/artpacks/t8k/floral_1080px.webp +0 -0
  72. package/templates/artpacks/t8k/floral_1920px.webp +0 -0
  73. package/templates/artpacks/t8k/floral_600px.webp +0 -0
  74. package/templates/artpacks/t8k/flower_1080px.webp +0 -0
  75. package/templates/artpacks/t8k/flower_1920px.webp +0 -0
  76. package/templates/artpacks/t8k/flower_600px.webp +0 -0
  77. package/templates/artpacks/t8k/foliage_1080px.webp +0 -0
  78. package/templates/artpacks/t8k/foliage_1920px.webp +0 -0
  79. package/templates/artpacks/t8k/foliage_600px.webp +0 -0
  80. package/templates/artpacks/t8k/mist_1080px.webp +0 -0
  81. package/templates/artpacks/t8k/mist_1920px.webp +0 -0
  82. package/templates/artpacks/t8k/mist_600px.webp +0 -0
  83. package/templates/artpacks/t8k/portal_1080px.webp +0 -0
  84. package/templates/artpacks/t8k/portal_1920px.webp +0 -0
  85. package/templates/artpacks/t8k/portal_600px.webp +0 -0
  86. package/templates/artpacks/t8k/storytime_1080px.webp +0 -0
  87. package/templates/artpacks/t8k/storytime_1920px.webp +0 -0
  88. package/templates/artpacks/t8k/storytime_600px.webp +0 -0
  89. package/templates/artpacks/t8k/tacky_1080px.webp +0 -0
  90. package/templates/artpacks/t8k/tacky_1920px.webp +0 -0
  91. package/templates/artpacks/t8k/tacky_600px.webp +0 -0
  92. package/templates/artpacks/t8k/wallpaper_1080px.webp +0 -0
  93. package/templates/artpacks/t8k/wallpaper_1920px.webp +0 -0
  94. package/templates/artpacks/t8k/wallpaper_600px.webp +0 -0
  95. package/templates/brand/favicon.ico +0 -0
  96. package/templates/brand/logo.svg +19 -0
  97. package/templates/brand/static.jpg +0 -0
  98. package/templates/brand/wordmark.svg +4 -0
  99. package/templates/css/custom.css +51 -0
  100. package/templates/css/frontend.css +3519 -0
  101. package/templates/css/storykeep.css +92872 -0
  102. package/templates/custom/minimal/CodeHook.astro +53 -0
  103. package/templates/custom/minimal/CustomRoutes.astro +46 -0
  104. package/templates/custom/with-examples/CodeHook.astro +49 -0
  105. package/templates/custom/with-examples/CustomHero.astro +13 -0
  106. package/templates/custom/with-examples/CustomRoutes.astro +39 -0
  107. package/templates/custom/with-examples/pages/Collections.astro +110 -0
  108. package/templates/env.example +8 -0
  109. package/templates/fonts/Inter-Black.woff2 +0 -0
  110. package/templates/fonts/Inter-Bold.woff2 +0 -0
  111. package/templates/fonts/Inter-Regular.woff2 +0 -0
  112. package/templates/icons/h2.svg +1 -0
  113. package/templates/icons/h3.svg +1 -0
  114. package/templates/icons/h4.svg +1 -0
  115. package/templates/icons/h5.svg +1 -0
  116. package/templates/icons/image.svg +7 -0
  117. package/templates/icons/text.svg +6 -0
  118. package/templates/socials/codepen.svg +1 -0
  119. package/templates/socials/discord.svg +1 -0
  120. package/templates/socials/facebook.svg +1 -0
  121. package/templates/socials/github.svg +1 -0
  122. package/templates/socials/instagram.svg +1 -0
  123. package/templates/socials/linkedin.svg +1 -0
  124. package/templates/socials/mail.svg +1 -0
  125. package/templates/socials/rumble.svg +1 -0
  126. package/templates/socials/tiktok.svg +1 -0
  127. package/templates/socials/twitch.svg +1 -0
  128. package/templates/socials/twitter.svg +1 -0
  129. package/templates/socials/x.svg +1 -0
  130. package/templates/socials/youtube.svg +1 -0
  131. package/templates/src/client/analytics-events.ts +213 -0
  132. package/templates/src/client/belief-events.ts +205 -0
  133. package/templates/src/client/sse.ts +667 -0
  134. package/templates/src/components/Footer.astro +246 -0
  135. package/templates/src/components/Fragment.astro +70 -0
  136. package/templates/src/components/Header.astro +458 -0
  137. package/templates/src/components/Menu.tsx +196 -0
  138. package/templates/src/components/codehooks/BunnyVideoSetup.tsx +692 -0
  139. package/templates/src/components/codehooks/BunnyVideoWrapper.astro +78 -0
  140. package/templates/src/components/codehooks/EpinetDurationSelector.tsx +1020 -0
  141. package/templates/src/components/codehooks/EpinetTableView.tsx +594 -0
  142. package/templates/src/components/codehooks/EpinetWrapper.tsx +424 -0
  143. package/templates/src/components/codehooks/FeaturedContent.astro +273 -0
  144. package/templates/src/components/codehooks/FeaturedContentSetup.tsx +738 -0
  145. package/templates/src/components/codehooks/ListContent.astro +460 -0
  146. package/templates/src/components/codehooks/ListContentSetup.tsx +649 -0
  147. package/templates/src/components/codehooks/SankeyDiagram.tsx +359 -0
  148. package/templates/src/components/compositor/Compositor.tsx +144 -0
  149. package/templates/src/components/compositor/Node.tsx +415 -0
  150. package/templates/src/components/compositor/NodeWithGuid.tsx +25 -0
  151. package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +87 -0
  152. package/templates/src/components/compositor/elements/Belief.tsx +148 -0
  153. package/templates/src/components/compositor/elements/BgImage.tsx +118 -0
  154. package/templates/src/components/compositor/elements/BgVisualBreak.tsx +102 -0
  155. package/templates/src/components/compositor/elements/BunnyVideo.tsx +63 -0
  156. package/templates/src/components/compositor/elements/IdentifyAs.tsx +66 -0
  157. package/templates/src/components/compositor/elements/PlayButton.tsx +19 -0
  158. package/templates/src/components/compositor/elements/SignUp.tsx +179 -0
  159. package/templates/src/components/compositor/elements/Svg.tsx +33 -0
  160. package/templates/src/components/compositor/elements/ToggleBelief.tsx +36 -0
  161. package/templates/src/components/compositor/elements/YouTubeWrapper.tsx +33 -0
  162. package/templates/src/components/compositor/nodes/BgPaneWrapper.tsx +35 -0
  163. package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +189 -0
  164. package/templates/src/components/compositor/nodes/Markdown.tsx +179 -0
  165. package/templates/src/components/compositor/nodes/Pane.tsx +277 -0
  166. package/templates/src/components/compositor/nodes/Pane_eraser.tsx +69 -0
  167. package/templates/src/components/compositor/nodes/Pane_layout.tsx +77 -0
  168. package/templates/src/components/compositor/nodes/RenderChildren.tsx +19 -0
  169. package/templates/src/components/compositor/nodes/StoryFragment.tsx +35 -0
  170. package/templates/src/components/compositor/nodes/TagElement.tsx +14 -0
  171. package/templates/src/components/compositor/nodes/Widget.tsx +115 -0
  172. package/templates/src/components/compositor/nodes/tagElements/NodeA.tsx +4 -0
  173. package/templates/src/components/compositor/nodes/tagElements/NodeA_eraser.tsx +26 -0
  174. package/templates/src/components/compositor/nodes/tagElements/NodeAnchorComponent.tsx +248 -0
  175. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +684 -0
  176. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_eraser.tsx +62 -0
  177. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_insert.tsx +120 -0
  178. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_settings.tsx +62 -0
  179. package/templates/src/components/compositor/nodes/tagElements/NodeButton.tsx +5 -0
  180. package/templates/src/components/compositor/nodes/tagElements/NodeButton_eraser.tsx +26 -0
  181. package/templates/src/components/compositor/nodes/tagElements/NodeImg.tsx +28 -0
  182. package/templates/src/components/compositor/nodes/tagElements/NodeText.tsx +18 -0
  183. package/templates/src/components/compositor/nodes/tagElements/TabIndicator.tsx +51 -0
  184. package/templates/src/components/compositor/preview/FeaturedContentPreview.tsx +128 -0
  185. package/templates/src/components/compositor/preview/ListContentPreview.tsx +213 -0
  186. package/templates/src/components/compositor/preview/OgImagePreview.tsx +223 -0
  187. package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +199 -0
  188. package/templates/src/components/compositor/preview/PanesPreviewGenerator.tsx +123 -0
  189. package/templates/src/components/compositor/preview/VisualBreakPreview.tsx +154 -0
  190. package/templates/src/components/edit/Header.tsx +181 -0
  191. package/templates/src/components/edit/PanelSwitch.tsx +446 -0
  192. package/templates/src/components/edit/SettingsPanel.tsx +70 -0
  193. package/templates/src/components/edit/ToolBar.tsx +101 -0
  194. package/templates/src/components/edit/ToolMode.tsx +121 -0
  195. package/templates/src/components/edit/context/ContextPaneConfig.tsx +91 -0
  196. package/templates/src/components/edit/context/ContextPaneConfig_slug.tsx +174 -0
  197. package/templates/src/components/edit/context/ContextPaneConfig_title.tsx +186 -0
  198. package/templates/src/components/edit/pane/AddPanePanel.tsx +136 -0
  199. package/templates/src/components/edit/pane/AddPanePanel_break.tsx +470 -0
  200. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +264 -0
  201. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +623 -0
  202. package/templates/src/components/edit/pane/AddPanePanel_newAICopy.tsx +107 -0
  203. package/templates/src/components/edit/pane/AddPanePanel_newAICopy_modal.tsx +217 -0
  204. package/templates/src/components/edit/pane/AddPanePanel_newCopyMode.tsx +109 -0
  205. package/templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx +39 -0
  206. package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +445 -0
  207. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +245 -0
  208. package/templates/src/components/edit/pane/PageGen.tsx +485 -0
  209. package/templates/src/components/edit/pane/PageGenSelector.tsx +238 -0
  210. package/templates/src/components/edit/pane/PageGenSpecial.tsx +362 -0
  211. package/templates/src/components/edit/pane/PageGen_preview.tsx +495 -0
  212. package/templates/src/components/edit/pane/PanePanel_impression.tsx +258 -0
  213. package/templates/src/components/edit/pane/PanePanel_path.tsx +268 -0
  214. package/templates/src/components/edit/pane/PanePanel_slug.tsx +219 -0
  215. package/templates/src/components/edit/pane/PanePanel_title.tsx +142 -0
  216. package/templates/src/components/edit/panels/StyleBreakPanel.tsx +182 -0
  217. package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +439 -0
  218. package/templates/src/components/edit/panels/StyleElementPanel.tsx +177 -0
  219. package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +349 -0
  220. package/templates/src/components/edit/panels/StyleElementPanel_remove.tsx +159 -0
  221. package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +320 -0
  222. package/templates/src/components/edit/panels/StyleImagePanel.tsx +460 -0
  223. package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +296 -0
  224. package/templates/src/components/edit/panels/StyleImagePanel_remove.tsx +153 -0
  225. package/templates/src/components/edit/panels/StyleImagePanel_update.tsx +312 -0
  226. package/templates/src/components/edit/panels/StyleLiElementPanel.tsx +273 -0
  227. package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +301 -0
  228. package/templates/src/components/edit/panels/StyleLiElementPanel_remove.tsx +132 -0
  229. package/templates/src/components/edit/panels/StyleLiElementPanel_update.tsx +313 -0
  230. package/templates/src/components/edit/panels/StyleLinkPanel.tsx +346 -0
  231. package/templates/src/components/edit/panels/StyleLinkPanel_add.tsx +265 -0
  232. package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +240 -0
  233. package/templates/src/components/edit/panels/StyleLinkPanel_remove.tsx +94 -0
  234. package/templates/src/components/edit/panels/StyleLinkPanel_update.tsx +110 -0
  235. package/templates/src/components/edit/panels/StyleParentPanel.tsx +263 -0
  236. package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +275 -0
  237. package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +112 -0
  238. package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +87 -0
  239. package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +141 -0
  240. package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +428 -0
  241. package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +292 -0
  242. package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +190 -0
  243. package/templates/src/components/edit/panels/StyleWidgetPanel_remove.tsx +152 -0
  244. package/templates/src/components/edit/panels/StyleWidgetPanel_update.tsx +308 -0
  245. package/templates/src/components/edit/state/SaveModal.tsx +811 -0
  246. package/templates/src/components/edit/state/StylesMemory.tsx +310 -0
  247. package/templates/src/components/edit/storyfragment/StoryFragmentConfigPanel.tsx +289 -0
  248. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +320 -0
  249. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +888 -0
  250. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +269 -0
  251. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_title.tsx +190 -0
  252. package/templates/src/components/edit/widgets/BeliefWidget.tsx +183 -0
  253. package/templates/src/components/edit/widgets/BunnyWidget.tsx +134 -0
  254. package/templates/src/components/edit/widgets/IdentifyAsWidget.tsx +193 -0
  255. package/templates/src/components/edit/widgets/SignupWidget.tsx +177 -0
  256. package/templates/src/components/edit/widgets/ToggleWidget.tsx +152 -0
  257. package/templates/src/components/edit/widgets/YouTubeWidget.tsx +65 -0
  258. package/templates/src/components/fields/ActionBuilderTimeSelector.tsx +353 -0
  259. package/templates/src/components/fields/ArtpackImage.tsx +480 -0
  260. package/templates/src/components/fields/BackgroundImage.tsx +530 -0
  261. package/templates/src/components/fields/BackgroundImageWrapper.tsx +192 -0
  262. package/templates/src/components/fields/BooleanParam.tsx +67 -0
  263. package/templates/src/components/fields/BunnyMomentSelector.tsx +56 -0
  264. package/templates/src/components/fields/ColorPickerCombo.tsx +284 -0
  265. package/templates/src/components/fields/ImageUpload.tsx +405 -0
  266. package/templates/src/components/fields/MultiParam.tsx +75 -0
  267. package/templates/src/components/fields/PaneBreakCollectionSelector.tsx +97 -0
  268. package/templates/src/components/fields/PaneBreakShapeSelector.tsx +134 -0
  269. package/templates/src/components/fields/SelectedTailwindClass.tsx +44 -0
  270. package/templates/src/components/fields/SingleParam.tsx +73 -0
  271. package/templates/src/components/fields/ViewportComboBox.tsx +252 -0
  272. package/templates/src/components/form/ActionBuilderField.tsx +282 -0
  273. package/templates/src/components/form/ActionBuilderSlugSelector.tsx +182 -0
  274. package/templates/src/components/form/BooleanToggle.tsx +94 -0
  275. package/templates/src/components/form/ColorPicker.tsx +153 -0
  276. package/templates/src/components/form/DateTimeInput.tsx +638 -0
  277. package/templates/src/components/form/EnumSelect.tsx +88 -0
  278. package/templates/src/components/form/FileUpload.tsx +465 -0
  279. package/templates/src/components/form/MagicPathBuilder.tsx +546 -0
  280. package/templates/src/components/form/NumberInput.tsx +101 -0
  281. package/templates/src/components/form/ParagraphArrayInput.tsx +207 -0
  282. package/templates/src/components/form/StringArrayInput.tsx +163 -0
  283. package/templates/src/components/form/StringInput.tsx +88 -0
  284. package/templates/src/components/form/UnsavedChangesBar.tsx +295 -0
  285. package/templates/src/components/form/advanced/APIConfigSection.tsx +69 -0
  286. package/templates/src/components/form/advanced/AuthConfigSection.tsx +97 -0
  287. package/templates/src/components/form/brand/BrandAssetsSection.tsx +93 -0
  288. package/templates/src/components/form/brand/BrandColorsSection.tsx +201 -0
  289. package/templates/src/components/form/brand/SEOSection.tsx +101 -0
  290. package/templates/src/components/form/brand/SiteConfigSection.tsx +61 -0
  291. package/templates/src/components/form/brand/SocialLinksSection.tsx +393 -0
  292. package/templates/src/components/profile/ProfileConsent.tsx +65 -0
  293. package/templates/src/components/profile/ProfileCreate.tsx +462 -0
  294. package/templates/src/components/profile/ProfileEdit.tsx +409 -0
  295. package/templates/src/components/profile/ProfileSwitch.tsx +255 -0
  296. package/templates/src/components/profile/ProfileUnlock.tsx +221 -0
  297. package/templates/src/components/storykeep/Dashboard.tsx +160 -0
  298. package/templates/src/components/storykeep/Dashboard_Activity.tsx +56 -0
  299. package/templates/src/components/storykeep/Dashboard_Advanced.tsx +165 -0
  300. package/templates/src/components/storykeep/Dashboard_Analytics.tsx +451 -0
  301. package/templates/src/components/storykeep/Dashboard_Branding.tsx +95 -0
  302. package/templates/src/components/storykeep/Dashboard_Content.tsx +191 -0
  303. package/templates/src/components/storykeep/controls/UsageCell.tsx +71 -0
  304. package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +378 -0
  305. package/templates/src/components/storykeep/controls/content/BeliefTable.tsx +329 -0
  306. package/templates/src/components/storykeep/controls/content/ContentBrowser.tsx +385 -0
  307. package/templates/src/components/storykeep/controls/content/ContentSummary.tsx +149 -0
  308. package/templates/src/components/storykeep/controls/content/KnownResourceForm.tsx +397 -0
  309. package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +260 -0
  310. package/templates/src/components/storykeep/controls/content/ManageContent.tsx +439 -0
  311. package/templates/src/components/storykeep/controls/content/MenuForm.tsx +239 -0
  312. package/templates/src/components/storykeep/controls/content/MenuTable.tsx +332 -0
  313. package/templates/src/components/storykeep/controls/content/ResourceBulkIngest.tsx +724 -0
  314. package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +355 -0
  315. package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +222 -0
  316. package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +482 -0
  317. package/templates/src/components/storykeep/state/BrandingWrapper.tsx +42 -0
  318. package/templates/src/components/storykeep/state/FetchAnalytics.tsx +350 -0
  319. package/templates/src/components/storykeep/widgets/ResponsiveLine.tsx +319 -0
  320. package/templates/src/components/storykeep/widgets/Wizard.tsx +278 -0
  321. package/templates/src/components/tenant/RegistrationForm.tsx +447 -0
  322. package/templates/src/components/widgets/BunnyVideoHero.astro +775 -0
  323. package/templates/src/components/widgets/Impression.tsx +102 -0
  324. package/templates/src/components/widgets/ImpressionWrapper.tsx +214 -0
  325. package/templates/src/constants/beliefs.ts +61 -0
  326. package/templates/src/constants/brandThemes.ts +133 -0
  327. package/templates/src/constants/prompts.json +55 -0
  328. package/templates/src/constants/shapes.ts +556 -0
  329. package/templates/src/constants/stopWords.ts +116 -0
  330. package/templates/src/constants/tailwindColors.json +344 -0
  331. package/templates/src/constants.ts +274 -0
  332. package/templates/src/hooks/useFormState.ts +203 -0
  333. package/templates/src/layouts/Layout.astro +290 -0
  334. package/templates/src/lib/session.ts +126 -0
  335. package/templates/src/lib/storyData.ts +56 -0
  336. package/templates/src/middleware.ts +52 -0
  337. package/templates/src/pages/404.astro +54 -0
  338. package/templates/src/pages/[...slug]/edit.astro +216 -0
  339. package/templates/src/pages/[...slug].astro +148 -0
  340. package/templates/src/pages/api/auth/decode.ts +101 -0
  341. package/templates/src/pages/api/auth/login.ts +122 -0
  342. package/templates/src/pages/api/auth/logout.ts +37 -0
  343. package/templates/src/pages/api/auth/profile.ts +76 -0
  344. package/templates/src/pages/api/orphan-analysis.ts +106 -0
  345. package/templates/src/pages/api/tailwind.ts +116 -0
  346. package/templates/src/pages/collections/[param1].astro +65 -0
  347. package/templates/src/pages/context/[...contextSlug]/edit.astro +207 -0
  348. package/templates/src/pages/context/[...contextSlug].astro +161 -0
  349. package/templates/src/pages/llms.txt.ts +122 -0
  350. package/templates/src/pages/maint.astro +183 -0
  351. package/templates/src/pages/media/[...slug].astro +67 -0
  352. package/templates/src/pages/robots.txt.ts +36 -0
  353. package/templates/src/pages/sandbox/activate.astro +258 -0
  354. package/templates/src/pages/sandbox/register.astro +44 -0
  355. package/templates/src/pages/sandbox/success.astro +179 -0
  356. package/templates/src/pages/sitemap.xml.ts +119 -0
  357. package/templates/src/pages/storykeep/advanced.astro +69 -0
  358. package/templates/src/pages/storykeep/branding.astro +57 -0
  359. package/templates/src/pages/storykeep/content.astro +71 -0
  360. package/templates/src/pages/storykeep/init.astro +36 -0
  361. package/templates/src/pages/storykeep/login.astro +266 -0
  362. package/templates/src/pages/storykeep/logout.astro +84 -0
  363. package/templates/src/pages/storykeep/profile.astro +98 -0
  364. package/templates/src/pages/storykeep.astro +81 -0
  365. package/templates/src/stores/analytics.ts +171 -0
  366. package/templates/src/stores/backend.ts +16 -0
  367. package/templates/src/stores/navigation.ts +149 -0
  368. package/templates/src/stores/nodes.ts +2390 -0
  369. package/templates/src/stores/nodesHistory.ts +85 -0
  370. package/templates/src/stores/notificationSystem.ts +41 -0
  371. package/templates/src/stores/orphanAnalysis.ts +409 -0
  372. package/templates/src/stores/storykeep.ts +247 -0
  373. package/templates/src/types/astro.ts +86 -0
  374. package/templates/src/types/compositorTypes.ts +456 -0
  375. package/templates/src/types/formTypes.ts +281 -0
  376. package/templates/src/types/multiTenant.ts +77 -0
  377. package/templates/src/types/nodeProps.ts +66 -0
  378. package/templates/src/types/tractstack.ts +445 -0
  379. package/templates/src/utils/aai/getTitleSlug.ts +72 -0
  380. package/templates/src/utils/actions/actionButton.ts +101 -0
  381. package/templates/src/utils/actions/lispLexer.ts +57 -0
  382. package/templates/src/utils/actions/preParse_Action.ts +85 -0
  383. package/templates/src/utils/actions/preParse_Bunny.ts +50 -0
  384. package/templates/src/utils/actions/preParse_Clicked.ts +87 -0
  385. package/templates/src/utils/actions/preParse_Impression.ts +71 -0
  386. package/templates/src/utils/api/advancedConfig.ts +66 -0
  387. package/templates/src/utils/api/advancedHelpers.ts +134 -0
  388. package/templates/src/utils/api/beliefConfig.ts +87 -0
  389. package/templates/src/utils/api/beliefHelpers.ts +196 -0
  390. package/templates/src/utils/api/brandConfig.ts +126 -0
  391. package/templates/src/utils/api/brandHelpers.ts +155 -0
  392. package/templates/src/utils/api/fileHelpers.ts +306 -0
  393. package/templates/src/utils/api/menuConfig.ts +57 -0
  394. package/templates/src/utils/api/menuHelpers.ts +156 -0
  395. package/templates/src/utils/api/resourceConfig.ts +158 -0
  396. package/templates/src/utils/api/resourceHelpers.ts +72 -0
  397. package/templates/src/utils/api/tenantConfig.ts +97 -0
  398. package/templates/src/utils/api/tenantHelpers.ts +172 -0
  399. package/templates/src/utils/api.ts +183 -0
  400. package/templates/src/utils/auth.ts +150 -0
  401. package/templates/src/utils/backend.ts +243 -0
  402. package/templates/src/utils/compositor/TemplateMarkdowns.ts +118 -0
  403. package/templates/src/utils/compositor/TemplateNodes.ts +138 -0
  404. package/templates/src/utils/compositor/TemplatePanes.ts +100 -0
  405. package/templates/src/utils/compositor/allowInsert.ts +100 -0
  406. package/templates/src/utils/compositor/domHelpers.ts +37 -0
  407. package/templates/src/utils/compositor/handleClickEvent.ts +131 -0
  408. package/templates/src/utils/compositor/nodesHelper.ts +491 -0
  409. package/templates/src/utils/compositor/nodesMarkdownGenerator.ts +292 -0
  410. package/templates/src/utils/compositor/processMarkdown.ts +431 -0
  411. package/templates/src/utils/compositor/reduceNodesClassNames.ts +192 -0
  412. package/templates/src/utils/compositor/tailwindClasses.ts +1795 -0
  413. package/templates/src/utils/compositor/tailwindColors.ts +227 -0
  414. package/templates/src/utils/compositor/templateMarkdownStyles.ts +1265 -0
  415. package/templates/src/utils/compositor/typeGuards.ts +193 -0
  416. package/templates/src/utils/etl/extractor.ts +119 -0
  417. package/templates/src/utils/etl/index.ts +88 -0
  418. package/templates/src/utils/etl/loader.ts +36 -0
  419. package/templates/src/utils/etl/transformer.ts +286 -0
  420. package/templates/src/utils/helpers.ts +435 -0
  421. package/templates/src/utils/layout.ts +209 -0
  422. package/templates/src/utils/profileStorage.ts +306 -0
  423. package/templates/src/utils/useInterval.ts +27 -0
  424. package/templates/tailwind.config.cjs +169 -0
  425. package/utils/create-resolver.ts +10 -0
  426. package/utils/inject-files.ts +2140 -0
  427. package/utils/validate-config.ts +43 -0
@@ -0,0 +1,87 @@
1
+ import { TractStackAPI } from '../api';
2
+ import { convertToBackendFormat, convertToLocalState } from './beliefHelpers';
3
+ import type { BeliefNode, BeliefNodeState } from '@/types/tractstack';
4
+
5
+ /**
6
+ * Save an existing belief
7
+ */
8
+ export async function saveBelief(
9
+ tenantId: string,
10
+ beliefState: BeliefNodeState
11
+ ): Promise<BeliefNode> {
12
+ const api = new TractStackAPI(tenantId);
13
+ const beliefData = convertToBackendFormat(beliefState);
14
+
15
+ const response = await api.put(
16
+ `/api/v1/nodes/beliefs/${beliefState.id}`,
17
+ beliefData
18
+ );
19
+ return response.data as BeliefNode;
20
+ }
21
+
22
+ /**
23
+ * Create a new belief
24
+ */
25
+ export async function createBelief(
26
+ tenantId: string,
27
+ beliefState: BeliefNodeState
28
+ ): Promise<BeliefNode> {
29
+ const api = new TractStackAPI(tenantId);
30
+ const beliefData = convertToBackendFormat(beliefState);
31
+
32
+ const response = await api.post('/api/v1/nodes/beliefs/create', beliefData);
33
+ return response.data as BeliefNode;
34
+ }
35
+
36
+ /**
37
+ * Delete a belief
38
+ */
39
+ export async function deleteBelief(
40
+ tenantId: string,
41
+ beliefId: string
42
+ ): Promise<void> {
43
+ const api = new TractStackAPI(tenantId);
44
+ await api.request(`/api/v1/nodes/beliefs/${beliefId}`, { method: 'DELETE' });
45
+ }
46
+
47
+ /**
48
+ * Get a belief by ID
49
+ */
50
+ export async function getBeliefById(
51
+ tenantId: string,
52
+ beliefId: string
53
+ ): Promise<BeliefNode> {
54
+ const api = new TractStackAPI(tenantId);
55
+ const response = await api.get(`/api/v1/nodes/beliefs/${beliefId}`);
56
+ return response.data as BeliefNode;
57
+ }
58
+
59
+ /**
60
+ * Main save workflow with state update
61
+ * Following the exact pattern from menuConfig.ts
62
+ */
63
+ export async function saveBeliefWithStateUpdate(
64
+ tenantId: string,
65
+ currentState: BeliefNodeState
66
+ ): Promise<BeliefNodeState> {
67
+ try {
68
+ let savedBelief: BeliefNode;
69
+
70
+ // Determine if this is a create or update operation
71
+ const isCreate = !currentState.id || currentState.id === '';
72
+
73
+ if (isCreate) {
74
+ // Generate temporary ID for create (backend will assign real ID)
75
+ const tempState = { ...currentState, id: crypto.randomUUID() };
76
+ savedBelief = await createBelief(tenantId, tempState);
77
+ } else {
78
+ savedBelief = await saveBelief(tenantId, currentState);
79
+ }
80
+
81
+ // Convert the saved belief back to state format
82
+ return convertToLocalState(savedBelief);
83
+ } catch (error) {
84
+ console.error('Belief save failed:', error);
85
+ throw error;
86
+ }
87
+ }
@@ -0,0 +1,196 @@
1
+ import type {
2
+ BeliefNode,
3
+ BeliefNodeState,
4
+ FieldErrors,
5
+ } from '@/types/tractstack';
6
+
7
+ /**
8
+ * Convert backend BeliefNode to frontend BeliefNodeState
9
+ */
10
+ export function convertToLocalState(beliefNode: BeliefNode): BeliefNodeState {
11
+ return {
12
+ id: beliefNode.id,
13
+ title: beliefNode.title,
14
+ slug: beliefNode.slug,
15
+ scale: beliefNode.scale,
16
+ customValues: beliefNode.customValues || [],
17
+ };
18
+ }
19
+
20
+ /**
21
+ * Convert frontend BeliefNodeState to backend BeliefNode format
22
+ */
23
+ export function convertToBackendFormat(state: BeliefNodeState): BeliefNode {
24
+ return {
25
+ id: state.id,
26
+ title: state.title,
27
+ slug: state.slug,
28
+ scale: state.scale,
29
+ customValues:
30
+ state.customValues.length > 0 ? state.customValues : undefined,
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Validate belief node state
36
+ */
37
+ export function validateBeliefNode(state: BeliefNodeState): FieldErrors {
38
+ const errors: FieldErrors = {};
39
+
40
+ // Validate title
41
+ if (!state.title?.trim()) {
42
+ errors.title = 'Title is required';
43
+ }
44
+
45
+ // Validate slug
46
+ if (!state.slug?.trim()) {
47
+ errors.slug = 'Slug is required';
48
+ }
49
+
50
+ // Validate scale
51
+ if (!state.scale?.trim()) {
52
+ errors.scale = 'Scale is required';
53
+ }
54
+
55
+ // Validate custom values if scale is custom
56
+ if (state.scale === 'custom') {
57
+ if (!state.customValues || state.customValues.length === 0) {
58
+ errors.customValues =
59
+ 'At least one custom value is required for custom scale';
60
+ } else {
61
+ state.customValues.forEach((value, index) => {
62
+ if (!value?.trim()) {
63
+ errors[`customValues.${index}`] = 'Custom value cannot be empty';
64
+ }
65
+ });
66
+ }
67
+ }
68
+
69
+ return errors;
70
+ }
71
+
72
+ /**
73
+ * State interceptor for form state management
74
+ */
75
+ export function beliefStateIntercept(
76
+ state: BeliefNodeState,
77
+ field: keyof BeliefNodeState,
78
+ value: any
79
+ ): BeliefNodeState {
80
+ const newState = { ...state };
81
+
82
+ switch (field) {
83
+ case 'title':
84
+ newState.title = value || '';
85
+ break;
86
+ case 'slug':
87
+ newState.slug = value || '';
88
+ break;
89
+ case 'scale':
90
+ newState.scale = value || '';
91
+ // Clear custom values when scale changes away from custom
92
+ if (value !== 'custom') {
93
+ newState.customValues = [];
94
+ }
95
+ break;
96
+ case 'customValues':
97
+ newState.customValues = value || [];
98
+ break;
99
+ default:
100
+ (newState as any)[field] = value;
101
+ }
102
+
103
+ return newState;
104
+ }
105
+
106
+ /**
107
+ * Add a new custom value to the state
108
+ */
109
+ export function addCustomValue(
110
+ state: BeliefNodeState,
111
+ value: string
112
+ ): BeliefNodeState {
113
+ if (!value.trim()) return state;
114
+
115
+ return {
116
+ ...state,
117
+ customValues: [...state.customValues, value.trim()],
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Remove a custom value from the state
123
+ */
124
+ export function removeCustomValue(
125
+ state: BeliefNodeState,
126
+ index: number
127
+ ): BeliefNodeState {
128
+ return {
129
+ ...state,
130
+ customValues: state.customValues.filter((_, i) => i !== index),
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Update a specific custom value in the state
136
+ */
137
+ export function updateCustomValue(
138
+ state: BeliefNodeState,
139
+ index: number,
140
+ value: string
141
+ ): BeliefNodeState {
142
+ const newCustomValues = [...state.customValues];
143
+ newCustomValues[index] = value;
144
+
145
+ return {
146
+ ...state,
147
+ customValues: newCustomValues,
148
+ };
149
+ }
150
+
151
+ /**
152
+ * Scale options for the belief form
153
+ */
154
+ export const SCALE_OPTIONS = [
155
+ { value: 'likert', label: 'Likert Scale (1-5)' },
156
+ { value: 'agreement', label: 'Agreement (Agree/Disagree)' },
157
+ { value: 'interest', label: 'Interest (Interested/Not Interested)' },
158
+ { value: 'yn', label: 'Yes/No' },
159
+ { value: 'tf', label: 'True/False' },
160
+ { value: 'custom', label: 'Custom Values' },
161
+ ];
162
+
163
+ /**
164
+ * Get scale preview data for displaying scale options
165
+ */
166
+ export function getScalePreview(scale: string) {
167
+ const scalePreviewData: {
168
+ [key: string]: Array<{ id: number; name: string; color: string }>;
169
+ } = {
170
+ likert: [
171
+ { id: 1, name: 'Strongly agree', color: 'bg-teal-400' },
172
+ { id: 2, name: 'Agree', color: 'bg-lime-400' },
173
+ { id: 3, name: 'Neither agree nor disagree', color: 'bg-slate-200' },
174
+ { id: 4, name: 'Disagree', color: 'bg-amber-400' },
175
+ { id: 5, name: 'Strongly disagree', color: 'bg-red-400' },
176
+ ],
177
+ agreement: [
178
+ { id: 1, name: 'Agree', color: 'bg-lime-400' },
179
+ { id: 2, name: 'Disagree', color: 'bg-amber-400' },
180
+ ],
181
+ interest: [
182
+ { id: 1, name: 'Interested', color: 'bg-lime-400' },
183
+ { id: 2, name: 'Not Interested', color: 'bg-amber-400' },
184
+ ],
185
+ yn: [
186
+ { id: 1, name: 'Yes', color: 'bg-lime-400' },
187
+ { id: 2, name: 'No', color: 'bg-amber-400' },
188
+ ],
189
+ tf: [
190
+ { id: 1, name: 'True', color: 'bg-lime-400' },
191
+ { id: 2, name: 'False', color: 'bg-amber-400' },
192
+ ],
193
+ };
194
+
195
+ return scalePreviewData[scale] || null;
196
+ }
@@ -0,0 +1,126 @@
1
+ import { TractStackAPI } from '../api';
2
+ import { convertToLocalState, convertToBackendFormat } from './brandHelpers';
3
+ import type { BrandConfig, BrandConfigState } from '@/types/tractstack';
4
+
5
+ export async function saveBrandConfig(
6
+ tenantId: string,
7
+ brandConfig: BrandConfig
8
+ ): Promise<BrandConfig> {
9
+ const api = new TractStackAPI(tenantId);
10
+ try {
11
+ const response = await api.put('/api/v1/config/brand', {
12
+ ...brandConfig,
13
+ SITE_INIT: true,
14
+ });
15
+ if (!response.success) {
16
+ throw new Error(response.error || 'Failed to save brand configuration');
17
+ }
18
+ return response.data;
19
+ } catch (error) {
20
+ // If it's a network error (backend down), redirect to maintenance
21
+ if (error instanceof TypeError && error.message.includes('fetch failed')) {
22
+ window.location.href = `/maint?from=${encodeURIComponent(window.location.pathname)}`;
23
+ throw error; // Still throw so caller knows something failed
24
+ }
25
+ throw error;
26
+ }
27
+ }
28
+
29
+ export async function getBrandConfig(tenantId: string): Promise<BrandConfig> {
30
+ const api = new TractStackAPI(tenantId);
31
+ try {
32
+ const response = await api.get('/api/v1/config/brand');
33
+ if (!response.success) {
34
+ // Check if it's a backend down scenario based on error message
35
+ if (
36
+ response.error &&
37
+ (response.error.includes('Network error') ||
38
+ response.error.includes('fetch failed'))
39
+ ) {
40
+ // Return empty/default config when backend is down
41
+ return {
42
+ SITE_INIT: false,
43
+ WORDMARK_MODE: '',
44
+ BRAND_COLOURS: '',
45
+ OPEN_DEMO: false,
46
+ HOME_SLUG: 'hello',
47
+ TRACTSTACK_HOME_SLUG: 'HELLO',
48
+ THEME: 'Default',
49
+ SOCIALS: '',
50
+ LOGO: '',
51
+ WORDMARK: '',
52
+ OG: '',
53
+ OGLOGO: '',
54
+ FAVICON: '',
55
+ SITE_URL: '',
56
+ SLOGAN: '',
57
+ FOOTER: '',
58
+ OGTITLE: '',
59
+ OGAUTHOR: '',
60
+ OGDESC: '',
61
+ GTAG: '',
62
+ STYLES_VER: 1,
63
+ KNOWN_RESOURCES: {},
64
+ HAS_AAI: false,
65
+ } as BrandConfig;
66
+ }
67
+ throw new Error(response.error || 'Failed to get brand configuration');
68
+ }
69
+ return response.data;
70
+ } catch (error) {
71
+ // If it's a network error (backend down), return default config
72
+ if (error instanceof TypeError && error.message.includes('fetch failed')) {
73
+ return {
74
+ SITE_INIT: false,
75
+ WORDMARK_MODE: '',
76
+ BRAND_COLOURS: '',
77
+ OPEN_DEMO: false,
78
+ HOME_SLUG: 'hello',
79
+ TRACTSTACK_HOME_SLUG: 'HELLO',
80
+ THEME: 'Default',
81
+ SOCIALS: '',
82
+ LOGO: '',
83
+ WORDMARK: '',
84
+ OG: '',
85
+ OGLOGO: '',
86
+ FAVICON: '',
87
+ SITE_URL: '',
88
+ SLOGAN: '',
89
+ FOOTER: '',
90
+ OGTITLE: '',
91
+ OGAUTHOR: '',
92
+ OGDESC: '',
93
+ GTAG: '',
94
+ STYLES_VER: 1,
95
+ KNOWN_RESOURCES: {},
96
+ HAS_AAI: false,
97
+ } as BrandConfig;
98
+ }
99
+ throw error;
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Handle complete brand config save workflow including state updates
105
+ */
106
+ export async function saveBrandConfigWithStateUpdate(
107
+ tenantId: string,
108
+ currentState: BrandConfigState
109
+ ): Promise<BrandConfigState> {
110
+ const backendFormat = convertToBackendFormat(currentState);
111
+
112
+ try {
113
+ // Send COMPLETE config, not just changed fields
114
+ await saveBrandConfig(tenantId, backendFormat);
115
+
116
+ // Get the complete updated config from backend
117
+ const freshConfig = await getBrandConfig(tenantId);
118
+
119
+ // Convert updated config back to local state format
120
+ const newLocalState = convertToLocalState(freshConfig);
121
+
122
+ return newLocalState;
123
+ } catch (error) {
124
+ throw error;
125
+ }
126
+ }
@@ -0,0 +1,155 @@
1
+ import type { FieldErrors } from '@/hooks/useFormState';
2
+ import { getThemeColors } from '@/constants/brandThemes';
3
+ import type { BrandConfig, BrandConfigState } from '@/types/tractstack';
4
+
5
+ /**
6
+ * Convert backend BrandConfig to local state format
7
+ * Splits CSV strings into arrays for easier client-side manipulation
8
+ * Provides safe defaults for undefined values to prevent controlled input warnings
9
+ */
10
+ export function convertToLocalState(
11
+ brandConfig: BrandConfig
12
+ ): BrandConfigState {
13
+ return {
14
+ siteInit: brandConfig.SITE_INIT ?? false,
15
+ wordmarkMode: brandConfig.WORDMARK_MODE ?? '',
16
+ brandColours: brandConfig.BRAND_COLOURS
17
+ ? brandConfig.BRAND_COLOURS.split(',').map((color) => color.trim())
18
+ : getThemeColors('Default'), // Fallback to Default theme
19
+ openDemo: brandConfig.OPEN_DEMO ?? false,
20
+ homeSlug: brandConfig.HOME_SLUG ?? 'hello',
21
+ tractstackHomeSlug: brandConfig.TRACTSTACK_HOME_SLUG ?? 'HELLO',
22
+ theme: brandConfig.THEME ?? 'light-bold',
23
+ socials: brandConfig.SOCIALS
24
+ ? brandConfig.SOCIALS.split(',').filter((social) => social.trim())
25
+ : [],
26
+ logo: brandConfig.LOGO ?? '',
27
+ wordmark: brandConfig.WORDMARK ?? '',
28
+ og: brandConfig.OG ?? '',
29
+ oglogo: brandConfig.OGLOGO ?? '',
30
+ favicon: brandConfig.FAVICON ?? '',
31
+ siteUrl: brandConfig.SITE_URL ?? '',
32
+ slogan: brandConfig.SLOGAN ?? '',
33
+ footer: brandConfig.FOOTER ?? '',
34
+ ogtitle: brandConfig.OGTITLE ?? '',
35
+ ogauthor: brandConfig.OGAUTHOR ?? '',
36
+ ogdesc: brandConfig.OGDESC ?? '',
37
+ gtag: brandConfig.GTAG ?? '',
38
+ stylesVer: brandConfig.STYLES_VER ?? 1,
39
+ knownResources: brandConfig.KNOWN_RESOURCES ?? {},
40
+ hasAAI: brandConfig.HAS_AAI ?? false,
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Convert local state back to backend BrandConfig format
46
+ * Joins arrays back into CSV/pipe-separated strings
47
+ */
48
+ export function convertToBackendFormat(
49
+ localState: BrandConfigState
50
+ ): BrandConfig {
51
+ return {
52
+ SITE_INIT: localState.siteInit,
53
+ WORDMARK_MODE: localState.wordmarkMode,
54
+ BRAND_COLOURS: localState.brandColours.join(','),
55
+ OPEN_DEMO: localState.openDemo,
56
+ STYLES_VER: localState.stylesVer,
57
+ HOME_SLUG: localState.homeSlug,
58
+ TRACTSTACK_HOME_SLUG: localState.tractstackHomeSlug,
59
+ THEME: localState.theme,
60
+ SOCIALS: localState.socials.join(','),
61
+ SITE_URL: localState.siteUrl,
62
+ SLOGAN: localState.slogan,
63
+ FOOTER: localState.footer,
64
+ OGTITLE: localState.ogtitle,
65
+ OGAUTHOR: localState.ogauthor,
66
+ OGDESC: localState.ogdesc,
67
+ GTAG: localState.gtag,
68
+ KNOWN_RESOURCES: localState.knownResources,
69
+ HAS_AAI: localState.hasAAI,
70
+
71
+ // ALWAYS send asset paths (current state)
72
+ LOGO: localState.logo,
73
+ WORDMARK: localState.wordmark,
74
+ OG: localState.og,
75
+ OGLOGO: localState.oglogo,
76
+ FAVICON: localState.favicon,
77
+
78
+ // Only send base64 when uploading
79
+ LOGO_BASE64: localState.logoBase64,
80
+ WORDMARK_BASE64: localState.wordmarkBase64,
81
+ OG_BASE64: localState.ogBase64,
82
+ OGLOGO_BASE64: localState.oglogoBase64,
83
+ FAVICON_BASE64: localState.faviconBase64,
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Validation function for brand configuration
89
+ * Returns field-level error messages
90
+ */
91
+ export function validateBrandConfig(state: BrandConfigState): FieldErrors {
92
+ const errors: FieldErrors = {};
93
+
94
+ // Validate required fields
95
+ if (!state.siteUrl?.trim()) {
96
+ errors.siteUrl = 'Site URL is required';
97
+ } else if (!isValidUrl(state.siteUrl)) {
98
+ errors.siteUrl = 'Please enter a valid URL';
99
+ }
100
+
101
+ if (!state.slogan?.trim()) {
102
+ errors.slogan = 'Site slogan is required';
103
+ }
104
+ if (!state.footer?.trim()) {
105
+ errors.footer = 'Site footer is required';
106
+ }
107
+
108
+ // Validate brand colors (must have exactly 8)
109
+ if (!state.brandColours || state.brandColours.length !== 8) {
110
+ errors.brandColours = 'Must have exactly 8 brand colors';
111
+ } else {
112
+ // Validate each color is a valid hex
113
+ const invalidColors = state.brandColours.filter(
114
+ (color) => !isValidHexColor(color)
115
+ );
116
+ if (invalidColors.length > 0) {
117
+ errors.brandColours = `Invalid hex colors: ${invalidColors.join(', ')}`;
118
+ }
119
+ }
120
+
121
+ // Validate social links format
122
+ if (state.socials && state.socials.length > 0) {
123
+ const invalidSocials = state.socials.filter((social) => {
124
+ const parts = social.split('|');
125
+ return parts.length !== 2 || !parts[0].trim() || !isValidUrl(parts[1]);
126
+ });
127
+
128
+ if (invalidSocials.length > 0) {
129
+ errors.socials = 'Social links must be in format "platform|url"';
130
+ }
131
+ }
132
+
133
+ return errors;
134
+ }
135
+
136
+ /**
137
+ * Helper function to validate URLs
138
+ */
139
+ function isValidUrl(url: string): boolean {
140
+ try {
141
+ new URL(url);
142
+ return true;
143
+ } catch {
144
+ return false;
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Helper function to validate hex colors
150
+ * Accepts both 3 and 6 character hex codes (with or without #)
151
+ */
152
+ function isValidHexColor(color: string): boolean {
153
+ const hex = color.startsWith('#') ? color.slice(1) : color;
154
+ return /^([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(hex);
155
+ }