@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,297 @@
1
+ "use client";
2
+
3
+ import { useState, useRef, useEffect, useMemo } from "react";
4
+ import { useBuilderStore } from "../../lib/builder/store";
5
+ import { findGaps } from "../../lib/builder/cascade";
6
+ import type { PageSectionV2, SectionColumn } from "../../lib/sanity/types";
7
+ import { getEffectiveColumnsV2, getSectionV2SettingValue } from "./settings-panel/responsive-helpers";
8
+ import SectionV2Column from "./SectionV2Column";
9
+ import SortableBlock from "./SortableBlock";
10
+ import { useColumnDragContext } from "./ColumnDragContext";
11
+ import { useColumnResize } from "./hooks/useColumnResize";
12
+ import { InsertionLines } from "./InsertionLines";
13
+
14
+ // ============================================
15
+ // SectionV2Canvas — Renders a V2 section in the builder
16
+ //
17
+ // Phase 3: Refined resize with debounced store updates,
18
+ // real-time cascade preview, snapping guide, column drag,
19
+ // and insertion lines between adjacent columns.
20
+ // ============================================
21
+
22
+ interface SectionV2CanvasProps {
23
+ section: PageSectionV2;
24
+ onAddBlockTarget: (sectionKey: string, colKey: string, insertIndex?: number) => void;
25
+ /** When true, the grid stretches to fill its parent height (used inside parallax slides) */
26
+ fillHeight?: boolean;
27
+ }
28
+
29
+ export default function SectionV2Canvas({
30
+ section,
31
+ onAddBlockTarget,
32
+ fillHeight,
33
+ }: SectionV2CanvasProps) {
34
+ const previewMode = useBuilderStore((s) => s.previewMode);
35
+ const canvasZoom = useBuilderStore((s) => s.canvasZoom);
36
+ const activeViewport = useBuilderStore((s) => s.activeViewport);
37
+ const selectedColumnKey = useBuilderStore((s) => s.selectedColumnKey);
38
+ const selectedBlockKey = useBuilderStore((s) => s.selectedBlockKey);
39
+ const addColumnV2 = useBuilderStore((s) => s.addColumnV2);
40
+ const deleteColumnV2 = useBuilderStore((s) => s.deleteColumnV2);
41
+ const resizeColumnV2 = useBuilderStore((s) => s.resizeColumnV2);
42
+ const resizeColumnV2Left = useBuilderStore((s) => s.resizeColumnV2Left);
43
+ const updateSectionV2Responsive = useBuilderStore((s) => s.updateSectionV2Responsive);
44
+ const selectColumnV2 = useBuilderStore((s) => s.selectColumnV2);
45
+ const selectBlock = useBuilderStore((s) => s.selectBlock);
46
+ const deleteBlock = useBuilderStore((s) => s.deleteBlock);
47
+ const duplicateBlock = useBuilderStore((s) => s.duplicateBlock);
48
+
49
+ const isResponsive = activeViewport !== "desktop";
50
+ const gridColumns = section.settings.grid_columns || 12;
51
+ const colGap = getSectionV2SettingValue(section, activeViewport, "col_gap", 20);
52
+ const rowGap = getSectionV2SettingValue(section, activeViewport, "row_gap", 20);
53
+
54
+ const [isSectionHovered, setIsSectionHovered] = useState(false);
55
+ const gridContainerRef = useRef<HTMLDivElement>(null);
56
+ const [containerWidth, setContainerWidth] = useState(0);
57
+
58
+ // Custom column drag context (replaces dnd-kit useDndContext for column drag)
59
+ const {
60
+ isDragging: isColDragActive,
61
+ draggedColumnKey: draggingColumnKey,
62
+ draggedSectionKey: draggingSectionKey,
63
+ dropTarget,
64
+ insertBetween,
65
+ startDrag,
66
+ } = useColumnDragContext();
67
+
68
+ // Only show insertion lines if the drag originates from THIS section
69
+ const showInsertionLines = isColDragActive && draggingSectionKey === section._key;
70
+
71
+ // When drag ends (isColDragActive becomes false), reset hover state so it
72
+ // doesn't stay stuck true if the pointer ended outside the grid.
73
+ const prevDragActiveRef = useRef(false);
74
+ useEffect(() => {
75
+ if (prevDragActiveRef.current && !isColDragActive) {
76
+ setIsSectionHovered(false);
77
+ }
78
+ prevDragActiveRef.current = isColDragActive;
79
+ }, [isColDragActive]);
80
+
81
+ // Responsive-aware effective column positions
82
+ const effectiveCols = useMemo(
83
+ () => getEffectiveColumnsV2(section, activeViewport),
84
+ [section, activeViewport]
85
+ );
86
+
87
+ // --- Resize logic (extracted to hook) ---
88
+ const { resizePreview, snappingInfo, handleResizeRight, handleResizeLeft } = useColumnResize({
89
+ section,
90
+ gridColumns,
91
+ colGap,
92
+ canvasZoom,
93
+ effectiveCols,
94
+ isResponsive,
95
+ activeViewport,
96
+ resizeColumnV2,
97
+ resizeColumnV2Left,
98
+ updateSectionV2Responsive,
99
+ });
100
+
101
+ // Measure container width for pixel position calculation.
102
+ // Uses ResizeObserver so it updates when the grid resizes (zoom change, window resize)
103
+ // and is also immediately available when a drag starts (no stale 0 value).
104
+ useEffect(() => {
105
+ const el = gridContainerRef.current;
106
+ if (!el) return;
107
+ const ro = new ResizeObserver((entries) => {
108
+ for (const entry of entries) {
109
+ setContainerWidth(entry.contentRect.width);
110
+ }
111
+ });
112
+ ro.observe(el);
113
+ // Set initial value immediately
114
+ setContainerWidth(el.getBoundingClientRect().width);
115
+ return () => ro.disconnect();
116
+ }, []);
117
+
118
+ // Use preview columns during resize, otherwise responsive-aware effective columns
119
+ const displayCols = useMemo(() => {
120
+ if (resizePreview) return resizePreview;
121
+ return effectiveCols;
122
+ }, [resizePreview, effectiveCols]);
123
+
124
+ // Compute gaps from display columns
125
+ const gaps = useMemo(() => findGaps(displayCols, gridColumns), [displayCols, gridColumns]);
126
+
127
+ // ---- Grid container ----
128
+ return (
129
+ <div
130
+ ref={gridContainerRef}
131
+ data-v2-grid-container
132
+ data-v2-section-key={section._key}
133
+ style={{
134
+ display: "grid",
135
+ gridTemplateColumns: `repeat(${gridColumns}, 1fr)`,
136
+ ...(fillHeight ? { gridTemplateRows: "1fr" } : {}),
137
+ columnGap: `${colGap}px`,
138
+ rowGap: `${rowGap}px`,
139
+ position: "relative",
140
+ ...(fillHeight ? { flex: 1, minHeight: 0 } : {}),
141
+ }}
142
+ onMouseEnter={() => setIsSectionHovered(true)}
143
+ onMouseLeave={() => {
144
+ // Don't clear hover during column drag — the DragOverlay steals the pointer
145
+ // but we still want gaps and column outlines visible as drop targets.
146
+ if (!isColDragActive) setIsSectionHovered(false);
147
+ }}
148
+ >
149
+ {/* Snapping guide overlay — shows a pulsing line when resize would cause overflow */}
150
+ {snappingInfo?.willOverflow && (
151
+ <div
152
+ className="pointer-events-none absolute left-0 right-0 z-[10]"
153
+ style={{
154
+ bottom: 0,
155
+ height: 2,
156
+ background: "linear-gradient(90deg, transparent, #ff6b35, #ff6b35, transparent)",
157
+ animation: "pulse 1s ease-in-out infinite",
158
+ opacity: 0.8,
159
+ }}
160
+ />
161
+ )}
162
+
163
+ {/* Columns — use displayCols for positions but real data for blocks */}
164
+ {section.columns.map((col) => {
165
+ const displayCol = displayCols.find((dc) => dc._key === col._key);
166
+ const effectiveGridColumn = displayCol?.grid_column ?? col.grid_column;
167
+ const effectiveGridRow = displayCol?.grid_row ?? col.grid_row;
168
+ const effectiveSpan = displayCol?.span ?? col.span;
169
+
170
+ const virtualCol: SectionColumn = {
171
+ ...col,
172
+ grid_column: effectiveGridColumn,
173
+ grid_row: effectiveGridRow,
174
+ span: effectiveSpan,
175
+ };
176
+
177
+ return (
178
+ <SectionV2Column
179
+ key={col._key}
180
+ column={virtualCol}
181
+ sectionKey={section._key}
182
+ section={section}
183
+ isSelected={selectedColumnKey === col._key}
184
+ isSectionHovered={isSectionHovered}
185
+ isResizing={resizePreview !== null}
186
+ snappingInfo={
187
+ snappingInfo?.columnKey === col._key
188
+ ? snappingInfo
189
+ : snappingInfo?.compressedNeighborKey === col._key
190
+ ? snappingInfo
191
+ : null
192
+ }
193
+ insertNudge={
194
+ insertBetween?.leftKey === col._key
195
+ ? "left"
196
+ : insertBetween?.rightKey === col._key
197
+ ? "right"
198
+ : null
199
+ }
200
+ isSwapTarget={
201
+ dropTarget?.type === "swap" &&
202
+ dropTarget.columnKey === col._key &&
203
+ dropTarget.sectionKey === section._key
204
+ }
205
+ isDraggedColumn={
206
+ draggingColumnKey === col._key &&
207
+ draggingSectionKey === section._key
208
+ }
209
+ onStartDrag={(e: React.MouseEvent) =>
210
+ startDrag(e, section._key, col._key)
211
+ }
212
+ containerWidth={containerWidth}
213
+ onSelect={() => selectColumnV2(section._key, col._key)}
214
+ onDelete={() => deleteColumnV2(section._key, col._key)}
215
+ onAddBlock={(insertIndex) =>
216
+ onAddBlockTarget(section._key, col._key, insertIndex)
217
+ }
218
+ onResizeRight={handleResizeRight}
219
+ onResizeLeft={handleResizeLeft}
220
+ >
221
+ {(col.blocks || []).map((block, blockIdx) => (
222
+ <SortableBlock
223
+ key={block._key}
224
+ block={block}
225
+ rowKey={section._key}
226
+ colKey={col._key}
227
+ blockIndex={blockIdx}
228
+ totalBlocks={col.blocks?.length || 0}
229
+ isSelected={selectedBlockKey === block._key}
230
+ onSelect={() => selectBlock(block._key)}
231
+ onDelete={() => deleteBlock(block._key)}
232
+ onContextMenu={() => {}}
233
+ onDuplicate={() => duplicateBlock(block._key)}
234
+ />
235
+ ))}
236
+ </SectionV2Column>
237
+ );
238
+ })}
239
+
240
+ {/* Gap buttons: "+ Add Column" — also act as hit-test targets for custom column drag */}
241
+ {!previewMode &&
242
+ gaps.map((gap) => {
243
+ const isGapTarget =
244
+ dropTarget?.type === "gap" &&
245
+ dropTarget.sectionKey === section._key &&
246
+ dropTarget.gapRow === gap.grid_row &&
247
+ dropTarget.gapCol === gap.grid_column;
248
+ const showAsDropTarget = showInsertionLines;
249
+
250
+ return (
251
+ <div
252
+ key={`gap-${gap.grid_row}-${gap.grid_column}`}
253
+ data-col-v2-gap=""
254
+ data-section-key={section._key}
255
+ data-gap-row={gap.grid_row}
256
+ data-gap-col={gap.grid_column}
257
+ data-gap-span={gap.span}
258
+ onClick={(e) => {
259
+ e.stopPropagation();
260
+ addColumnV2(section._key, gap.grid_row, gap.grid_column, gap.span);
261
+ }}
262
+ style={{
263
+ gridColumn: `${gap.grid_column} / span ${gap.span}`,
264
+ gridRow: gap.grid_row,
265
+ minHeight: 70,
266
+ }}
267
+ className={`rounded-lg border-2 border-dashed text-xs font-medium transition-all flex items-center justify-center cursor-pointer ${
268
+ isGapTarget
269
+ ? "border-green-500 text-green-500 bg-green-500/10 opacity-100"
270
+ : showAsDropTarget
271
+ ? "border-green-500/40 text-green-500/60 bg-green-500/5 opacity-100"
272
+ : isSectionHovered
273
+ ? "border-[#076bff]/25 text-[#076bff]/50 hover:text-[#076bff] hover:border-[#076bff]/60 hover:bg-[#076bff]/5 opacity-100"
274
+ : "border-transparent text-transparent opacity-0 pointer-events-none"
275
+ }`}
276
+ >
277
+ {isGapTarget ? "Drop Here" : showAsDropTarget ? "Drop Here" : "+ Add Column"}
278
+ </div>
279
+ );
280
+ })}
281
+
282
+ {/* Insertion lines between adjacent columns — only during custom column drag */}
283
+ {showInsertionLines && (
284
+ <InsertionLines
285
+ displayCols={displayCols}
286
+ draggingColumnKey={draggingColumnKey}
287
+ containerWidth={containerWidth}
288
+ gridColumns={gridColumns}
289
+ colGap={colGap}
290
+ sectionKey={section._key}
291
+ dropTarget={dropTarget}
292
+ />
293
+ )}
294
+ </div>
295
+ );
296
+ }
297
+