@morphika/webframe 0.1.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 (319) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +46 -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 +210 -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,228 @@
1
+ "use client";
2
+
3
+ import { useBuilderStore } from "../../lib/builder/store";
4
+ import type { ParallaxGroup, ParallaxSlideV2, PageSectionV2 } from "../../lib/sanity/types";
5
+ import SectionV2Canvas from "./SectionV2Canvas";
6
+ import ParallaxSlideHeader from "./ParallaxSlideHeader";
7
+ import { BUILDER_GREEN, BUILDER_VIOLET } from "../../lib/builder/constants";
8
+ import { DEVICE_HEIGHTS } from "../../lib/builder/types";
9
+ import { useAssetUrl } from "../../lib/contexts/AssetContext";
10
+
11
+ /**
12
+ * ParallaxGroupCanvas — renders a ParallaxGroup in the builder canvas.
13
+ *
14
+ * Each slide is displayed as a stacked section with:
15
+ * - ParallaxSlideHeader (index, bg indicator, reorder, delete)
16
+ * - SectionV2Canvas (full V2 grid editor reuse)
17
+ * - Faint background preview when a bg image is set
18
+ * - Empty state message when slide has no background and no content blocks
19
+ *
20
+ * An "Add Slide" button appears at the bottom.
21
+ *
22
+ * Session 123: Parallax V2 Phase 2
23
+ * Session 127: Phase 5 — empty state, slide counter badge, smooth reorder
24
+ */
25
+
26
+ interface ParallaxGroupCanvasProps {
27
+ group: ParallaxGroup;
28
+ onAddBlockTarget: (sectionKey: string, colKey: string, insertIndex?: number) => void;
29
+ }
30
+
31
+ /** Check whether a slide is empty (no background set and no content blocks) */
32
+ function isSlideEmpty(slide: ParallaxSlideV2): boolean {
33
+ const hasBg = slide.background_type === "video"
34
+ ? !!slide.background_video
35
+ : !!slide.background_image;
36
+ const hasBlocks = slide.columns.some((col) => col.blocks.length > 0);
37
+ return !hasBg && !hasBlocks;
38
+ }
39
+
40
+ export default function ParallaxGroupCanvas({
41
+ group,
42
+ onAddBlockTarget,
43
+ }: ParallaxGroupCanvasProps) {
44
+ const store = useBuilderStore();
45
+ const selectedRowKey = store.selectedRowKey;
46
+ const activeViewport = store.activeViewport || "desktop";
47
+ const slidePreviewHeight = DEVICE_HEIGHTS[activeViewport];
48
+ const assetUrl = useAssetUrl();
49
+
50
+ return (
51
+ <div
52
+ className="relative"
53
+ style={{
54
+ borderRadius: 12,
55
+ border: "1.5px solid rgba(139, 92, 246, 0.25)",
56
+ overflow: "visible",
57
+ }}
58
+ >
59
+ {/* Group header */}
60
+ <div
61
+ className="flex items-center gap-2 px-3 py-2 cursor-pointer"
62
+ style={{
63
+ background: selectedRowKey === group._key
64
+ ? "linear-gradient(135deg, #e8deff 0%, #ddd0ff 100%)"
65
+ : "linear-gradient(135deg, #f3f0ff 0%, #ede5ff 100%)",
66
+ borderBottom: "1px solid rgba(139, 92, 246, 0.15)",
67
+ borderRadius: "12px 12px 0 0",
68
+ }}
69
+ onClick={(e) => {
70
+ e.stopPropagation();
71
+ store.selectRow(group._key);
72
+ }}
73
+ >
74
+ <span className="text-[11px] font-semibold text-[#8b5cf6]">
75
+ ▽ Parallax Showcase
76
+ </span>
77
+ {/* Slide counter badge */}
78
+ <span
79
+ className="inline-flex items-center justify-center rounded-full text-[9px] font-bold text-white min-w-[18px] h-[18px] px-1"
80
+ style={{ background: BUILDER_VIOLET }}
81
+ >
82
+ {group.slides.length}
83
+ </span>
84
+ <div className="flex-1" />
85
+ <span className="text-[9px] text-neutral-400 uppercase tracking-wider">
86
+ {group.transition_effect}
87
+ </span>
88
+ </div>
89
+
90
+ {/* Slides — CSS transition for smooth reorder */}
91
+ {group.slides.map((slide, slideIndex) => {
92
+ const isSlideSelected = selectedRowKey === slide._key;
93
+ const bgImagePath = slide.background_type === "image" && slide.background_image
94
+ ? assetUrl(slide.background_image)
95
+ : null;
96
+ // For video backgrounds, show the first frame as a poster-like preview
97
+ const bgVideoPath = slide.background_type === "video" && slide.background_video
98
+ ? assetUrl(slide.background_video)
99
+ : null;
100
+ const hasBgPreview = !!(bgImagePath || bgVideoPath);
101
+ const slideEmpty = isSlideEmpty(slide);
102
+
103
+ // Create a virtual PageSectionV2 to pass to SectionV2Canvas
104
+ const virtualSection: PageSectionV2 = {
105
+ _type: "pageSectionV2",
106
+ _key: slide._key,
107
+ section_type: "empty-v2",
108
+ columns: slide.columns,
109
+ settings: slide.section_settings,
110
+ };
111
+
112
+ return (
113
+ <div
114
+ key={slide._key}
115
+ className="relative"
116
+ style={{
117
+ borderTop: slideIndex > 0 ? "1px solid rgba(139, 92, 246, 0.1)" : undefined,
118
+ transition: "opacity 0.2s ease",
119
+ }}
120
+ >
121
+ {/* Slide header */}
122
+ <ParallaxSlideHeader
123
+ slide={slide}
124
+ slideIndex={slideIndex}
125
+ totalSlides={group.slides.length}
126
+ groupKey={group._key}
127
+ isSelected={isSlideSelected}
128
+ onSelect={() => store.selectRow(slide._key)}
129
+ />
130
+
131
+ {/* Slide content with optional background preview — 100vh equivalent */}
132
+ <div className="relative" style={{ minHeight: slidePreviewHeight }}>
133
+ {/* Background preview — faint image behind the grid */}
134
+ {bgImagePath && (
135
+ <div
136
+ className="absolute inset-0 bg-cover bg-center pointer-events-none"
137
+ style={{
138
+ backgroundImage: `url(${bgImagePath})`,
139
+ backgroundPosition: slide.background_position || "center center",
140
+ opacity: 0.12,
141
+ }}
142
+ />
143
+ )}
144
+
145
+ {/* Video background preview — muted autoplay thumbnail */}
146
+ {bgVideoPath && (
147
+ <video
148
+ src={bgVideoPath}
149
+ muted
150
+ playsInline
151
+ autoPlay
152
+ loop
153
+ className="absolute inset-0 w-full h-full object-cover pointer-events-none"
154
+ style={{ opacity: 0.12 }}
155
+ />
156
+ )}
157
+
158
+ {/* Overlay preview */}
159
+ {(slide.background_overlay_opacity ?? 0) > 0 && hasBgPreview && (
160
+ <div
161
+ className="absolute inset-0 pointer-events-none"
162
+ style={{
163
+ backgroundColor: slide.background_overlay_color || "#000000",
164
+ opacity: (slide.background_overlay_opacity ?? 0) / 100 * 0.3,
165
+ }}
166
+ />
167
+ )}
168
+
169
+ {/* Empty state message */}
170
+ {slideEmpty && (
171
+ <div
172
+ className="absolute inset-0 flex items-center justify-center pointer-events-none"
173
+ style={{ zIndex: 5 }}
174
+ >
175
+ <div className="text-center px-6 py-4 rounded-lg" style={{ background: "rgba(139, 92, 246, 0.06)" }}>
176
+ <p className="text-[12px] font-medium text-[#8b5cf6] mb-1">
177
+ Empty slide
178
+ </p>
179
+ <p className="text-[11px] text-neutral-400 leading-relaxed">
180
+ Set a background image and add content to this slide
181
+ </p>
182
+ </div>
183
+ </div>
184
+ )}
185
+
186
+ {/* V2 section grid editor — stretch to fill slide height */}
187
+ <div
188
+ className="relative"
189
+ style={{
190
+ minHeight: slidePreviewHeight,
191
+ display: "flex",
192
+ flexDirection: "column",
193
+ }}
194
+ >
195
+ <SectionV2Canvas
196
+ section={virtualSection}
197
+ onAddBlockTarget={onAddBlockTarget}
198
+ fillHeight
199
+ />
200
+ </div>
201
+ </div>
202
+ </div>
203
+ );
204
+ })}
205
+
206
+ {/* Add Slide button */}
207
+ <div
208
+ className="flex justify-center py-3"
209
+ style={{
210
+ background: "linear-gradient(135deg, #f9f7ff 0%, #f3f0ff 100%)",
211
+ borderTop: "1px solid rgba(139, 92, 246, 0.1)",
212
+ borderRadius: "0 0 12px 12px",
213
+ }}
214
+ >
215
+ <button
216
+ className="flex items-center gap-1.5 rounded-lg px-4 py-1.5 text-[11px] font-medium text-white transition-colors hover:opacity-90"
217
+ style={{ background: BUILDER_GREEN }}
218
+ onClick={(e) => {
219
+ e.stopPropagation();
220
+ store.addParallaxSlide(group._key);
221
+ }}
222
+ >
223
+ <span className="text-sm leading-none">+</span> Add Slide
224
+ </button>
225
+ </div>
226
+ </div>
227
+ );
228
+ }
@@ -0,0 +1,113 @@
1
+ "use client";
2
+
3
+ import { useBuilderStore } from "../../lib/builder/store";
4
+ import type { ParallaxSlideV2 } from "../../lib/sanity/types";
5
+ import { BUILDER_GREEN } from "../../lib/builder/constants";
6
+
7
+ /**
8
+ * ParallaxSlideHeader — thin header bar for each slide in a parallax group.
9
+ * Shows slide index, background preview thumbnail, settings icon,
10
+ * reorder arrows, and delete button.
11
+ *
12
+ * Session 123: Parallax V2 Phase 2
13
+ */
14
+
15
+ interface ParallaxSlideHeaderProps {
16
+ slide: ParallaxSlideV2;
17
+ slideIndex: number;
18
+ totalSlides: number;
19
+ groupKey: string;
20
+ isSelected: boolean;
21
+ onSelect: () => void;
22
+ }
23
+
24
+ export default function ParallaxSlideHeader({
25
+ slide,
26
+ slideIndex,
27
+ totalSlides,
28
+ groupKey,
29
+ isSelected,
30
+ onSelect,
31
+ }: ParallaxSlideHeaderProps) {
32
+ const store = useBuilderStore();
33
+ const hasBg = slide.background_type === "image"
34
+ ? !!slide.background_image
35
+ : !!slide.background_video;
36
+
37
+ return (
38
+ <div
39
+ className="flex items-center gap-2 px-3 py-1.5 rounded-t-lg cursor-pointer select-none transition-colors"
40
+ style={{
41
+ background: isSelected
42
+ ? "linear-gradient(135deg, #c8a8ff 0%, #d8b8ff 100%)"
43
+ : "#f5f0ff",
44
+ borderBottom: "1px solid rgba(139, 92, 246, 0.15)",
45
+ }}
46
+ onClick={(e) => {
47
+ e.stopPropagation();
48
+ onSelect();
49
+ }}
50
+ >
51
+ {/* Slide number */}
52
+ <span
53
+ className="flex items-center justify-center rounded-md text-[10px] font-bold text-white min-w-[22px] h-[22px] px-1"
54
+ style={{ background: "#8b5cf6" }}
55
+ >
56
+ {slideIndex + 1}
57
+ </span>
58
+
59
+ {/* Background type indicator */}
60
+ <span className="text-[10px] text-neutral-500 uppercase tracking-wider font-medium">
61
+ {slide.background_type === "video" ? "Video BG" : "Image BG"}
62
+ {hasBg && <span className="ml-1 text-green-500">●</span>}
63
+ </span>
64
+
65
+ {/* Spacer */}
66
+ <div className="flex-1" />
67
+
68
+ {/* Reorder arrows */}
69
+ <button
70
+ className="p-0.5 text-neutral-400 hover:text-neutral-700 disabled:opacity-30 transition-colors"
71
+ disabled={slideIndex === 0}
72
+ onClick={(e) => {
73
+ e.stopPropagation();
74
+ store.moveParallaxSlide(groupKey, slide._key, "up");
75
+ }}
76
+ title="Move slide up"
77
+ >
78
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
79
+ <path d="M6 3L9 7H3L6 3Z" fill="currentColor" />
80
+ </svg>
81
+ </button>
82
+ <button
83
+ className="p-0.5 text-neutral-400 hover:text-neutral-700 disabled:opacity-30 transition-colors"
84
+ disabled={slideIndex === totalSlides - 1}
85
+ onClick={(e) => {
86
+ e.stopPropagation();
87
+ store.moveParallaxSlide(groupKey, slide._key, "down");
88
+ }}
89
+ title="Move slide down"
90
+ >
91
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
92
+ <path d="M6 9L3 5H9L6 9Z" fill="currentColor" />
93
+ </svg>
94
+ </button>
95
+
96
+ {/* Delete slide */}
97
+ {totalSlides > 1 && (
98
+ <button
99
+ className="p-0.5 text-neutral-400 hover:text-red-500 transition-colors"
100
+ onClick={(e) => {
101
+ e.stopPropagation();
102
+ store.removeParallaxSlide(groupKey, slide._key);
103
+ }}
104
+ title="Delete slide"
105
+ >
106
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
107
+ <path d="M3 3L9 9M9 3L3 9" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
108
+ </svg>
109
+ </button>
110
+ )}
111
+ </div>
112
+ );
113
+ }