@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,488 @@
1
+ "use client";
2
+
3
+ import { useBuilderStore } from "../../../lib/builder/store";
4
+ import { getEffectiveValue, setResponsiveOverride } from "../../../lib/builder/responsive";
5
+ import type { CoverBlock, ContentBlock } from "../../../lib/sanity/types";
6
+ import {
7
+ SettingsField,
8
+ SettingsSection,
9
+ StyledInput,
10
+ StyledCheckbox,
11
+ AssetPathInput,
12
+ ViewportBadge,
13
+ ResponsiveField,
14
+ useActiveViewport,
15
+ SELECT_CLASS,
16
+ } from "./shared";
17
+ import ColorSwatchPicker, { usePaletteSwatches } from "../ColorSwatchPicker";
18
+
19
+ interface Props {
20
+ block: CoverBlock;
21
+ }
22
+
23
+ // ============================================
24
+ // 9-point position grid (Semplice-style)
25
+ // ============================================
26
+
27
+ const POSITIONS = [
28
+ { label: "↖", value: "top left" },
29
+ { label: "↑", value: "top center" },
30
+ { label: "↗", value: "top right" },
31
+ { label: "←", value: "center left" },
32
+ { label: "·", value: "center center" },
33
+ { label: "→", value: "center right" },
34
+ { label: "↙", value: "bottom left" },
35
+ { label: "↓", value: "bottom center" },
36
+ { label: "↘", value: "bottom right" },
37
+ ];
38
+
39
+ function PositionGrid({
40
+ value,
41
+ onChange,
42
+ }: {
43
+ value: string;
44
+ onChange: (v: string) => void;
45
+ }) {
46
+ return (
47
+ <div className="grid grid-cols-3 gap-0.5 w-[84px]">
48
+ {POSITIONS.map((pos) => (
49
+ <button
50
+ key={pos.value}
51
+ onClick={() => onChange(pos.value)}
52
+ className={`w-7 h-7 rounded text-[10px] flex items-center justify-center transition-colors ${
53
+ value === pos.value
54
+ ? "bg-[#076bff] text-white"
55
+ : "bg-neutral-100 text-neutral-400 hover:bg-neutral-200"
56
+ }`}
57
+ title={pos.value}
58
+ >
59
+ {pos.label}
60
+ </button>
61
+ ))}
62
+ </div>
63
+ );
64
+ }
65
+
66
+ // ============================================
67
+ // 3x3 content alignment grid
68
+ // ============================================
69
+
70
+ function ContentAlignGrid({
71
+ alignH,
72
+ alignV,
73
+ onChangeH,
74
+ onChangeV,
75
+ }: {
76
+ alignH: string;
77
+ alignV: string;
78
+ onChangeH: (v: "left" | "center" | "right") => void;
79
+ onChangeV: (v: "top" | "center" | "bottom") => void;
80
+ }) {
81
+ const vOptions: Array<{ label: string; value: "top" | "center" | "bottom" }> = [
82
+ { label: "T", value: "top" },
83
+ { label: "M", value: "center" },
84
+ { label: "B", value: "bottom" },
85
+ ];
86
+ const hOptions: Array<{ label: string; value: "left" | "center" | "right" }> = [
87
+ { label: "L", value: "left" },
88
+ { label: "C", value: "center" },
89
+ { label: "R", value: "right" },
90
+ ];
91
+
92
+ return (
93
+ <div className="grid grid-cols-3 gap-0.5 w-[84px]">
94
+ {vOptions.map((v) =>
95
+ hOptions.map((h) => {
96
+ const isActive = alignH === h.value && alignV === v.value;
97
+ return (
98
+ <button
99
+ key={`${v.value}-${h.value}`}
100
+ onClick={() => {
101
+ onChangeH(h.value);
102
+ onChangeV(v.value);
103
+ }}
104
+ className={`w-7 h-7 rounded flex items-center justify-center transition-colors ${
105
+ isActive
106
+ ? "bg-[#076bff] text-white"
107
+ : "bg-neutral-100 text-neutral-400 hover:bg-neutral-200"
108
+ }`}
109
+ title={`${v.value} ${h.value}`}
110
+ >
111
+ <div
112
+ className={`w-1.5 h-1.5 rounded-full ${
113
+ isActive ? "bg-white" : "bg-neutral-400"
114
+ }`}
115
+ />
116
+ </button>
117
+ );
118
+ })
119
+ )}
120
+ </div>
121
+ );
122
+ }
123
+
124
+ // ============================================
125
+ // Main Editor
126
+ // ============================================
127
+
128
+ export default function CoverBlockEditor({ block }: Props) {
129
+ const store = useBuilderStore();
130
+ const viewport = useActiveViewport();
131
+ const paletteSwatches = usePaletteSwatches();
132
+ const cta = block.cta_button || {};
133
+
134
+ const snapshotOnFocus = () => store._pushSnapshot();
135
+
136
+ // Responsive-aware update
137
+ const updateResponsive = (property: string, value: unknown) => {
138
+ if (viewport === "desktop") {
139
+ store.updateBlock(block._key, { [property]: value } as Partial<ContentBlock>);
140
+ } else {
141
+ const overrides = setResponsiveOverride(block as ContentBlock, viewport, property, value);
142
+ store.updateBlock(block._key, overrides as Partial<ContentBlock>);
143
+ }
144
+ };
145
+
146
+ const updateResponsiveDebounced = (property: string, value: unknown) => {
147
+ if (viewport === "desktop") {
148
+ store.updateBlockDebounced(block._key, { [property]: value } as Partial<ContentBlock>);
149
+ } else {
150
+ const overrides = setResponsiveOverride(block as ContentBlock, viewport, property, value);
151
+ store.updateBlockDebounced(block._key, overrides as Partial<ContentBlock>);
152
+ }
153
+ };
154
+
155
+ const resetOverride = (property: string) => {
156
+ const overrides = setResponsiveOverride(block as ContentBlock, viewport, property, undefined);
157
+ store.updateBlock(block._key, overrides as Partial<ContentBlock>);
158
+ };
159
+
160
+ // Direct update (base block, not responsive)
161
+ const update = (updates: Partial<CoverBlock>) => {
162
+ store.updateBlock(block._key, updates as Partial<ContentBlock>);
163
+ };
164
+
165
+ const updateDebounced = (updates: Partial<CoverBlock>) => {
166
+ store.updateBlockDebounced(block._key, updates as Partial<ContentBlock>);
167
+ };
168
+
169
+ // Effective values for the active viewport
170
+ const effectiveHeight = getEffectiveValue<string>(
171
+ block as ContentBlock, viewport, "height", block.height || "100vh"
172
+ );
173
+ const effectiveCustomHeight = getEffectiveValue<string>(
174
+ block as ContentBlock, viewport, "custom_height", block.custom_height || ""
175
+ );
176
+ const effectiveAlignH = getEffectiveValue<string>(
177
+ block as ContentBlock, viewport, "content_align_h", block.content_align_h || "center"
178
+ );
179
+ const effectiveAlignV = getEffectiveValue<string>(
180
+ block as ContentBlock, viewport, "content_align_v", block.content_align_v || "center"
181
+ );
182
+ const effectiveMaxWidth = getEffectiveValue<string>(
183
+ block as ContentBlock, viewport, "content_max_width", block.content_max_width || "800px"
184
+ );
185
+
186
+ return (
187
+ <>
188
+ <ViewportBadge />
189
+
190
+ {/* ========== CONTENT ========== */}
191
+ <SettingsSection title="Content" defaultOpen>
192
+ <SettingsField label="Headline">
193
+ <StyledInput
194
+ value={block.headline || ""}
195
+ onFocus={snapshotOnFocus}
196
+ onChange={(v) => updateDebounced({ headline: v })}
197
+ placeholder="Page Headline"
198
+ />
199
+ </SettingsField>
200
+
201
+ <SettingsField label="Subheadline">
202
+ <StyledInput
203
+ value={block.subheadline || ""}
204
+ onFocus={snapshotOnFocus}
205
+ onChange={(v) => updateDebounced({ subheadline: v })}
206
+ placeholder="Supporting text"
207
+ />
208
+ </SettingsField>
209
+ </SettingsSection>
210
+
211
+ {/* ========== CTA BUTTON ========== */}
212
+ <SettingsSection title="CTA Button">
213
+ <SettingsField label="Button Text">
214
+ <StyledInput
215
+ value={cta.text || ""}
216
+ onFocus={snapshotOnFocus}
217
+ onChange={(v) =>
218
+ updateDebounced({ cta_button: { ...cta, text: v } })
219
+ }
220
+ placeholder="View Project"
221
+ />
222
+ </SettingsField>
223
+
224
+ <SettingsField label="Button URL">
225
+ <StyledInput
226
+ value={cta.url || ""}
227
+ onFocus={snapshotOnFocus}
228
+ onChange={(v) =>
229
+ updateDebounced({ cta_button: { ...cta, url: v } })
230
+ }
231
+ placeholder="/work/project-name"
232
+ />
233
+ </SettingsField>
234
+
235
+ <SettingsField label="Style">
236
+ <div className="flex gap-1">
237
+ {(["primary", "secondary", "outline", "text"] as const).map((s) => (
238
+ <button
239
+ key={s}
240
+ onClick={() =>
241
+ update({ cta_button: { ...cta, style: s } })
242
+ }
243
+ className={`flex-1 rounded border py-1 text-[10px] capitalize transition-colors ${
244
+ (cta.style || "primary") === s
245
+ ? "border-[#076bff] bg-[#076bff]/20 text-neutral-900"
246
+ : "border-neutral-200 bg-white text-neutral-500 hover:border-neutral-600"
247
+ }`}
248
+ >
249
+ {s}
250
+ </button>
251
+ ))}
252
+ </div>
253
+ </SettingsField>
254
+
255
+ <StyledCheckbox
256
+ label="Open in new tab"
257
+ checked={cta.target_blank || false}
258
+ onChange={(v) =>
259
+ update({ cta_button: { ...cta, target_blank: v } })
260
+ }
261
+ />
262
+ </SettingsSection>
263
+
264
+ {/* ========== MEDIA BACKGROUND ========== */}
265
+ <SettingsSection title="Cover Background" defaultOpen>
266
+ <SettingsField label="Media Type">
267
+ <div className="flex gap-1">
268
+ {(["image", "video"] as const).map((t) => (
269
+ <button
270
+ key={t}
271
+ onClick={() => update({ media_type: t })}
272
+ className={`flex-1 rounded border py-1.5 text-xs capitalize transition-colors ${
273
+ (block.media_type || "image") === t
274
+ ? "border-[#076bff] bg-[#076bff]/20 text-neutral-900"
275
+ : "border-neutral-200 bg-white text-neutral-500 hover:border-neutral-600"
276
+ }`}
277
+ >
278
+ {t}
279
+ </button>
280
+ ))}
281
+ </div>
282
+ </SettingsField>
283
+
284
+ <SettingsField
285
+ label={block.media_type === "video" ? "Video Path" : "Image Path"}
286
+ >
287
+ <AssetPathInput
288
+ value={block.media_path || ""}
289
+ onFocus={snapshotOnFocus}
290
+ onChange={(v) => updateDebounced({ media_path: v })}
291
+ placeholder={
292
+ block.media_type === "video"
293
+ ? "projects/cover.mp4"
294
+ : "projects/cover.jpg"
295
+ }
296
+ filterType={block.media_type === "video" ? "video" : "image"}
297
+ />
298
+ </SettingsField>
299
+
300
+ {block.media_type === "video" && (
301
+ <SettingsField label="Poster Image" hint="Fallback while video loads">
302
+ <AssetPathInput
303
+ value={block.video_poster || ""}
304
+ onFocus={snapshotOnFocus}
305
+ onChange={(v) => updateDebounced({ video_poster: v })}
306
+ placeholder="projects/cover-poster.jpg"
307
+ filterType="image"
308
+ />
309
+ </SettingsField>
310
+ )}
311
+
312
+ <SettingsField label="Size">
313
+ <div className="flex gap-1">
314
+ {(["cover", "contain", "none"] as const).map((s) => (
315
+ <button
316
+ key={s}
317
+ onClick={() => update({ background_size: s })}
318
+ className={`flex-1 rounded border py-1 text-[10px] transition-colors ${
319
+ (block.background_size || "cover") === s
320
+ ? "border-[#076bff] bg-[#076bff]/20 text-neutral-900"
321
+ : "border-neutral-200 bg-white text-neutral-500 hover:border-neutral-600"
322
+ }`}
323
+ >
324
+ {s === "none" ? "No Scale" : s.charAt(0).toUpperCase() + s.slice(1)}
325
+ </button>
326
+ ))}
327
+ </div>
328
+ </SettingsField>
329
+
330
+ <SettingsField label="Position">
331
+ <PositionGrid
332
+ value={block.background_position || "center center"}
333
+ onChange={(v) => update({ background_position: v })}
334
+ />
335
+ </SettingsField>
336
+
337
+ <SettingsField label="Repeat">
338
+ <select
339
+ value={block.background_repeat || "no-repeat"}
340
+ onChange={(e) =>
341
+ update({
342
+ background_repeat: e.target.value as CoverBlock["background_repeat"],
343
+ })
344
+ }
345
+ className={SELECT_CLASS}
346
+ >
347
+ <option value="no-repeat">No Repeat</option>
348
+ <option value="repeat">Repeat</option>
349
+ <option value="repeat-x">Repeat X</option>
350
+ <option value="repeat-y">Repeat Y</option>
351
+ </select>
352
+ </SettingsField>
353
+ </SettingsSection>
354
+
355
+ {/* ========== OVERLAY ========== */}
356
+ <SettingsSection title="Cover Effects">
357
+ <SettingsField label="Overlay">
358
+ <select
359
+ value={block.overlay || "dark"}
360
+ onChange={(e) =>
361
+ update({
362
+ overlay: e.target.value as CoverBlock["overlay"],
363
+ })
364
+ }
365
+ className={SELECT_CLASS}
366
+ >
367
+ <option value="none">None</option>
368
+ <option value="dark">Dark</option>
369
+ <option value="light">Light</option>
370
+ <option value="gradient-bottom">Gradient (Bottom)</option>
371
+ <option value="gradient-top">Gradient (Top)</option>
372
+ </select>
373
+ </SettingsField>
374
+
375
+ {block.overlay && block.overlay !== "none" && (
376
+ <SettingsField label="Opacity">
377
+ <div className="flex items-center gap-2">
378
+ <input
379
+ type="range"
380
+ min={0}
381
+ max={100}
382
+ value={block.overlay_opacity ?? 50}
383
+ onChange={(e) =>
384
+ update({ overlay_opacity: parseInt(e.target.value, 10) })
385
+ }
386
+ className="flex-1 accent-[#076bff]"
387
+ />
388
+ <span className="text-xs text-neutral-500 w-8 text-right">
389
+ {block.overlay_opacity ?? 50}%
390
+ </span>
391
+ </div>
392
+ </SettingsField>
393
+ )}
394
+ </SettingsSection>
395
+
396
+ {/* ========== LAYOUT ========== */}
397
+ <SettingsSection title="Layout">
398
+ <ResponsiveField
399
+ label="Content Pos"
400
+ block={block as ContentBlock}
401
+ property="content_align_h"
402
+ onReset={() => {
403
+ resetOverride("content_align_h");
404
+ resetOverride("content_align_v");
405
+ }}
406
+ >
407
+ <ContentAlignGrid
408
+ alignH={effectiveAlignH}
409
+ alignV={effectiveAlignV}
410
+ onChangeH={(v) => updateResponsive("content_align_h", v)}
411
+ onChangeV={(v) => updateResponsive("content_align_v", v)}
412
+ />
413
+ </ResponsiveField>
414
+
415
+ <ResponsiveField
416
+ label="Max Width"
417
+ block={block as ContentBlock}
418
+ property="content_max_width"
419
+ hint="Text container width"
420
+ onReset={() => resetOverride("content_max_width")}
421
+ >
422
+ <StyledInput
423
+ value={effectiveMaxWidth}
424
+ onFocus={snapshotOnFocus}
425
+ onChange={(v) => updateResponsiveDebounced("content_max_width", v)}
426
+ placeholder="800px"
427
+ />
428
+ </ResponsiveField>
429
+
430
+ <ResponsiveField
431
+ label="Height"
432
+ block={block as ContentBlock}
433
+ property="height"
434
+ onReset={() => resetOverride("height")}
435
+ >
436
+ <select
437
+ value={effectiveHeight}
438
+ onChange={(e) =>
439
+ updateResponsive("height", e.target.value)
440
+ }
441
+ className={SELECT_CLASS}
442
+ >
443
+ <option value="100vh">Full Screen (100vh)</option>
444
+ <option value="80vh">Large (80vh)</option>
445
+ <option value="60vh">Medium (60vh)</option>
446
+ <option value="40vh">Small (40vh)</option>
447
+ <option value="custom">Custom</option>
448
+ </select>
449
+ </ResponsiveField>
450
+
451
+ {effectiveHeight === "custom" && (
452
+ <ResponsiveField
453
+ label="Custom Height"
454
+ block={block as ContentBlock}
455
+ property="custom_height"
456
+ onReset={() => resetOverride("custom_height")}
457
+ >
458
+ <StyledInput
459
+ value={effectiveCustomHeight}
460
+ onFocus={snapshotOnFocus}
461
+ onChange={(v) => updateResponsiveDebounced("custom_height", v)}
462
+ placeholder="500px, 70vh"
463
+ />
464
+ </ResponsiveField>
465
+ )}
466
+
467
+ {/* Legacy mobile_height removed — use tablet/phone viewport overrides instead */}
468
+ </SettingsSection>
469
+
470
+ {/* ========== APPEARANCE ========== */}
471
+ <SettingsSection title="Appearance">
472
+ <SettingsField label="Text Color">
473
+ <ColorSwatchPicker
474
+ value={block.text_color || ""}
475
+ onChange={(hex) => update({ text_color: hex || "#ffffff" })}
476
+ swatches={paletteSwatches}
477
+ />
478
+ </SettingsField>
479
+
480
+ <StyledCheckbox
481
+ label="Show scroll down arrow"
482
+ checked={block.show_scroll_indicator || false}
483
+ onChange={(v) => update({ show_scroll_indicator: v })}
484
+ />
485
+ </SettingsSection>
486
+ </>
487
+ );
488
+ }