@morphika/andami 0.1.2

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 (319) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/admin/assets.ts +4 -0
  4. package/admin/database.ts +4 -0
  5. package/admin/index.ts +6 -0
  6. package/admin/login.ts +4 -0
  7. package/admin/navigation.ts +4 -0
  8. package/admin/pages-editor.ts +4 -0
  9. package/admin/pages.ts +4 -0
  10. package/admin/projects-editor.ts +4 -0
  11. package/admin/projects.ts +4 -0
  12. package/admin/settings.ts +4 -0
  13. package/admin/setup.ts +4 -0
  14. package/admin/storage.ts +4 -0
  15. package/admin/styles.ts +4 -0
  16. package/app/(site)/[slug]/loading.tsx +20 -0
  17. package/app/(site)/[slug]/page.tsx +83 -0
  18. package/app/(site)/error.tsx +32 -0
  19. package/app/(site)/layout.tsx +53 -0
  20. package/app/(site)/loading.tsx +20 -0
  21. package/app/(site)/not-found.tsx +41 -0
  22. package/app/(site)/page.tsx +43 -0
  23. package/app/(site)/preview/page.tsx +99 -0
  24. package/app/(site)/work/[slug]/loading.tsx +23 -0
  25. package/app/(site)/work/[slug]/page.tsx +84 -0
  26. package/app/admin/assets/page.tsx +573 -0
  27. package/app/admin/database/page.tsx +302 -0
  28. package/app/admin/error.tsx +53 -0
  29. package/app/admin/layout.tsx +273 -0
  30. package/app/admin/login/page.tsx +88 -0
  31. package/app/admin/navigation/page.tsx +157 -0
  32. package/app/admin/page.tsx +17 -0
  33. package/app/admin/pages/[slug]/page.tsx +849 -0
  34. package/app/admin/pages/page.tsx +588 -0
  35. package/app/admin/projects/[slug]/page.tsx +3 -0
  36. package/app/admin/projects/page.tsx +669 -0
  37. package/app/admin/settings/page.tsx +132 -0
  38. package/app/admin/setup/page.tsx +64 -0
  39. package/app/admin/storage/page.tsx +518 -0
  40. package/app/admin/styles/page.tsx +243 -0
  41. package/app/api/admin/assets/file/route.ts +81 -0
  42. package/app/api/admin/assets/health/route.ts +170 -0
  43. package/app/api/admin/assets/register/route.ts +163 -0
  44. package/app/api/admin/assets/registry/route.ts +98 -0
  45. package/app/api/admin/assets/relink/confirm/route.ts +242 -0
  46. package/app/api/admin/assets/relink/route.ts +202 -0
  47. package/app/api/admin/assets/scan/route.ts +271 -0
  48. package/app/api/admin/auth/route.ts +160 -0
  49. package/app/api/admin/custom-sections/[slug]/route.ts +159 -0
  50. package/app/api/admin/custom-sections/route.ts +127 -0
  51. package/app/api/admin/database/route.ts +53 -0
  52. package/app/api/admin/pages/[slug]/duplicate/route.ts +91 -0
  53. package/app/api/admin/pages/[slug]/route.ts +617 -0
  54. package/app/api/admin/pages/[slug]/set-home/route.ts +76 -0
  55. package/app/api/admin/pages/route.ts +129 -0
  56. package/app/api/admin/preview/route.ts +53 -0
  57. package/app/api/admin/r2/connect/route.ts +181 -0
  58. package/app/api/admin/r2/delete/route.ts +198 -0
  59. package/app/api/admin/r2/disconnect/route.ts +42 -0
  60. package/app/api/admin/r2/rename/route.ts +265 -0
  61. package/app/api/admin/r2/status/route.ts +106 -0
  62. package/app/api/admin/r2/upload-url/route.ts +148 -0
  63. package/app/api/admin/revalidate/route.ts +55 -0
  64. package/app/api/admin/settings/route.ts +279 -0
  65. package/app/api/admin/setup/complete/route.ts +51 -0
  66. package/app/api/admin/setup/route.ts +118 -0
  67. package/app/api/admin/storage/switch/route.ts +117 -0
  68. package/app/api/admin/styles/fonts/route.ts +97 -0
  69. package/app/api/admin/styles/route.ts +304 -0
  70. package/app/api/assets/[...path]/route.ts +98 -0
  71. package/app/api/custom-sections/[id]/route.ts +43 -0
  72. package/app/api/draft-mode/disable/route.ts +10 -0
  73. package/app/api/draft-mode/enable/route.ts +26 -0
  74. package/app/api/projects/route.ts +42 -0
  75. package/app/api/styles/route.ts +88 -0
  76. package/app/favicon.ico +0 -0
  77. package/app/globals.css +7 -0
  78. package/app/layout.tsx +53 -0
  79. package/app/robots.ts +17 -0
  80. package/app/sitemap.ts +48 -0
  81. package/app/studio/[[...index]]/page.tsx +8 -0
  82. package/components/admin/MetadataEditor.tsx +173 -0
  83. package/components/admin/PublishToggle.tsx +130 -0
  84. package/components/admin/icons.tsx +40 -0
  85. package/components/admin/nav-builder/NavBuilder.tsx +182 -0
  86. package/components/admin/nav-builder/NavBuilderGrid.tsx +326 -0
  87. package/components/admin/nav-builder/NavGeneralSettings.tsx +275 -0
  88. package/components/admin/nav-builder/NavGridCell.tsx +48 -0
  89. package/components/admin/nav-builder/NavGridItem.tsx +189 -0
  90. package/components/admin/nav-builder/NavItemSettings.tsx +288 -0
  91. package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -0
  92. package/components/admin/nav-builder/NavLivePreview.tsx +125 -0
  93. package/components/admin/nav-builder/NavSettingsFields.tsx +248 -0
  94. package/components/admin/nav-builder/NavSettingsPanel.tsx +127 -0
  95. package/components/admin/nav-builder/index.ts +10 -0
  96. package/components/admin/nav-builder/nav-builder-utils.ts +238 -0
  97. package/components/admin/setup-wizard/BrandingStep.tsx +218 -0
  98. package/components/admin/setup-wizard/DatabaseStep.tsx +331 -0
  99. package/components/admin/setup-wizard/DoneStep.tsx +187 -0
  100. package/components/admin/setup-wizard/SetupWizard.tsx +166 -0
  101. package/components/admin/setup-wizard/StorageStep.tsx +308 -0
  102. package/components/admin/setup-wizard/WelcomeStep.tsx +96 -0
  103. package/components/admin/setup-wizard/index.ts +9 -0
  104. package/components/admin/styles/ColorsEditor.tsx +214 -0
  105. package/components/admin/styles/FontsEditor.tsx +258 -0
  106. package/components/admin/styles/GridLayoutEditor.tsx +292 -0
  107. package/components/admin/styles/LinksButtonsEditor.tsx +120 -0
  108. package/components/admin/styles/TypographyEditor.tsx +266 -0
  109. package/components/admin/styles/index.ts +9 -0
  110. package/components/admin/styles/shared.tsx +68 -0
  111. package/components/blocks/BlockRenderer.tsx +404 -0
  112. package/components/blocks/ButtonBlockRenderer.tsx +52 -0
  113. package/components/blocks/CoverBlockRenderer.tsx +239 -0
  114. package/components/blocks/CustomSectionInstanceRenderer.tsx +82 -0
  115. package/components/blocks/EnterAnimationWrapper.tsx +140 -0
  116. package/components/blocks/HoverAnimationWrapper.tsx +308 -0
  117. package/components/blocks/ImageBlockRenderer.tsx +61 -0
  118. package/components/blocks/ImageGridBlockRenderer.tsx +545 -0
  119. package/components/blocks/PageBackground.tsx +28 -0
  120. package/components/blocks/PageNavAnimation.tsx +35 -0
  121. package/components/blocks/PageNavColor.tsx +24 -0
  122. package/components/blocks/PageRenderer.tsx +142 -0
  123. package/components/blocks/ParallaxGroupRenderer.tsx +448 -0
  124. package/components/blocks/ParallaxSlideRenderer.tsx +175 -0
  125. package/components/blocks/ProjectGridBlockRenderer.tsx +556 -0
  126. package/components/blocks/SectionRenderer.tsx +170 -0
  127. package/components/blocks/SectionV2Renderer.tsx +330 -0
  128. package/components/blocks/ShaderCanvas.tsx +392 -0
  129. package/components/blocks/SpacerBlockRenderer.tsx +17 -0
  130. package/components/blocks/TextBlockRenderer.tsx +87 -0
  131. package/components/blocks/TypewriterRichText.tsx +464 -0
  132. package/components/blocks/TypewriterWrapper.tsx +149 -0
  133. package/components/blocks/VideoBlockRenderer.tsx +304 -0
  134. package/components/blocks/index.ts +2 -0
  135. package/components/builder/AssetBrowser.tsx +2 -0
  136. package/components/builder/BlockLivePreview.tsx +101 -0
  137. package/components/builder/BlockTypePicker.tsx +178 -0
  138. package/components/builder/BuilderCanvas.tsx +354 -0
  139. package/components/builder/CanvasMinimap.tsx +200 -0
  140. package/components/builder/CanvasToolbar.tsx +202 -0
  141. package/components/builder/ColorPicker.tsx +243 -0
  142. package/components/builder/ColorSwatchPicker.tsx +274 -0
  143. package/components/builder/ColumnDragContext.tsx +51 -0
  144. package/components/builder/ColumnDragOverlay.tsx +110 -0
  145. package/components/builder/CustomSectionInstanceCard.tsx +97 -0
  146. package/components/builder/DeviceFrame.tsx +123 -0
  147. package/components/builder/DndWrapper.tsx +337 -0
  148. package/components/builder/InsertionLines.tsx +186 -0
  149. package/components/builder/ParallaxGroupCanvas.tsx +228 -0
  150. package/components/builder/ParallaxSlideHeader.tsx +113 -0
  151. package/components/builder/ReadOnlyFrame.tsx +417 -0
  152. package/components/builder/SectionEditorBar.tsx +288 -0
  153. package/components/builder/SectionTypePicker.tsx +422 -0
  154. package/components/builder/SectionV2Canvas.tsx +297 -0
  155. package/components/builder/SectionV2Column.tsx +488 -0
  156. package/components/builder/SettingsPanel.tsx +911 -0
  157. package/components/builder/SortableBlock.tsx +230 -0
  158. package/components/builder/SortableRow.tsx +362 -0
  159. package/components/builder/VirtualAssetGrid.tsx +397 -0
  160. package/components/builder/asset-browser/AssetBrowser.tsx +178 -0
  161. package/components/builder/asset-browser/FileLightbox.tsx +116 -0
  162. package/components/builder/asset-browser/FolderTreeItem.tsx +55 -0
  163. package/components/builder/asset-browser/R2BrowserContent.tsx +436 -0
  164. package/components/builder/asset-browser/R2ContextMenu.tsx +98 -0
  165. package/components/builder/asset-browser/VideoThumbnail.tsx +63 -0
  166. package/components/builder/asset-browser/helpers.ts +88 -0
  167. package/components/builder/asset-browser/index.ts +1 -0
  168. package/components/builder/asset-browser/types.ts +49 -0
  169. package/components/builder/asset-browser/useAssetBrowser.ts +344 -0
  170. package/components/builder/asset-browser/useR2DragDrop.ts +116 -0
  171. package/components/builder/asset-browser/useR2Operations.ts +189 -0
  172. package/components/builder/blockStyles.tsx +295 -0
  173. package/components/builder/editors/ButtonBlockEditor.tsx +184 -0
  174. package/components/builder/editors/CoverBlockEditor.tsx +488 -0
  175. package/components/builder/editors/EnterAnimationPicker.tsx +297 -0
  176. package/components/builder/editors/HoverEffectPicker.tsx +209 -0
  177. package/components/builder/editors/ImageBlockEditor.tsx +206 -0
  178. package/components/builder/editors/ImageGridBlockEditor.tsx +386 -0
  179. package/components/builder/editors/ProjectGridEditor.tsx +648 -0
  180. package/components/builder/editors/SpacerBlockEditor.tsx +167 -0
  181. package/components/builder/editors/StaggerSettings.tsx +108 -0
  182. package/components/builder/editors/TextAlignmentIcons.tsx +39 -0
  183. package/components/builder/editors/TextBlockEditor.tsx +462 -0
  184. package/components/builder/editors/TextStylePicker.tsx +183 -0
  185. package/components/builder/editors/VideoBlockEditor.tsx +278 -0
  186. package/components/builder/editors/index.ts +10 -0
  187. package/components/builder/editors/shared.tsx +345 -0
  188. package/components/builder/hooks/useColumnDrag.ts +472 -0
  189. package/components/builder/hooks/useColumnResize.ts +221 -0
  190. package/components/builder/index.ts +12 -0
  191. package/components/builder/live-preview/LiveButtonPreview.tsx +38 -0
  192. package/components/builder/live-preview/LiveCoverPreview.tsx +146 -0
  193. package/components/builder/live-preview/LiveImageGridPreview.tsx +123 -0
  194. package/components/builder/live-preview/LiveImagePreview.tsx +107 -0
  195. package/components/builder/live-preview/LiveProjectGridPreview.tsx +1010 -0
  196. package/components/builder/live-preview/LiveSpacerPreview.tsx +9 -0
  197. package/components/builder/live-preview/LiveTextEditor.tsx +198 -0
  198. package/components/builder/live-preview/LiveVideoPreview.tsx +98 -0
  199. package/components/builder/live-preview/index.ts +10 -0
  200. package/components/builder/live-preview/shared.tsx +153 -0
  201. package/components/builder/settings-panel/BlockLayoutTab.tsx +532 -0
  202. package/components/builder/settings-panel/BlockSettings.tsx +94 -0
  203. package/components/builder/settings-panel/ColumnV2Settings.tsx +160 -0
  204. package/components/builder/settings-panel/LayoutTab.tsx +310 -0
  205. package/components/builder/settings-panel/PageSettings.tsx +200 -0
  206. package/components/builder/settings-panel/ParallaxGroupSettings.tsx +118 -0
  207. package/components/builder/settings-panel/ParallaxSlideSettings.tsx +178 -0
  208. package/components/builder/settings-panel/SectionV2AnimationTab.tsx +103 -0
  209. package/components/builder/settings-panel/SectionV2LayoutTab.tsx +312 -0
  210. package/components/builder/settings-panel/SectionV2Settings.tsx +323 -0
  211. package/components/builder/settings-panel/TRBLInputs.tsx +51 -0
  212. package/components/builder/settings-panel/index.ts +19 -0
  213. package/components/builder/settings-panel/responsive-helpers.ts +524 -0
  214. package/components/ui/CustomCursor.tsx +118 -0
  215. package/components/ui/NavContentLightbox.tsx +152 -0
  216. package/components/ui/Navbar.tsx +582 -0
  217. package/components/ui/PortfolioTracker.tsx +87 -0
  218. package/components/ui/ScrollToTop.tsx +47 -0
  219. package/lib/animation/enter-presets.ts +147 -0
  220. package/lib/animation/enter-resolve.ts +90 -0
  221. package/lib/animation/enter-types.ts +128 -0
  222. package/lib/animation/hover-effect-presets.ts +210 -0
  223. package/lib/animation/hover-effect-types.ts +126 -0
  224. package/lib/asset-retry.ts +111 -0
  225. package/lib/assets.ts +92 -0
  226. package/lib/audit.ts +35 -0
  227. package/lib/auth-token.ts +94 -0
  228. package/lib/auth.ts +13 -0
  229. package/lib/builder/cascade-helpers.ts +51 -0
  230. package/lib/builder/cascade.ts +533 -0
  231. package/lib/builder/constants.ts +103 -0
  232. package/lib/builder/defaults.ts +182 -0
  233. package/lib/builder/history.ts +48 -0
  234. package/lib/builder/index.ts +21 -0
  235. package/lib/builder/layout-styles.ts +344 -0
  236. package/lib/builder/masonry.ts +166 -0
  237. package/lib/builder/responsive.ts +156 -0
  238. package/lib/builder/serializer.ts +845 -0
  239. package/lib/builder/store-blocks.ts +193 -0
  240. package/lib/builder/store-canvas.ts +319 -0
  241. package/lib/builder/store-helpers.ts +490 -0
  242. package/lib/builder/store-sections.ts +709 -0
  243. package/lib/builder/store.ts +333 -0
  244. package/lib/builder/templates.ts +297 -0
  245. package/lib/builder/types.ts +374 -0
  246. package/lib/builder/utils.ts +37 -0
  247. package/lib/color-utils.ts +116 -0
  248. package/lib/config/index.ts +57 -0
  249. package/lib/config/types.ts +122 -0
  250. package/lib/contexts/AssetContext.tsx +79 -0
  251. package/lib/contexts/NavAnimationContext.tsx +44 -0
  252. package/lib/contexts/NavColorContext.tsx +38 -0
  253. package/lib/contexts/PageExitContext.tsx +194 -0
  254. package/lib/contexts/ThumbStatusContext.tsx +83 -0
  255. package/lib/csrf-client.ts +34 -0
  256. package/lib/csrf.ts +68 -0
  257. package/lib/format-utils.ts +24 -0
  258. package/lib/hooks/useViewport.ts +42 -0
  259. package/lib/logger.ts +81 -0
  260. package/lib/revalidate.ts +23 -0
  261. package/lib/sanitize.ts +91 -0
  262. package/lib/sanity/client.ts +8 -0
  263. package/lib/sanity/queries.ts +486 -0
  264. package/lib/sanity/types.ts +869 -0
  265. package/lib/sanity/writeClient.ts +24 -0
  266. package/lib/security.ts +402 -0
  267. package/lib/setup/detect.ts +156 -0
  268. package/lib/shader/glsl/index.ts +27 -0
  269. package/lib/shader/glsl/pixelate.ts +51 -0
  270. package/lib/shader/glsl/rgb-shift.ts +45 -0
  271. package/lib/shader/glsl/ripple.ts +46 -0
  272. package/lib/shader/glsl/vertex.ts +14 -0
  273. package/lib/storage/index.ts +211 -0
  274. package/lib/storage/r2-adapter.ts +286 -0
  275. package/lib/storage/types.ts +125 -0
  276. package/lib/styles/provider.tsx +267 -0
  277. package/lib/thumbnails/generate.ts +151 -0
  278. package/lib/utils.ts +6 -0
  279. package/package.json +212 -0
  280. package/sanity/compose.ts +65 -0
  281. package/sanity/sanity.config.ts +126 -0
  282. package/sanity/schemas/assetRegistry.ts +301 -0
  283. package/sanity/schemas/blocks/blockLayout.ts +90 -0
  284. package/sanity/schemas/blocks/buttonBlock.ts +82 -0
  285. package/sanity/schemas/blocks/coverBlock.ts +229 -0
  286. package/sanity/schemas/blocks/imageBlock.ts +58 -0
  287. package/sanity/schemas/blocks/imageGridBlock.ts +112 -0
  288. package/sanity/schemas/blocks/index.ts +9 -0
  289. package/sanity/schemas/blocks/projectGridBlock.ts +251 -0
  290. package/sanity/schemas/blocks/spacerBlock.ts +41 -0
  291. package/sanity/schemas/blocks/textBlock.ts +139 -0
  292. package/sanity/schemas/blocks/videoBlock.ts +80 -0
  293. package/sanity/schemas/customSection.ts +69 -0
  294. package/sanity/schemas/customSectionInstance.ts +163 -0
  295. package/sanity/schemas/index.ts +111 -0
  296. package/sanity/schemas/objects/enterAnimationConfig.ts +72 -0
  297. package/sanity/schemas/objects/hoverEffectConfig.ts +90 -0
  298. package/sanity/schemas/objects/parallaxGroup.ts +66 -0
  299. package/sanity/schemas/objects/parallaxSlide.ts +217 -0
  300. package/sanity/schemas/objects/typewriterConfig.ts +38 -0
  301. package/sanity/schemas/page.ts +162 -0
  302. package/sanity/schemas/pageSection.ts +157 -0
  303. package/sanity/schemas/pageSectionV2.ts +269 -0
  304. package/sanity/schemas/siteSettings.ts +256 -0
  305. package/sanity/schemas/siteStyles.ts +212 -0
  306. package/site/error.ts +4 -0
  307. package/site/index.ts +8 -0
  308. package/site/not-found.ts +4 -0
  309. package/site/page.ts +4 -0
  310. package/site/preview.ts +4 -0
  311. package/site/robots.ts +4 -0
  312. package/site/sitemap.ts +4 -0
  313. package/site/work.ts +4 -0
  314. package/studio/index.ts +4 -0
  315. package/styles/admin.css +85 -0
  316. package/styles/animations.css +237 -0
  317. package/styles/base.css +148 -0
  318. package/styles/globals.css +10 -0
  319. package/tsconfig.json +25 -0
@@ -0,0 +1,374 @@
1
+ // ============================================
2
+ // Builder State Types
3
+ // ============================================
4
+ // These types define the in-memory state for the Visual Page Builder.
5
+ // They mirror the Sanity schema types but are optimized for the builder UI.
6
+
7
+ import type {
8
+ Page,
9
+ PageType,
10
+ ContentBlock,
11
+ ContentItem,
12
+ PageSection,
13
+ PageSectionV2,
14
+ SectionSettings,
15
+ SectionV2Settings,
16
+ SectionV2Preset,
17
+ SectionColumn,
18
+ PageMetadata,
19
+ } from "../../lib/sanity/types";
20
+ import { DEFAULT_BG_COLOR, DEFAULT_TEXT_COLOR, DEFAULT_GRID_WIDTH } from "./constants";
21
+
22
+ // ============================================
23
+ // Block Type Registry
24
+ // ============================================
25
+
26
+ export type BlockType = ContentBlock["_type"];
27
+
28
+ export type BlockCategory = "content" | "section";
29
+
30
+ export interface BlockTypeInfo {
31
+ type: BlockType;
32
+ label: string;
33
+ description: string;
34
+ group: "generic";
35
+ icon: string; // SVG path or emoji for now
36
+ /** Block category for grouping in the picker */
37
+ category: BlockCategory;
38
+ }
39
+
40
+ /**
41
+ * Content blocks — available in the "+ Add Block" picker inside columns.
42
+ * Section-level blocks (projectGrid) are NOT here —
43
+ * they live in the SectionTypePicker as Page Sections.
44
+ */
45
+ export const BLOCK_TYPE_REGISTRY: BlockTypeInfo[] = [
46
+ { type: "textBlock", label: "Text", description: "Rich text content", group: "generic", icon: "T", category: "content" },
47
+ { type: "imageBlock", label: "Image", description: "Single image with caption", group: "generic", icon: "🖼", category: "content" },
48
+ { type: "imageGridBlock", label: "Image Grid", description: "Multiple images in grid", group: "generic", icon: "⊞", category: "content" },
49
+ { type: "videoBlock", label: "Video", description: "Vimeo, YouTube, or MP4", group: "generic", icon: "▶", category: "content" },
50
+ { type: "spacerBlock", label: "Spacer", description: "Vertical spacing", group: "generic", icon: "↕", category: "content" },
51
+ { type: "buttonBlock", label: "Button", description: "Call-to-action button", group: "generic", icon: "▣", category: "content" },
52
+ { type: "coverBlock", label: "Cover", description: "Full-screen hero section", group: "generic", icon: "◈", category: "content" },
53
+ ];
54
+
55
+ /**
56
+ * Complete block info lookup — includes BOTH content blocks and section blocks.
57
+ * Used by SettingsPanel, SortableBlock, DndWrapper, etc. for labels/icons.
58
+ * The BLOCK_TYPE_REGISTRY above only contains content blocks (for the picker).
59
+ */
60
+ export const ALL_BLOCK_INFO: BlockTypeInfo[] = [
61
+ ...BLOCK_TYPE_REGISTRY,
62
+ // Section blocks — not in the content picker but still need label/icon lookup
63
+ { type: "projectGridBlock", label: "Project Grid", description: "Staggered project showcase grid", group: "generic", icon: "⬡", category: "section" },
64
+ ];
65
+
66
+ // Parallax group info — used by BuilderCanvas/SortableRow for label/icon lookup (not a block)
67
+ export const PARALLAX_GROUP_INFO = { label: "Parallax Showcase", icon: "▽" };
68
+
69
+ // ============================================
70
+ // Section Types — for the "+ Add Section" picker
71
+ // ============================================
72
+
73
+ /** Section block types that create a full-width row with a pre-populated block */
74
+ export type SectionBlockType = "projectGridBlock";
75
+
76
+ /** Set for fast lookup — used by SortableBlock, ColumnDropZone, SortableRow to suppress inner chrome */
77
+ const SECTION_BLOCK_TYPES: ReadonlySet<string> = new Set<string>(["projectGridBlock"]);
78
+
79
+ /** Check if a block type is a section-level block (should render without block/column chrome) */
80
+ export function isSectionBlockType(type: string): boolean {
81
+ return SECTION_BLOCK_TYPES.has(type);
82
+ }
83
+
84
+ export type SectionType = "empty-v2" | "parallaxGroup" | SectionBlockType;
85
+
86
+ export interface SectionTypeInfo {
87
+ type: SectionType;
88
+ label: string;
89
+ description: string;
90
+ icon: string;
91
+ /** If set, this section auto-creates a row with this block type pre-populated */
92
+ blockType?: SectionBlockType;
93
+ }
94
+
95
+ export const SECTION_TYPE_REGISTRY: SectionTypeInfo[] = [
96
+ { type: "empty-v2", label: "Empty Section", description: "Grid section with flexible columns", icon: "⊞" },
97
+ { type: "projectGridBlock", label: "Project Grid", description: "Staggered project showcase grid", icon: "⬡", blockType: "projectGridBlock" },
98
+ { type: "parallaxGroup", label: "Parallax Section", description: "Full-screen parallax showcase with V2 slides", icon: "▽" },
99
+ ];
100
+
101
+ // ============================================
102
+ // Builder Store State
103
+ // ============================================
104
+
105
+ // ============================================
106
+ // Page-level Settings (global per page)
107
+ // ============================================
108
+
109
+ export interface PageSettings {
110
+ background_color: string;
111
+ nav_color: string;
112
+ text_color: string;
113
+ /** Page-level default enter animation (cascade: page → section → column → block) */
114
+ enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
115
+ /** Per-page nav entrance animation override */
116
+ nav_entrance_animation?: import("../../lib/sanity/types").NavEntrancePreset;
117
+ nav_entrance_duration?: number;
118
+ nav_entrance_delay?: number;
119
+ nav_entrance_disabled?: boolean;
120
+ }
121
+
122
+ export const DEFAULT_PAGE_SETTINGS: PageSettings = {
123
+ background_color: DEFAULT_BG_COLOR,
124
+ nav_color: "",
125
+ text_color: DEFAULT_TEXT_COLOR,
126
+ };
127
+
128
+ // ============================================
129
+ // Canvas Types
130
+ // ============================================
131
+
132
+ export type CanvasTool = 'select' | 'hand';
133
+ export type DeviceViewport = 'desktop' | 'tablet' | 'phone';
134
+
135
+ export const DEVICE_WIDTHS: Record<DeviceViewport, number> = {
136
+ desktop: 1920,
137
+ tablet: 810,
138
+ phone: 390,
139
+ } as const;
140
+
141
+ /** Simulated viewport heights for converting vh units in the builder canvas */
142
+ export const DEVICE_HEIGHTS: Record<DeviceViewport, number> = {
143
+ desktop: 800,
144
+ tablet: 1080,
145
+ phone: 844,
146
+ } as const;
147
+
148
+ // ============================================
149
+ // Grid Settings (from Customize → Grid)
150
+ // ============================================
151
+
152
+ export interface GridSettings {
153
+ width: string; // e.g. "1445"
154
+ outer_padding: string; // e.g. "30"
155
+ gutter_desktop: string;
156
+ gutter_responsive: string;
157
+ gutter_phone: string;
158
+ }
159
+
160
+ export const DEFAULT_GRID_SETTINGS: GridSettings = {
161
+ width: DEFAULT_GRID_WIDTH,
162
+ outer_padding: "30",
163
+ gutter_desktop: "30",
164
+ gutter_responsive: "30",
165
+ gutter_phone: "16",
166
+ };
167
+
168
+ export interface BuilderState {
169
+ // Page metadata
170
+ pageId: string | null;
171
+ pageTitle: string;
172
+ pageSlug: string;
173
+ /** The slug as loaded from Sanity — used for API calls even if slug is edited. */
174
+ _originalSlug: string;
175
+ pageType: PageType;
176
+ metadata: PageMetadata;
177
+ publishedAt: string | null;
178
+ draftMode: boolean;
179
+
180
+ // Content — page sections (V1 + V2)
181
+ rows: ContentItem[];
182
+
183
+ // Selection
184
+ selectedRowKey: string | null;
185
+ selectedColumnKey: string | null;
186
+ selectedBlockKey: string | null;
187
+ /** Sub-selection: which project card is selected within a ProjectGrid block */
188
+ selectedProjectCardKey: string | null;
189
+
190
+ // UI state
191
+ isDirty: boolean;
192
+ isSaving: boolean;
193
+ saveError: string | null;
194
+ lastSavedAt: string | null;
195
+
196
+ // Editor mode
197
+ previewMode: boolean;
198
+
199
+ // Custom section editor mode (Session 107)
200
+ editorMode: "page" | "customSection";
201
+ customSectionSlug: string | null;
202
+ customSectionTitle: string | null;
203
+ /** Stashed page state while editing a custom section */
204
+ savedPageState: { rows: ContentItem[]; selectedKey: string | null } | null;
205
+
206
+ // Page-level settings
207
+ pageSettings: PageSettings;
208
+
209
+ // Grid settings (from Customize, ephemeral — NOT saved per page)
210
+ gridSettings: GridSettings;
211
+
212
+ // Canvas state (ephemeral — NOT saved to Sanity)
213
+ canvasZoom: number;
214
+ canvasPanX: number;
215
+ canvasPanY: number;
216
+ canvasTool: CanvasTool;
217
+ activeViewport: DeviceViewport;
218
+
219
+ // BUG-014 fix: Track whether the Sanity document had page_settings.
220
+ // When true, applyGlobalStyles() won't overwrite user-chosen colors.
221
+ _hasDocumentPageSettings: boolean;
222
+
223
+ // History (Undo/Redo) — BUG-010 fix: snapshots include pageSettings
224
+ _history: import("./history").HistorySnapshot[];
225
+ _future: import("./history").HistorySnapshot[];
226
+ _isTimeTraveling: boolean;
227
+
228
+ /** Cache of fetched custom section base settings, keyed by custom_section_id.
229
+ * Populated by CustomSectionInstanceCard/ReadOnlyCustomSection on fetch.
230
+ * Used by SortableRow to merge base settings with per-instance overrides. */
231
+ _customSectionCache: Record<string, import("../../lib/sanity/types").SectionV2Settings>;
232
+ }
233
+
234
+ export interface BuilderActions {
235
+ // Page metadata
236
+ setPageTitle: (title: string) => void;
237
+ setPageSlug: (slug: string) => void;
238
+ setMetadata: (metadata: Partial<PageMetadata>) => void;
239
+ setDraftMode: (draft: boolean) => void;
240
+ publishPage: () => void;
241
+ unpublishPage: () => void;
242
+
243
+ // Section operations
244
+ addSection: (blockType: "projectGridBlock", afterRowKey?: string | null) => void;
245
+ reorderRows: (fromIndex: number, toIndex: number) => void;
246
+ /** Update settings for a PageSection (background, spacing, animation, etc.) */
247
+ updateSectionSettings: (sectionKey: string, settings: Partial<SectionSettings>) => void;
248
+ /** BUG-013 fix: Update responsive overrides for a PageSection */
249
+ updateSectionResponsive: (sectionKey: string, responsive: PageSection["responsive"]) => void;
250
+ /** Update the block content inside a PageSection */
251
+ updateSectionBlock: (sectionKey: string, updates: Partial<import("../../lib/sanity/types").ContentBlock>) => void;
252
+ /** Delete a section (PageSection or PageSectionV2) by key */
253
+ deleteSection: (sectionKey: string) => void;
254
+ /** Duplicate a section (PageSection or PageSectionV2), inserting copy after the original */
255
+ duplicateSection: (sectionKey: string) => void;
256
+
257
+ // Block operations
258
+ updateBlock: (blockKey: string, updates: Partial<ContentBlock>) => void;
259
+ deleteBlock: (blockKey: string) => void;
260
+ /** Duplicate a content block (inserts copy immediately after the original) */
261
+ duplicateBlock: (blockKey: string) => void;
262
+ moveBlock: (blockKey: string, targetSectionKey: string, targetColumnKey: string, toIndex: number) => void;
263
+ reorderBlocks: (sectionKey: string, columnKey: string, fromIndex: number, toIndex: number) => void;
264
+
265
+ // Selection
266
+ selectRow: (key: string | null) => void;
267
+ selectColumn: (rowKey: string | null, colKey: string | null) => void;
268
+ selectBlock: (key: string | null) => void;
269
+ /** Select a specific project card within a ProjectGrid block */
270
+ selectProjectCard: (key: string | null) => void;
271
+ clearSelection: () => void;
272
+
273
+ // Persistence
274
+ loadFromDocument: (doc: Page) => void;
275
+ save: () => Promise<void>;
276
+ reset: () => void;
277
+
278
+ // Dirty tracking
279
+ markDirty: () => void;
280
+ markClean: () => void;
281
+
282
+ // History (Undo/Redo)
283
+ undo: () => void;
284
+ redo: () => void;
285
+ canUndo: () => boolean;
286
+ canRedo: () => boolean;
287
+ /** Push a snapshot before a mutation (called internally). */
288
+ _pushSnapshot: () => void;
289
+
290
+ // ---- V2 Section operations ----
291
+ /** Add a new V2 grid section with a preset layout */
292
+ addSectionV2: (preset: import("../../lib/sanity/types").SectionV2Preset, afterRowKey?: string | null) => void;
293
+ /** Add a column to a V2 section at a specific position */
294
+ addColumnV2: (sectionKey: string, gridRow: number, gridColumn: number, span: number) => void;
295
+ /** Delete a column from a V2 section */
296
+ deleteColumnV2: (sectionKey: string, columnKey: string) => void;
297
+ /** Resize a column in a V2 section (right edge) */
298
+ resizeColumnV2: (sectionKey: string, columnKey: string, newSpan: number) => void;
299
+ /** Resize a column in a V2 section from the left edge */
300
+ resizeColumnV2Left: (sectionKey: string, columnKey: string, newGridColumn: number) => void;
301
+ /** Move a column in a V2 section (drag & drop) */
302
+ moveColumnV2: (sectionKey: string, columnKey: string, targetRow: number, targetColumn: number) => void;
303
+ /** Swap two columns in a V2 section (exchange positions, blocks stay) */
304
+ swapColumnV2: (sectionKey: string, draggedKey: string, targetKey: string) => void;
305
+ /** Move a column to an empty gap, adopting the gap's span */
306
+ moveColumnToGapV2: (sectionKey: string, columnKey: string, targetRow: number, targetColumn: number, targetSpan: number) => void;
307
+ /** Apply a layout preset to a V2 section (replaces columns + updates preset atomically) */
308
+ applyPresetV2: (sectionKey: string, preset: SectionV2Preset) => void;
309
+ /** Update settings for a V2 section */
310
+ updateSectionV2Settings: (sectionKey: string, settings: Partial<SectionV2Settings>) => void;
311
+ /** Update responsive overrides for a V2 section */
312
+ updateSectionV2Responsive: (sectionKey: string, responsive: PageSectionV2["responsive"]) => void;
313
+ /** Add a block to a V2 section column, optionally at a specific index */
314
+ addBlockV2: (sectionKey: string, columnKey: string, blockType: BlockType, insertIndex?: number) => void;
315
+ /** Select a V2 column */
316
+ selectColumnV2: (sectionKey: string | null, columnKey: string | null) => void;
317
+ /** Update enter animation for a V2 column (Session 117) */
318
+ updateColumnEnterAnimation: (sectionKey: string, colKey: string, config: import("../../lib/animation/enter-types").EnterAnimationConfig | undefined) => void;
319
+
320
+ // Debounced update (no snapshot per keystroke)
321
+ updateBlockDebounced: (blockKey: string, updates: Partial<ContentBlock>) => void;
322
+
323
+ // Editor mode
324
+ togglePreviewMode: () => void;
325
+ setPreviewMode: (preview: boolean) => void;
326
+
327
+ // Custom section editor mode (Session 107)
328
+ /** Enter section editor mode — stashes current page rows, replaces with a single V2 section */
329
+ enterSectionEditor: (slug: string | null, title: string | null, sectionData: PageSectionV2 | null) => void;
330
+ /** Exit section editor mode — restores page rows from stash. Pass wasSaved=true to mark page dirty. */
331
+ exitSectionEditor: (wasSaved?: boolean) => void;
332
+ /** Save the custom section being edited, then exit editor mode.
333
+ * Returns { id, slug, title } on success (for instance insertion after creation). */
334
+ saveSectionEditor: (title: string) => Promise<{ id: string; slug: string; title: string } | null>;
335
+ /** Insert a custom section instance reference into the page rows */
336
+ addCustomSectionInstance: (id: string, slug: string, title: string, afterRowKey?: string | null) => void;
337
+ /** Detach a custom section instance — replaces it with a copy of the section data (inline V2 section) */
338
+ detachCustomSectionInstance: (instanceKey: string, sectionData: PageSectionV2) => void;
339
+ /** Update the cached title on a custom section instance (cosmetic sync, Session 110) */
340
+ updateCustomSectionInstanceTitle: (instanceKey: string, newTitle: string) => void;
341
+ /** Update per-instance section settings overrides (Session 130) */
342
+ updateCustomSectionInstanceSettings: (instanceKey: string, updates: Partial<SectionV2Settings>) => void;
343
+ /** Cache base settings from a fetched custom section (for SortableRow merge) */
344
+ cacheCustomSectionSettings: (sectionId: string, settings: SectionV2Settings) => void;
345
+
346
+ // ---- Parallax Group operations (Session 123) ----
347
+ /** Add a new ParallaxGroup with 1 empty slide */
348
+ addParallaxGroup: (afterRowKey?: string | null) => void;
349
+ /** Add a new empty slide to a parallax group */
350
+ addParallaxSlide: (groupKey: string) => void;
351
+ /** Remove a slide from a parallax group (minimum 1 enforced) */
352
+ removeParallaxSlide: (groupKey: string, slideKey: string) => void;
353
+ /** Reorder a slide up or down within its group */
354
+ moveParallaxSlide: (groupKey: string, slideKey: string, direction: "up" | "down") => void;
355
+ /** Update background fields on a parallax slide */
356
+ updateParallaxSlideBackground: (groupKey: string, slideKey: string, fields: Partial<import("../../lib/sanity/types").ParallaxSlideV2>) => void;
357
+ /** Update group-level settings (transition effect, snap, etc.) */
358
+ updateParallaxGroupSettings: (groupKey: string, fields: Partial<Pick<import("../../lib/sanity/types").ParallaxGroup, "transition_effect" | "snap_enabled" | "parallax_intensity">>) => void;
359
+
360
+ // Page-level settings
361
+ updatePageSettings: (settings: Partial<PageSettings>) => void;
362
+ applyGlobalStyles: () => Promise<void>;
363
+
364
+ // Canvas
365
+ setCanvasZoom: (zoom: number) => void;
366
+ setCanvasPan: (x: number, y: number) => void;
367
+ setCanvasTool: (tool: CanvasTool) => void;
368
+ setActiveViewport: (viewport: DeviceViewport) => void;
369
+ zoomToFit: (viewportWidth: number, viewportHeight: number) => void;
370
+ zoomToPoint: (newZoom: number, cursorX: number, cursorY: number) => void;
371
+ zoomToFrame: (device: DeviceViewport, viewportWidth: number, viewportHeight: number) => void;
372
+ }
373
+
374
+ export type BuilderStore = BuilderState & BuilderActions;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Generate a unique key for Sanity objects (rows, columns, blocks).
3
+ * Uses crypto.randomUUID() for reliable collision resistance.
4
+ * Falls back to Math.random() in environments without crypto support.
5
+ */
6
+ export function generateKey(): string {
7
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
8
+ return crypto.randomUUID().replace(/-/g, "").substring(0, 12);
9
+ }
10
+ // Fallback: 12-character base-36 string
11
+ return (
12
+ Math.random().toString(36).substring(2, 8) +
13
+ Math.random().toString(36).substring(2, 8)
14
+ );
15
+ }
16
+
17
+ /**
18
+ * Normalize a min-height CSS value.
19
+ *
20
+ * BUG-015 fix: Prevents invalid CSS like '500pxpx' or bare numbers.
21
+ * - Preset values like '100vh', '80vh' etc. → use as-is
22
+ * - Values already containing a unit (px, vh, %, em, rem) → use as-is
23
+ * - Bare numbers like '500' → append 'px'
24
+ * - Empty/falsy → undefined
25
+ */
26
+ export function normalizeMinHeight(value: string | undefined): string | undefined {
27
+ if (!value) return undefined;
28
+ const trimmed = value.trim();
29
+ if (!trimmed) return undefined;
30
+ // Already has a CSS unit — use as-is
31
+ if (/[a-z%]$/i.test(trimmed)) return trimmed;
32
+ // Bare number — append px
33
+ if (/^\d+(\.\d+)?$/.test(trimmed)) return `${trimmed}px`;
34
+ // Unrecognized — return as-is and let CSS handle it
35
+ return trimmed;
36
+ }
37
+
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Centralized color conversion utilities.
3
+ *
4
+ * Previously duplicated across:
5
+ * - components/builder/ColorPicker.tsx (hexToHSL, hsvToHex, hexToHSV)
6
+ * - components/blocks/BlockRenderer.tsx (hexToRgba)
7
+ * - lib/builder/layout-styles.ts (hexToRgba)
8
+ * - components/admin/nav-builder/nav-builder-utils.ts (hexToRgba)
9
+ */
10
+
11
+ /** Convert hex (#RRGGBB) to HSL values. */
12
+ export function hexToHSL(hex: string): { h: number; s: number; l: number } {
13
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
14
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
15
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
16
+ const max = Math.max(r, g, b);
17
+ const min = Math.min(r, g, b);
18
+ let h = 0;
19
+ let s = 0;
20
+ const l = (max + min) / 2;
21
+ if (max !== min) {
22
+ const d = max - min;
23
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
24
+ switch (max) {
25
+ case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
26
+ case g: h = ((b - r) / d + 2) / 6; break;
27
+ case b: h = ((r - g) / d + 4) / 6; break;
28
+ }
29
+ }
30
+ return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
31
+ }
32
+
33
+ /** Convert HSV (hue 0–360, saturation 0–100, value 0–100) to hex (#RRGGBB). */
34
+ export function hsvToHex(h: number, s: number, v: number): string {
35
+ s /= 100; v /= 100;
36
+ const c = v * s;
37
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
38
+ const m = v - c;
39
+ let r = 0, g = 0, b = 0;
40
+ if (h < 60) { r = c; g = x; }
41
+ else if (h < 120) { r = x; g = c; }
42
+ else if (h < 180) { g = c; b = x; }
43
+ else if (h < 240) { g = x; b = c; }
44
+ else if (h < 300) { r = x; b = c; }
45
+ else { r = c; b = x; }
46
+ const toHex = (n: number) => Math.round((n + m) * 255).toString(16).padStart(2, "0");
47
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
48
+ }
49
+
50
+ /** Convert hex (#RRGGBB) to HSV values. */
51
+ export function hexToHSV(hex: string): { h: number; s: number; v: number } {
52
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
53
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
54
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
55
+ const max = Math.max(r, g, b);
56
+ const min = Math.min(r, g, b);
57
+ const d = max - min;
58
+ let h = 0;
59
+ const s = max === 0 ? 0 : d / max;
60
+ const v = max;
61
+ if (max !== min) {
62
+ switch (max) {
63
+ case r: h = ((g - b) / d + (g < b ? 6 : 0)) * 60; break;
64
+ case g: h = ((b - r) / d + 2) * 60; break;
65
+ case b: h = ((r - g) / d + 4) * 60; break;
66
+ }
67
+ }
68
+ return { h: Math.round(h), s: Math.round(s * 100), v: Math.round(v * 100) };
69
+ }
70
+
71
+ /**
72
+ * Convert hex color to rgba string.
73
+ * Supports #RGB, #RRGGBB, and #RRGGBBAA formats.
74
+ */
75
+ export function hexToRgba(hex: string, alpha: number): string {
76
+ let r = 0, g = 0, b = 0;
77
+ const clean = hex.replace("#", "");
78
+ if (clean.length === 3) {
79
+ r = parseInt(clean[0] + clean[0], 16);
80
+ g = parseInt(clean[1] + clean[1], 16);
81
+ b = parseInt(clean[2] + clean[2], 16);
82
+ } else if (clean.length >= 6) {
83
+ r = parseInt(clean.slice(0, 2), 16);
84
+ g = parseInt(clean.slice(2, 4), 16);
85
+ b = parseInt(clean.slice(4, 6), 16);
86
+ }
87
+ return `rgba(${r},${g},${b},${alpha})`;
88
+ }
89
+
90
+ /** Validate a hex color string (#RRGGBB format). */
91
+ export function isValidHex(hex: string): boolean {
92
+ return /^#[0-9a-fA-F]{6}$/.test(hex);
93
+ }
94
+
95
+ /**
96
+ * Linearly interpolate between two hex colors.
97
+ * @param a - Start hex color (#RRGGBB)
98
+ * @param b - End hex color (#RRGGBB)
99
+ * @param t - Interpolation factor 0..1 (0 = a, 1 = b)
100
+ * @returns Interpolated hex color (#RRGGBB)
101
+ */
102
+ export function lerpHex(a: string, b: string, t: number): string {
103
+ const cleanA = a.replace("#", "");
104
+ const cleanB = b.replace("#", "");
105
+ const rA = parseInt(cleanA.slice(0, 2), 16);
106
+ const gA = parseInt(cleanA.slice(2, 4), 16);
107
+ const bA = parseInt(cleanA.slice(4, 6), 16);
108
+ const rB = parseInt(cleanB.slice(0, 2), 16);
109
+ const gB = parseInt(cleanB.slice(2, 4), 16);
110
+ const bB = parseInt(cleanB.slice(4, 6), 16);
111
+ const clamp = (n: number) => Math.max(0, Math.min(255, Math.round(n)));
112
+ const r = clamp(rA + (rB - rA) * t);
113
+ const g = clamp(gA + (gB - gA) * t);
114
+ const bl = clamp(bA + (bB - bA) * t);
115
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${bl.toString(16).padStart(2, "0")}`;
116
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Site configuration accessor.
3
+ *
4
+ * Call `registerConfig(config)` at app startup (e.g. in your root layout)
5
+ * before any component calls `getSiteConfig()`. The instance's
6
+ * `app/layout.tsx` imports its own `site.config.ts` and passes it here.
7
+ */
8
+
9
+ import type { SiteConfig } from "./types";
10
+
11
+ /** Module-level slot for the registered config. */
12
+ let _registeredConfig: SiteConfig | null = null;
13
+
14
+ /** Cached reference. */
15
+ let _resolved: SiteConfig | null = null;
16
+
17
+ /**
18
+ * Register the site configuration.
19
+ *
20
+ * Call this once at app startup (root layout module scope) before any
21
+ * component or API route calls `getSiteConfig()`. The instance's
22
+ * `app/layout.tsx` imports its own `site.config.ts` and passes it here.
23
+ *
24
+ * Calling this more than once replaces the previous config (useful for
25
+ * testing, but not expected in production).
26
+ */
27
+ export function registerConfig(config: SiteConfig): void {
28
+ _registeredConfig = config;
29
+ // Invalidate resolved cache so next getSiteConfig() picks up the new value.
30
+ _resolved = null;
31
+ }
32
+
33
+ /**
34
+ * Get the site configuration.
35
+ *
36
+ * Returns the config registered via `registerConfig()`.
37
+ * Throws if no config has been registered yet.
38
+ *
39
+ * Returns the same object reference on every call (singleton).
40
+ * Safe to call from both server and client components.
41
+ */
42
+ export function getSiteConfig(): SiteConfig {
43
+ if (!_resolved) {
44
+ if (_registeredConfig) {
45
+ _resolved = _registeredConfig;
46
+ } else {
47
+ throw new Error(
48
+ "SiteConfig not registered. Call registerConfig(config) in your root layout before using getSiteConfig().\n" +
49
+ "See: https://github.com/MorphikaStudio/Morphika_Andami#quick-start",
50
+ );
51
+ }
52
+ }
53
+ return _resolved;
54
+ }
55
+
56
+ // Re-export types for convenience
57
+ export type { SiteConfig } from "./types";