@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,422 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect } from "react";
4
+ import { SECTION_TYPE_REGISTRY } from "../../lib/builder/types";
5
+ import type { CustomSectionListItem } from "../../lib/sanity/types";
6
+ import { ProjectGridBlockIcon, ParallaxGroupIcon } from "./blockStyles";
7
+ import { BLOCK_GRADIENTS } from "./blockStyles";
8
+ import { BUILDER_BLUE, BUILDER_VIOLET } from "../../lib/builder/constants";
9
+
10
+ // ── Section card icons ──
11
+
12
+ function EmptySectionV2Icon({ size = 28 }: { size?: number }) {
13
+ return (
14
+ <svg width={size} height={size} viewBox="0 0 40 40" fill="none">
15
+ <defs>
16
+ <linearGradient id="es2Grad" x1="5" y1="5" x2="35" y2="35">
17
+ <stop offset="0%" stopColor={BUILDER_BLUE} />
18
+ <stop offset="100%" stopColor="#0550c0" />
19
+ </linearGradient>
20
+ </defs>
21
+ {/* Grid pattern */}
22
+ <rect x="3" y="3" width="34" height="34" rx="4" fill="url(#es2Grad)" opacity="0.15" />
23
+ <rect x="3" y="3" width="34" height="34" rx="4" stroke="url(#es2Grad)" strokeWidth="2" fill="none" opacity="0.5" />
24
+ {/* Grid columns */}
25
+ <rect x="6" y="8" width="8" height="24" rx="2" fill="url(#es2Grad)" opacity="0.3" />
26
+ <rect x="16" y="8" width="8" height="24" rx="2" fill="url(#es2Grad)" opacity="0.3" />
27
+ <rect x="26" y="8" width="8" height="24" rx="2" fill="url(#es2Grad)" opacity="0.3" />
28
+ </svg>
29
+ );
30
+ }
31
+
32
+ function SavedSectionIcon({ size = 28 }: { size?: number }) {
33
+ return (
34
+ <svg width={size} height={size} viewBox="0 0 40 40" fill="none">
35
+ <defs>
36
+ <linearGradient id="savedGrad" x1="5" y1="5" x2="35" y2="35">
37
+ <stop offset="0%" stopColor={BUILDER_VIOLET} />
38
+ <stop offset="100%" stopColor="#6d28d9" />
39
+ </linearGradient>
40
+ </defs>
41
+ <rect x="3" y="3" width="34" height="34" rx="4" fill="url(#savedGrad)" opacity="0.15" />
42
+ <rect x="3" y="3" width="34" height="34" rx="4" stroke="url(#savedGrad)" strokeWidth="2" fill="none" opacity="0.5" />
43
+ {/* Component/puzzle icon */}
44
+ <rect x="8" y="8" width="10" height="10" rx="2" fill="url(#savedGrad)" opacity="0.5" />
45
+ <rect x="22" y="8" width="10" height="10" rx="2" fill="url(#savedGrad)" opacity="0.5" />
46
+ <rect x="8" y="22" width="10" height="10" rx="2" fill="url(#savedGrad)" opacity="0.5" />
47
+ <rect x="22" y="22" width="10" height="10" rx="2" fill="url(#savedGrad)" opacity="0.3" />
48
+ </svg>
49
+ );
50
+ }
51
+
52
+ const SECTION_ICON_COMPONENTS: Record<string, React.FC<{ size?: number }>> = {
53
+ "empty-v2": EmptySectionV2Icon,
54
+ projectGridBlock: ProjectGridBlockIcon,
55
+ parallaxGroup: ParallaxGroupIcon,
56
+ };
57
+
58
+ const SECTION_GRADIENTS: Record<string, string> = {
59
+ "empty-v2": "linear-gradient(135deg, #d0e0f8 0%, #b8d0f0 50%, #a0c0e8 100%)",
60
+ projectGridBlock: BLOCK_GRADIENTS.projectGridBlock,
61
+ parallaxGroup: BLOCK_GRADIENTS.parallaxGroup,
62
+ };
63
+
64
+ // ── V2 layout presets (use cascade preset names) ──
65
+ type V2Preset = "full" | "halves" | "thirds" | "quarters" | "1/3+2/3" | "2/3+1/3";
66
+ const layoutPresets: { key: V2Preset; label: string; widths: number[] }[] = [
67
+ { key: "full", label: "Full Width", widths: [12] },
68
+ { key: "halves", label: "Two Halves", widths: [6, 6] },
69
+ { key: "thirds", label: "Three Thirds", widths: [4, 4, 4] },
70
+ { key: "quarters", label: "Four Quarters", widths: [3, 3, 3, 3] },
71
+ { key: "1/3+2/3", label: "1/3 + 2/3", widths: [4, 8] },
72
+ { key: "2/3+1/3", label: "2/3 + 1/3", widths: [8, 4] },
73
+ ];
74
+
75
+ // ── Shared card component (glass card with gradient, icon, title, subtitle) ──
76
+
77
+ interface SectionCardProps {
78
+ gradient: string;
79
+ icon: React.ReactNode;
80
+ title: string;
81
+ subtitle?: string;
82
+ isHovered: boolean;
83
+ onMouseEnter: () => void;
84
+ onMouseLeave: () => void;
85
+ onClick: () => void;
86
+ }
87
+
88
+ function SectionCard({
89
+ gradient,
90
+ icon,
91
+ title,
92
+ subtitle,
93
+ isHovered,
94
+ onMouseEnter,
95
+ onMouseLeave,
96
+ onClick,
97
+ }: SectionCardProps) {
98
+ return (
99
+ <button
100
+ onClick={onClick}
101
+ onMouseEnter={onMouseEnter}
102
+ onMouseLeave={onMouseLeave}
103
+ className="relative flex items-center gap-3 rounded-2xl px-3.5 py-3 transition-all text-left group overflow-hidden border-0"
104
+ style={{
105
+ background: gradient,
106
+ transform: isHovered ? "translateY(-1px) scale(1.015)" : "translateY(0) scale(1)",
107
+ boxShadow: isHovered
108
+ ? "0 8px 24px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.3)"
109
+ : "0 2px 8px rgba(0,0,0,0.08), inset 0 1px 0 rgba(255,255,255,0.2)",
110
+ transition: "all 0.3s cubic-bezier(0.23, 1, 0.32, 1)",
111
+ }}
112
+ >
113
+ {/* Glass overlay */}
114
+ <div
115
+ className="absolute inset-0 rounded-2xl pointer-events-none"
116
+ style={{
117
+ background: "linear-gradient(135deg, rgba(255,255,255,0.25) 0%, rgba(255,255,255,0.05) 100%)",
118
+ }}
119
+ />
120
+ {/* Icon container */}
121
+ <div
122
+ className="relative shrink-0 flex items-center justify-center"
123
+ style={{
124
+ width: 44,
125
+ height: 44,
126
+ borderRadius: 12,
127
+ background: "rgba(255,255,255,0.4)",
128
+ backdropFilter: "blur(8px)",
129
+ boxShadow: "0 2px 8px rgba(0,0,0,0.06), inset 0 1px 0 rgba(255,255,255,0.5)",
130
+ transition: "transform 0.3s",
131
+ transform: isHovered ? "scale(1.08)" : "scale(1)",
132
+ }}
133
+ >
134
+ {icon}
135
+ </div>
136
+ {/* Text */}
137
+ <div className="relative z-10 min-w-0 flex-1">
138
+ <p
139
+ className="text-sm font-semibold truncate"
140
+ style={{ color: "rgba(0,0,0,0.72)", textShadow: "0 1px 0 rgba(255,255,255,0.3)" }}
141
+ >
142
+ {title}
143
+ </p>
144
+ {subtitle && (
145
+ <p
146
+ className="text-xs truncate leading-snug mt-0.5"
147
+ style={{ color: "rgba(0,0,0,0.42)" }}
148
+ >
149
+ {subtitle}
150
+ </p>
151
+ )}
152
+ </div>
153
+ </button>
154
+ );
155
+ }
156
+
157
+ // ============================================
158
+
159
+ interface SectionTypePickerProps {
160
+ onSelectEmptyV2?: (preset: "full" | "halves" | "thirds" | "quarters" | "1/3+2/3" | "2/3+1/3") => void;
161
+ onSelectSection: (blockType: "projectGridBlock") => void;
162
+ onSelectParallaxGroup?: () => void;
163
+ onSelectCustomSection?: (section: CustomSectionListItem) => void;
164
+ onCreateCustomSection?: () => void;
165
+ onClose: () => void;
166
+ /** Pre-fetched custom sections list — skips internal fetch when provided */
167
+ preloadedSections?: CustomSectionListItem[] | null;
168
+ }
169
+
170
+ export default function SectionTypePicker({
171
+ onSelectEmptyV2,
172
+ onSelectSection,
173
+ onSelectParallaxGroup,
174
+ onSelectCustomSection,
175
+ onCreateCustomSection,
176
+ onClose,
177
+ preloadedSections,
178
+ }: SectionTypePickerProps) {
179
+ const [hovered, setHovered] = useState<string | null>(null);
180
+ // "pick" = choosing section type, "layout" = choosing column layout
181
+ const [step, setStep] = useState<"pick" | "layout">("pick");
182
+
183
+ // Custom sections — use preloaded data when available, otherwise fetch on mount
184
+ const [savedSections, setSavedSections] = useState<CustomSectionListItem[]>(
185
+ preloadedSections ?? []
186
+ );
187
+ const [loadingSaved, setLoadingSaved] = useState(!preloadedSections);
188
+ // Show skeletons only if fetch takes longer than 400ms (avoids flash on fast loads)
189
+ const [showSkeletons, setShowSkeletons] = useState(false);
190
+ const [savedError, setSavedError] = useState<string | null>(null);
191
+ const showCustomSections = !!(onSelectCustomSection || onCreateCustomSection);
192
+
193
+ // Fetch custom sections on mount — only if no preloaded data
194
+ useEffect(() => {
195
+ if (!showCustomSections || preloadedSections) return;
196
+ let cancelled = false;
197
+ setLoadingSaved(true);
198
+ setSavedError(null);
199
+
200
+ // Delay skeleton display to avoid flash on fast responses
201
+ const skeletonTimer = setTimeout(() => {
202
+ if (!cancelled) setShowSkeletons(true);
203
+ }, 400);
204
+
205
+ fetch("/api/admin/custom-sections")
206
+ .then((res) => {
207
+ if (!res.ok) throw new Error("Failed to load");
208
+ return res.json();
209
+ })
210
+ .then((data) => {
211
+ if (!cancelled) setSavedSections(data.sections || []);
212
+ })
213
+ .catch(() => {
214
+ if (!cancelled) setSavedError("Failed to load custom sections");
215
+ })
216
+ .finally(() => {
217
+ if (!cancelled) {
218
+ setLoadingSaved(false);
219
+ setShowSkeletons(false);
220
+ clearTimeout(skeletonTimer);
221
+ }
222
+ });
223
+
224
+ return () => { cancelled = true; clearTimeout(skeletonTimer); };
225
+ }, [showCustomSections, preloadedSections]);
226
+
227
+ return (
228
+ <div
229
+ className="fixed inset-0 z-50 flex items-center justify-center bg-black/30 backdrop-blur-sm"
230
+ onClick={onClose}
231
+ >
232
+ <div
233
+ className="w-full max-w-2xl rounded-2xl bg-white max-h-[80vh] flex flex-col shadow-2xl border border-neutral-200/50 overflow-hidden"
234
+ style={{ fontFamily: "Inter, system-ui, sans-serif" }}
235
+ onClick={(e) => e.stopPropagation()}
236
+ >
237
+ {/* Header */}
238
+ <div className="px-6 pt-6 pb-4 border-b border-neutral-100">
239
+ <div className="flex items-center justify-between mb-1">
240
+ <div className="flex items-center gap-2">
241
+ {step === "layout" && (
242
+ <button
243
+ onClick={() => setStep("pick")}
244
+ className="w-7 h-7 rounded-lg flex items-center justify-center text-neutral-400 hover:text-neutral-600 hover:bg-neutral-100 transition-colors"
245
+ aria-label="Back to section types"
246
+ >
247
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
248
+ <polyline points="15 18 9 12 15 6" />
249
+ </svg>
250
+ </button>
251
+ )}
252
+ <h3 className="text-lg font-semibold text-neutral-900">
253
+ {step === "pick" ? "Add Section" : "Choose Layout"}
254
+ </h3>
255
+ </div>
256
+ <button
257
+ onClick={onClose}
258
+ className="w-8 h-8 rounded-lg flex items-center justify-center text-neutral-400 hover:text-neutral-600 hover:bg-neutral-100 transition-colors"
259
+ aria-label="Close section picker"
260
+ >
261
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
262
+ <line x1="18" y1="6" x2="6" y2="18" />
263
+ <line x1="6" y1="6" x2="18" y2="18" />
264
+ </svg>
265
+ </button>
266
+ </div>
267
+ <p className="text-sm text-neutral-400">
268
+ {step === "pick"
269
+ ? "Choose a section type to add to your page"
270
+ : "Select a column layout for your section"}
271
+ </p>
272
+ </div>
273
+
274
+ {/* Content */}
275
+ <div className="overflow-y-auto px-6 py-4 flex-1">
276
+ {step === "layout" ? (
277
+ /* ── Column layout picker ── */
278
+ <div className="grid grid-cols-3 gap-2">
279
+ {layoutPresets.map(({ key, label, widths }) => (
280
+ <button
281
+ key={key}
282
+ onClick={() => {
283
+ if (onSelectEmptyV2) {
284
+ onSelectEmptyV2(key);
285
+ }
286
+ onClose();
287
+ }}
288
+ className="rounded-xl border border-neutral-200 bg-white p-3 hover:border-[#076bff] hover:bg-[#076bff]/5 transition-colors group shadow-sm"
289
+ title={label}
290
+ >
291
+ <div className="flex gap-1 h-6">
292
+ {widths.map((w, i) => (
293
+ <div
294
+ key={i}
295
+ className="bg-neutral-200 group-hover:bg-[#076bff]/30 rounded-sm transition-colors"
296
+ style={{ flex: w }}
297
+ />
298
+ ))}
299
+ </div>
300
+ <p className="text-xs text-neutral-500 mt-1.5 group-hover:text-neutral-700">
301
+ {label}
302
+ </p>
303
+ </button>
304
+ ))}
305
+ </div>
306
+ ) : step === "pick" ? (
307
+ /* ── Section type cards + inline custom sections ── */
308
+ <div>
309
+ <p className="text-[10px] font-semibold uppercase tracking-widest text-neutral-300 mb-2.5">
310
+ Page Sections
311
+ </p>
312
+ <div className="grid grid-cols-2 gap-2.5">
313
+ {SECTION_TYPE_REGISTRY.map((section) => {
314
+ const IconComponent = SECTION_ICON_COMPONENTS[section.type];
315
+ const gradient = SECTION_GRADIENTS[section.type] || "#f5f5f5";
316
+
317
+ return (
318
+ <SectionCard
319
+ key={section.type}
320
+ gradient={gradient}
321
+ icon={IconComponent ? <IconComponent /> : null}
322
+ title={section.label}
323
+ subtitle={section.description}
324
+ isHovered={hovered === section.type}
325
+ onMouseEnter={() => setHovered(section.type)}
326
+ onMouseLeave={() => setHovered(null)}
327
+ onClick={() => {
328
+ if (section.type === "empty-v2") {
329
+ setStep("layout");
330
+ } else if (section.type === "parallaxGroup") {
331
+ onSelectParallaxGroup?.();
332
+ onClose();
333
+ } else if (section.blockType) {
334
+ onSelectSection(section.blockType);
335
+ onClose();
336
+ }
337
+ }}
338
+ />
339
+ );
340
+ })}
341
+ </div>
342
+
343
+ {/* Custom Sections — inline */}
344
+ {showCustomSections && (
345
+ <>
346
+ <p className="text-[10px] font-semibold uppercase tracking-widest text-neutral-300 mb-2.5 mt-5">
347
+ Custom Sections
348
+ </p>
349
+
350
+ <div className="grid grid-cols-2 gap-2.5">
351
+ {/* Create new section button — always visible immediately */}
352
+ {onCreateCustomSection && (
353
+ <SectionCard
354
+ gradient="linear-gradient(135deg, #f3f0ff 0%, #ede5ff 50%, #e0d4fc 100%)"
355
+ icon={<span className="text-[#8b5cf6] text-xl font-light">+</span>}
356
+ title="Create New"
357
+ subtitle="Build a reusable section"
358
+ isHovered={hovered === "create-custom"}
359
+ onMouseEnter={() => setHovered("create-custom")}
360
+ onMouseLeave={() => setHovered(null)}
361
+ onClick={() => {
362
+ onCreateCustomSection();
363
+ onClose();
364
+ }}
365
+ />
366
+ )}
367
+
368
+ {/* Loading placeholder cards — only shown if fetch takes >400ms */}
369
+ {loadingSaved && showSkeletons && (
370
+ <>
371
+ {[1, 2].map((i) => (
372
+ <div
373
+ key={`skeleton-${i}`}
374
+ className="flex items-center gap-3 rounded-2xl px-3.5 py-3 animate-pulse"
375
+ style={{ background: "linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%)" }}
376
+ >
377
+ <div className="w-11 h-11 rounded-xl bg-white/60" />
378
+ <div className="flex-1 space-y-1.5">
379
+ <div className="h-3.5 bg-white/60 rounded w-24" />
380
+ <div className="h-2.5 bg-white/40 rounded w-16" />
381
+ </div>
382
+ </div>
383
+ ))}
384
+ </>
385
+ )}
386
+
387
+ {/* Error state */}
388
+ {!loadingSaved && savedError && (
389
+ <div className="col-span-2 flex items-center justify-center py-4">
390
+ <p className="text-xs text-red-400">{savedError}</p>
391
+ </div>
392
+ )}
393
+
394
+ {/* Existing custom sections */}
395
+ {!loadingSaved && savedSections.map((section) => (
396
+ <SectionCard
397
+ key={section._id}
398
+ gradient="linear-gradient(135deg, #ede9fe 0%, #ddd6fe 50%, #c4b5fd 100%)"
399
+ icon={<SavedSectionIcon size={20} />}
400
+ title={section.title}
401
+ subtitle={section.description}
402
+ isHovered={hovered === section._id}
403
+ onMouseEnter={() => setHovered(section._id)}
404
+ onMouseLeave={() => setHovered(null)}
405
+ onClick={() => {
406
+ if (onSelectCustomSection) {
407
+ onSelectCustomSection(section);
408
+ }
409
+ onClose();
410
+ }}
411
+ />
412
+ ))}
413
+ </div>
414
+ </>
415
+ )}
416
+ </div>
417
+ ) : null}
418
+ </div>
419
+ </div>
420
+ </div>
421
+ );
422
+ }