@morphika/andami 0.5.1 → 0.5.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 (117) hide show
  1. package/app/admin/assets/page.tsx +6 -6
  2. package/app/admin/database/page.tsx +302 -302
  3. package/app/admin/error.tsx +53 -53
  4. package/app/admin/layout.tsx +320 -320
  5. package/app/admin/navigation/page.tsx +255 -255
  6. package/app/admin/pages/[slug]/page.tsx +6 -6
  7. package/app/admin/pages/page.tsx +11 -11
  8. package/app/admin/projects/page.tsx +14 -14
  9. package/app/admin/setup/page.tsx +1 -1
  10. package/app/admin/styles/page.tsx +1 -1
  11. package/components/admin/MetadataEditor.tsx +6 -6
  12. package/components/admin/nav-builder/NavBuilder.tsx +1 -1
  13. package/components/admin/nav-builder/NavBuilderGrid.tsx +3 -3
  14. package/components/admin/nav-builder/NavGridCell.tsx +48 -48
  15. package/components/admin/nav-builder/NavGridItem.tsx +4 -4
  16. package/components/admin/nav-builder/NavItemSettings.tsx +331 -331
  17. package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -102
  18. package/components/admin/nav-builder/NavLivePreview.tsx +1 -1
  19. package/components/admin/nav-builder/NavMobileLivePreview.tsx +226 -226
  20. package/components/admin/nav-builder/NavMobileSettings.tsx +242 -242
  21. package/components/admin/nav-builder/NavSettingsFields.tsx +514 -514
  22. package/components/admin/setup-wizard/BrandingStep.tsx +3 -3
  23. package/components/admin/setup-wizard/DatabaseStep.tsx +2 -2
  24. package/components/admin/setup-wizard/DoneStep.tsx +1 -1
  25. package/components/admin/setup-wizard/SetupWizard.tsx +4 -4
  26. package/components/admin/setup-wizard/StorageStep.tsx +2 -2
  27. package/components/admin/setup-wizard/WelcomeStep.tsx +2 -2
  28. package/components/admin/styles/ColorsEditor.tsx +2 -2
  29. package/components/admin/styles/FontsEditor.tsx +6 -6
  30. package/components/admin/styles/GridLayoutEditor.tsx +9 -9
  31. package/components/admin/styles/LinksButtonsEditor.tsx +5 -5
  32. package/components/admin/styles/TypographyEditor.tsx +6 -6
  33. package/components/admin/styles/shared.tsx +68 -68
  34. package/components/blocks/AudioBlockRenderer.tsx +286 -286
  35. package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
  36. package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
  37. package/components/builder/BlockCardIcons.tsx +316 -316
  38. package/components/builder/BlockTypePicker.tsx +1 -1
  39. package/components/builder/BubbleIcons.tsx +90 -0
  40. package/components/builder/BuilderCanvas.tsx +2 -0
  41. package/components/builder/CanvasMinimap.tsx +2 -2
  42. package/components/builder/CoverSectionCanvas.tsx +363 -363
  43. package/components/builder/DeviceFrame.tsx +1 -1
  44. package/components/builder/DndWrapper.tsx +3 -3
  45. package/components/builder/InsertionLines.tsx +1 -1
  46. package/components/builder/SectionCardIcons.tsx +421 -320
  47. package/components/builder/SectionEditorBar.tsx +1 -1
  48. package/components/builder/SectionTypePicker.tsx +4 -4
  49. package/components/builder/SectionV2Canvas.tsx +1 -1
  50. package/components/builder/SectionV2Column.tsx +69 -67
  51. package/components/builder/SortableBlock.tsx +93 -73
  52. package/components/builder/SortableRow.tsx +27 -26
  53. package/components/builder/VirtualAssetGrid.tsx +2 -2
  54. package/components/builder/asset-browser/R2BrowserContent.tsx +11 -11
  55. package/components/builder/blockStyles.tsx +192 -185
  56. package/components/builder/color-picker/AlphaSlider.tsx +141 -141
  57. package/components/builder/color-picker/ColorInputs.tsx +105 -105
  58. package/components/builder/color-picker/EyedropperButton.tsx +74 -74
  59. package/components/builder/color-picker/HueSlider.tsx +124 -124
  60. package/components/builder/color-picker/SaturationCanvas.tsx +142 -142
  61. package/components/builder/color-picker/SwatchBar.tsx +93 -93
  62. package/components/builder/editors/AudioBlockEditor.tsx +242 -242
  63. package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -360
  64. package/components/builder/editors/ButtonBlockEditor.tsx +4 -4
  65. package/components/builder/editors/EnterAnimationPicker.tsx +2 -2
  66. package/components/builder/editors/HoverEffectPicker.tsx +2 -2
  67. package/components/builder/editors/ImageBlockEditor.tsx +2 -2
  68. package/components/builder/editors/ImageGridBlockEditor.tsx +4 -4
  69. package/components/builder/editors/MarqueeBlockEditor.tsx +621 -0
  70. package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
  71. package/components/builder/editors/ProjectGridEditor.tsx +9 -9
  72. package/components/builder/editors/SpacerBlockEditor.tsx +5 -5
  73. package/components/builder/editors/StaggerSettings.tsx +109 -109
  74. package/components/builder/editors/TextBlockEditor.tsx +3 -3
  75. package/components/builder/editors/TextStylePicker.tsx +1 -1
  76. package/components/builder/editors/VideoBlockEditor.tsx +2 -2
  77. package/components/builder/editors/index.ts +11 -10
  78. package/components/builder/editors/shared.tsx +6 -6
  79. package/components/builder/live-preview/LiveAudioPreview.tsx +120 -120
  80. package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +1 -1
  81. package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
  82. package/components/builder/live-preview/LiveImagePreview.tsx +1 -1
  83. package/components/builder/live-preview/LiveMarqueePreview.tsx +39 -0
  84. package/components/builder/live-preview/LiveProjectCarouselPreview.tsx +1 -1
  85. package/components/builder/live-preview/LiveVideoPreview.tsx +1 -1
  86. package/components/builder/live-preview/ProjectCardWrapper.tsx +291 -291
  87. package/components/builder/settings-panel/AnimationTab.tsx +138 -138
  88. package/components/builder/settings-panel/BlockLayoutTab.tsx +7 -7
  89. package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
  90. package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
  91. package/components/builder/settings-panel/CoverSectionLayoutTab.tsx +71 -71
  92. package/components/builder/settings-panel/CoverSectionSettings.tsx +335 -335
  93. package/components/builder/settings-panel/PageSettings.tsx +3 -3
  94. package/components/builder/settings-panel/ParallaxSlideSettings.tsx +2 -2
  95. package/components/builder/settings-panel/SectionV2AnimationTab.tsx +4 -4
  96. package/components/builder/settings-panel/SectionV2LayoutTab.tsx +356 -356
  97. package/components/builder/settings-panel/SectionV2Settings.tsx +14 -14
  98. package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
  99. package/lib/animation/enter-types.ts +1 -0
  100. package/lib/animation/hover-effect-presets.ts +210 -210
  101. package/lib/animation/hover-effect-types.ts +1 -0
  102. package/lib/builder/block-registrations.ts +468 -417
  103. package/lib/builder/constants.ts +111 -111
  104. package/lib/builder/store-sections.ts +2 -2
  105. package/lib/builder/types-slices.ts +414 -414
  106. package/lib/builder/types.ts +4 -1
  107. package/lib/config/index.ts +27 -27
  108. package/lib/sanity/types.ts +98 -1
  109. package/lib/version.ts +1 -1
  110. package/package.json +1 -1
  111. package/sanity/schemas/blocks/audioBlock.ts +69 -69
  112. package/sanity/schemas/blocks/index.ts +12 -11
  113. package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
  114. package/sanity/schemas/index.ts +120 -117
  115. package/styles/admin.css +85 -85
  116. package/styles/animations.css +237 -237
  117. package/styles/base.css +114 -114
@@ -1,335 +1,335 @@
1
- "use client";
2
-
3
- /**
4
- * CoverSectionSettings — Settings panel for a Cover Section.
5
- *
6
- * Displays:
7
- * - Background type segmented control (Image / Video)
8
- * - AssetBrowser picker for image or video
9
- * - Background position / size dropdowns
10
- * - Overlay color + overlay opacity slider
11
- * - Section height selector (100vh / 80vh / 50vh)
12
- * - Row list with percentages and vertical align
13
- * - Add/Remove row controls
14
- *
15
- * Layout and Animation tabs are delegated to SectionV2LayoutTab / SectionV2AnimationTab
16
- * via a virtual PageSectionV2 (same pattern as parallax slides).
17
- *
18
- * Session 176: Cover Sections — Phase 6 (Settings Panel).
19
- */
20
-
21
- import { useBuilderStore } from "../../../lib/builder/store";
22
- import type { CoverSection } from "../../../lib/sanity/types";
23
- import {
24
- BackgroundIcon,
25
- NavbarColorIcon,
26
- OverlayIcon,
27
- SpacingIcon,
28
- GridGapsIcon,
29
- } from "../editors/section-icons";
30
- import {
31
- SettingsField,
32
- SettingsSection,
33
- } from "../editors/shared";
34
- import { AssetPathInput } from "../editors/shared";
35
- import ColorSwatchPicker, { usePaletteSwatches } from "../ColorSwatchPicker";
36
- import StaggerSettings from "../editors/StaggerSettings";
37
-
38
- const BG_POSITION_OPTIONS = [
39
- { value: "center center", label: "Center" },
40
- { value: "top center", label: "Top" },
41
- { value: "bottom center", label: "Bottom" },
42
- { value: "center left", label: "Left" },
43
- { value: "center right", label: "Right" },
44
- { value: "top left", label: "Top Left" },
45
- { value: "top right", label: "Top Right" },
46
- { value: "bottom left", label: "Bottom Left" },
47
- { value: "bottom right", label: "Bottom Right" },
48
- ];
49
-
50
- const BG_SIZE_OPTIONS = [
51
- { value: "cover", label: "Cover" },
52
- { value: "contain", label: "Contain" },
53
- { value: "auto", label: "Auto" },
54
- ];
55
-
56
- const HEIGHT_OPTIONS = [
57
- { value: "100vh", label: "Full Viewport (100vh)" },
58
- { value: "80vh", label: "80% Viewport (80vh)" },
59
- { value: "50vh", label: "50% Viewport (50vh)" },
60
- { value: "20vh", label: "20% Viewport (20vh)" },
61
- ];
62
-
63
- const ALIGN_OPTIONS = [
64
- { value: "start", label: "Top" },
65
- { value: "center", label: "Center" },
66
- { value: "end", label: "Bottom" },
67
- ];
68
-
69
- const SELECT_CLASS = "w-full rounded-lg border border-transparent bg-[#f5f5f5] px-2.5 py-[7px] text-xs text-neutral-900 font-normal outline-none transition-all hover:bg-[#efefef] focus:bg-white focus:border-[#076bff] focus:shadow-[0_0_0_3px_rgba(7,107,255,0.06)]";
70
-
71
- interface CoverSectionSettingsProps {
72
- section: CoverSection;
73
- }
74
-
75
- export default function CoverSectionSettings({ section }: CoverSectionSettingsProps) {
76
- const store = useBuilderStore();
77
- const paletteSwatches = usePaletteSwatches();
78
-
79
- const bgType = section.background_type || "image";
80
- const bgPosition = section.background_position || "center center";
81
- const bgSize = section.background_size || "cover";
82
- const overlayColor = section.background_overlay_color || "#000000";
83
- const overlayOpacity = section.background_overlay_opacity ?? 0;
84
-
85
- const updateBg = (fields: Partial<Pick<CoverSection,
86
- "background_type" | "background_color" | "background_image" | "background_video" |
87
- "background_position" | "background_size" |
88
- "background_overlay_color" | "background_overlay_opacity" |
89
- "nav_color"
90
- >>) => {
91
- store.updateCoverBackground(section._key, fields);
92
- };
93
-
94
- return (
95
- <>
96
- {/* Background */}
97
- <SettingsSection title="Background" defaultOpen icon={<BackgroundIcon />}>
98
- <SettingsField label="Type">
99
- <div className="flex rounded-lg bg-[#f0f0f0] p-[3px]">
100
- {(["image", "video", "color"] as const).map((type) => (
101
- <button
102
- key={type}
103
- onClick={() => updateBg({ background_type: type })}
104
- className={`flex-1 py-1.5 rounded-md text-[11px] font-medium transition-all ${
105
- bgType === type
106
- ? "bg-white text-neutral-900 shadow-sm border border-[#e5e5e5]"
107
- : "text-neutral-400 hover:text-neutral-500"
108
- }`}
109
- >
110
- {type === "image" ? "Image" : type === "video" ? "Video" : "Color"}
111
- </button>
112
- ))}
113
- </div>
114
- </SettingsField>
115
-
116
- {bgType === "color" ? (
117
- <SettingsField label="Color">
118
- <ColorSwatchPicker
119
- value={section.background_color || "#000000"}
120
- onChange={(val) => updateBg({ background_color: typeof val === "string" ? val : "" })}
121
- swatches={paletteSwatches}
122
- />
123
- </SettingsField>
124
- ) : (
125
- <>
126
- <SettingsField label={bgType === "image" ? "Image" : "Video"}>
127
- <AssetPathInput
128
- value={bgType === "image" ? (section.background_image || "") : (section.background_video || "")}
129
- onChange={(path) => {
130
- if (bgType === "image") {
131
- updateBg({ background_image: path });
132
- } else {
133
- updateBg({ background_video: path });
134
- }
135
- }}
136
- filterType={bgType === "image" ? "image" : "video"}
137
- placeholder={bgType === "image" ? "path/to/image.jpg" : "path/to/video.mp4"}
138
- />
139
- </SettingsField>
140
-
141
- <SettingsField label="Position">
142
- <select
143
- value={bgPosition}
144
- onChange={(e) => updateBg({ background_position: e.target.value })}
145
- className={SELECT_CLASS}
146
- >
147
- {BG_POSITION_OPTIONS.map((opt) => (
148
- <option key={opt.value} value={opt.value}>{opt.label}</option>
149
- ))}
150
- </select>
151
- </SettingsField>
152
-
153
- <SettingsField label="Size">
154
- <select
155
- value={bgSize}
156
- onChange={(e) => updateBg({ background_size: e.target.value as CoverSection["background_size"] })}
157
- className={SELECT_CLASS}
158
- >
159
- {BG_SIZE_OPTIONS.map((opt) => (
160
- <option key={opt.value} value={opt.value}>{opt.label}</option>
161
- ))}
162
- </select>
163
- </SettingsField>
164
- </>
165
- )}
166
- </SettingsSection>
167
-
168
- {/* Navbar Color Override */}
169
- <SettingsSection title="Navbar Color" defaultOpen={false} icon={<NavbarColorIcon />}>
170
- <SettingsField label="Color">
171
- <div className="flex items-center gap-2">
172
- <ColorSwatchPicker
173
- value={section.nav_color || ""}
174
- onChange={(val) => updateBg({ nav_color: typeof val === "string" ? val : undefined })}
175
- swatches={paletteSwatches}
176
- />
177
- {section.nav_color && (
178
- <button
179
- onClick={() => updateBg({ nav_color: undefined })}
180
- className="text-[10px] text-neutral-400 hover:text-neutral-600 transition-colors shrink-0"
181
- >
182
- Clear
183
- </button>
184
- )}
185
- </div>
186
- </SettingsField>
187
- <p className="text-[10px] text-neutral-400 leading-snug px-0.5">
188
- Override the navbar text color while this cover section is on screen. Clears when the next section takes over.
189
- </p>
190
- </SettingsSection>
191
-
192
- {/* Overlay */}
193
- <SettingsSection title="Overlay" defaultOpen icon={<OverlayIcon />}>
194
- <SettingsField label="Color">
195
- <ColorSwatchPicker
196
- value={overlayColor}
197
- onChange={(val) => updateBg({ background_overlay_color: typeof val === "string" ? val : "" })}
198
- swatches={paletteSwatches}
199
- />
200
- </SettingsField>
201
-
202
- <SettingsField label="Opacity">
203
- <div className="flex items-center gap-2">
204
- <input
205
- type="range"
206
- min={0}
207
- max={100}
208
- step={5}
209
- value={overlayOpacity}
210
- onChange={(e) => updateBg({ background_overlay_opacity: parseInt(e.target.value) })}
211
- className="flex-1 accent-[#076bff]"
212
- />
213
- <span className="text-xs text-neutral-900 w-10 text-right tabular-nums">
214
- {overlayOpacity}%
215
- </span>
216
- </div>
217
- </SettingsField>
218
- </SettingsSection>
219
-
220
- {/* Section Height */}
221
- <SettingsSection title="Height" defaultOpen icon={<SpacingIcon />}>
222
- <SettingsField label="Height">
223
- <select
224
- value={section.height}
225
- onChange={(e) => store.updateCoverHeight(section._key, e.target.value as CoverSection["height"])}
226
- className={SELECT_CLASS}
227
- >
228
- {HEIGHT_OPTIONS.map((opt) => (
229
- <option key={opt.value} value={opt.value}>{opt.label}</option>
230
- ))}
231
- </select>
232
- </SettingsField>
233
- </SettingsSection>
234
-
235
- {/* Rows */}
236
- <SettingsSection title="Rows" defaultOpen icon={<GridGapsIcon />}>
237
- <div className="space-y-2">
238
- {section.cover_rows.map((row, i) => (
239
- <div
240
- key={row._key}
241
- className="flex items-center gap-2 rounded-lg bg-[#f5f5f5] px-2.5 py-2"
242
- >
243
- <span className="text-[10px] font-semibold text-neutral-400 w-6 shrink-0">
244
- R{i + 1}
245
- </span>
246
- <span className="text-xs text-neutral-700 font-medium tabular-nums w-10">
247
- {row.height_percent}%
248
- </span>
249
- <select
250
- value={row.vertical_align}
251
- onChange={(e) =>
252
- store.updateCoverRowAlign(
253
- section._key,
254
- row._key,
255
- e.target.value as "start" | "center" | "end"
256
- )
257
- }
258
- className="flex-1 rounded-md border border-transparent bg-white px-2 py-1 text-[11px] text-neutral-700 outline-none hover:border-[#e5e5e5] focus:border-[#076bff]"
259
- >
260
- {ALIGN_OPTIONS.map((opt) => (
261
- <option key={opt.value} value={opt.value}>{opt.label}</option>
262
- ))}
263
- </select>
264
- {section.cover_rows.length > 1 && (
265
- <button
266
- onClick={() => store.removeCoverRow(section._key, row._key)}
267
- className="text-neutral-300 hover:text-red-500 transition-colors text-xs shrink-0"
268
- title="Remove row"
269
- >
270
-
271
- </button>
272
- )}
273
- </div>
274
- ))}
275
- </div>
276
-
277
- {section.cover_rows.length < 5 && (
278
- <button
279
- onClick={() => store.addCoverRow(section._key)}
280
- className="w-full mt-2 rounded-lg border border-dashed border-neutral-300 py-2 text-[11px] font-medium text-neutral-400 hover:text-neutral-600 hover:border-neutral-400 transition-colors"
281
- >
282
- + Add Row
283
- </button>
284
- )}
285
-
286
- <p className="text-[10px] text-neutral-400 leading-snug px-0.5 mt-2">
287
- Drag the handles between rows in the canvas to resize. Heights always sum to 100%.
288
- </p>
289
- </SettingsSection>
290
-
291
- {/* Grid Gaps */}
292
- <SettingsSection title="Grid" defaultOpen={false} icon={<GridGapsIcon />}>
293
- <SettingsField label="Col Gap">
294
- <div className="flex items-center gap-2">
295
- <input
296
- type="range"
297
- min={0}
298
- max={60}
299
- step={2}
300
- value={section.settings.col_gap ?? 20}
301
- onChange={(e) => store.updateCoverSettings(section._key, { col_gap: parseInt(e.target.value) })}
302
- className="flex-1 accent-[#076bff]"
303
- />
304
- <span className="text-xs text-neutral-900 w-10 text-right tabular-nums">
305
- {section.settings.col_gap ?? 20}px
306
- </span>
307
- </div>
308
- </SettingsField>
309
-
310
- <SettingsField label="Row Gap">
311
- <div className="flex items-center gap-2">
312
- <input
313
- type="range"
314
- min={0}
315
- max={60}
316
- step={2}
317
- value={section.settings.row_gap ?? 20}
318
- onChange={(e) => store.updateCoverSettings(section._key, { row_gap: parseInt(e.target.value) })}
319
- className="flex-1 accent-[#076bff]"
320
- />
321
- <span className="text-xs text-neutral-900 w-10 text-right tabular-nums">
322
- {section.settings.row_gap ?? 20}px
323
- </span>
324
- </div>
325
- </SettingsField>
326
- </SettingsSection>
327
-
328
- {/* Stagger */}
329
- <StaggerSettings
330
- stagger={section.settings.stagger}
331
- onChange={(s) => store.updateCoverSettings(section._key, { stagger: s })}
332
- />
333
- </>
334
- );
335
- }
1
+ "use client";
2
+
3
+ /**
4
+ * CoverSectionSettings — Settings panel for a Cover Section.
5
+ *
6
+ * Displays:
7
+ * - Background type segmented control (Image / Video)
8
+ * - AssetBrowser picker for image or video
9
+ * - Background position / size dropdowns
10
+ * - Overlay color + overlay opacity slider
11
+ * - Section height selector (100vh / 80vh / 50vh)
12
+ * - Row list with percentages and vertical align
13
+ * - Add/Remove row controls
14
+ *
15
+ * Layout and Animation tabs are delegated to SectionV2LayoutTab / SectionV2AnimationTab
16
+ * via a virtual PageSectionV2 (same pattern as parallax slides).
17
+ *
18
+ * Session 176: Cover Sections — Phase 6 (Settings Panel).
19
+ */
20
+
21
+ import { useBuilderStore } from "../../../lib/builder/store";
22
+ import type { CoverSection } from "../../../lib/sanity/types";
23
+ import {
24
+ BackgroundIcon,
25
+ NavbarColorIcon,
26
+ OverlayIcon,
27
+ SpacingIcon,
28
+ GridGapsIcon,
29
+ } from "../editors/section-icons";
30
+ import {
31
+ SettingsField,
32
+ SettingsSection,
33
+ } from "../editors/shared";
34
+ import { AssetPathInput } from "../editors/shared";
35
+ import ColorSwatchPicker, { usePaletteSwatches } from "../ColorSwatchPicker";
36
+ import StaggerSettings from "../editors/StaggerSettings";
37
+
38
+ const BG_POSITION_OPTIONS = [
39
+ { value: "center center", label: "Center" },
40
+ { value: "top center", label: "Top" },
41
+ { value: "bottom center", label: "Bottom" },
42
+ { value: "center left", label: "Left" },
43
+ { value: "center right", label: "Right" },
44
+ { value: "top left", label: "Top Left" },
45
+ { value: "top right", label: "Top Right" },
46
+ { value: "bottom left", label: "Bottom Left" },
47
+ { value: "bottom right", label: "Bottom Right" },
48
+ ];
49
+
50
+ const BG_SIZE_OPTIONS = [
51
+ { value: "cover", label: "Cover" },
52
+ { value: "contain", label: "Contain" },
53
+ { value: "auto", label: "Auto" },
54
+ ];
55
+
56
+ const HEIGHT_OPTIONS = [
57
+ { value: "100vh", label: "Full Viewport (100vh)" },
58
+ { value: "80vh", label: "80% Viewport (80vh)" },
59
+ { value: "50vh", label: "50% Viewport (50vh)" },
60
+ { value: "20vh", label: "20% Viewport (20vh)" },
61
+ ];
62
+
63
+ const ALIGN_OPTIONS = [
64
+ { value: "start", label: "Top" },
65
+ { value: "center", label: "Center" },
66
+ { value: "end", label: "Bottom" },
67
+ ];
68
+
69
+ const SELECT_CLASS = "w-full rounded-lg border border-transparent bg-[#f5f5f5] px-2.5 py-[7px] text-xs text-neutral-900 font-normal outline-none transition-all hover:bg-[#efefef] focus:bg-white focus:border-[#3580f9] focus:shadow-[0_0_0_3px_rgba(53, 128, 249,0.06)]";
70
+
71
+ interface CoverSectionSettingsProps {
72
+ section: CoverSection;
73
+ }
74
+
75
+ export default function CoverSectionSettings({ section }: CoverSectionSettingsProps) {
76
+ const store = useBuilderStore();
77
+ const paletteSwatches = usePaletteSwatches();
78
+
79
+ const bgType = section.background_type || "image";
80
+ const bgPosition = section.background_position || "center center";
81
+ const bgSize = section.background_size || "cover";
82
+ const overlayColor = section.background_overlay_color || "#000000";
83
+ const overlayOpacity = section.background_overlay_opacity ?? 0;
84
+
85
+ const updateBg = (fields: Partial<Pick<CoverSection,
86
+ "background_type" | "background_color" | "background_image" | "background_video" |
87
+ "background_position" | "background_size" |
88
+ "background_overlay_color" | "background_overlay_opacity" |
89
+ "nav_color"
90
+ >>) => {
91
+ store.updateCoverBackground(section._key, fields);
92
+ };
93
+
94
+ return (
95
+ <>
96
+ {/* Background */}
97
+ <SettingsSection title="Background" defaultOpen icon={<BackgroundIcon />}>
98
+ <SettingsField label="Type">
99
+ <div className="flex rounded-lg bg-[#f0f0f0] p-[3px]">
100
+ {(["image", "video", "color"] as const).map((type) => (
101
+ <button
102
+ key={type}
103
+ onClick={() => updateBg({ background_type: type })}
104
+ className={`flex-1 py-1.5 rounded-md text-[11px] font-medium transition-all ${
105
+ bgType === type
106
+ ? "bg-white text-neutral-900 shadow-sm border border-[#e5e5e5]"
107
+ : "text-neutral-400 hover:text-neutral-500"
108
+ }`}
109
+ >
110
+ {type === "image" ? "Image" : type === "video" ? "Video" : "Color"}
111
+ </button>
112
+ ))}
113
+ </div>
114
+ </SettingsField>
115
+
116
+ {bgType === "color" ? (
117
+ <SettingsField label="Color">
118
+ <ColorSwatchPicker
119
+ value={section.background_color || "#000000"}
120
+ onChange={(val) => updateBg({ background_color: typeof val === "string" ? val : "" })}
121
+ swatches={paletteSwatches}
122
+ />
123
+ </SettingsField>
124
+ ) : (
125
+ <>
126
+ <SettingsField label={bgType === "image" ? "Image" : "Video"}>
127
+ <AssetPathInput
128
+ value={bgType === "image" ? (section.background_image || "") : (section.background_video || "")}
129
+ onChange={(path) => {
130
+ if (bgType === "image") {
131
+ updateBg({ background_image: path });
132
+ } else {
133
+ updateBg({ background_video: path });
134
+ }
135
+ }}
136
+ filterType={bgType === "image" ? "image" : "video"}
137
+ placeholder={bgType === "image" ? "path/to/image.jpg" : "path/to/video.mp4"}
138
+ />
139
+ </SettingsField>
140
+
141
+ <SettingsField label="Position">
142
+ <select
143
+ value={bgPosition}
144
+ onChange={(e) => updateBg({ background_position: e.target.value })}
145
+ className={SELECT_CLASS}
146
+ >
147
+ {BG_POSITION_OPTIONS.map((opt) => (
148
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
149
+ ))}
150
+ </select>
151
+ </SettingsField>
152
+
153
+ <SettingsField label="Size">
154
+ <select
155
+ value={bgSize}
156
+ onChange={(e) => updateBg({ background_size: e.target.value as CoverSection["background_size"] })}
157
+ className={SELECT_CLASS}
158
+ >
159
+ {BG_SIZE_OPTIONS.map((opt) => (
160
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
161
+ ))}
162
+ </select>
163
+ </SettingsField>
164
+ </>
165
+ )}
166
+ </SettingsSection>
167
+
168
+ {/* Navbar Color Override */}
169
+ <SettingsSection title="Navbar Color" defaultOpen={false} icon={<NavbarColorIcon />}>
170
+ <SettingsField label="Color">
171
+ <div className="flex items-center gap-2">
172
+ <ColorSwatchPicker
173
+ value={section.nav_color || ""}
174
+ onChange={(val) => updateBg({ nav_color: typeof val === "string" ? val : undefined })}
175
+ swatches={paletteSwatches}
176
+ />
177
+ {section.nav_color && (
178
+ <button
179
+ onClick={() => updateBg({ nav_color: undefined })}
180
+ className="text-[10px] text-neutral-400 hover:text-neutral-600 transition-colors shrink-0"
181
+ >
182
+ Clear
183
+ </button>
184
+ )}
185
+ </div>
186
+ </SettingsField>
187
+ <p className="text-[10px] text-neutral-400 leading-snug px-0.5">
188
+ Override the navbar text color while this cover section is on screen. Clears when the next section takes over.
189
+ </p>
190
+ </SettingsSection>
191
+
192
+ {/* Overlay */}
193
+ <SettingsSection title="Overlay" defaultOpen icon={<OverlayIcon />}>
194
+ <SettingsField label="Color">
195
+ <ColorSwatchPicker
196
+ value={overlayColor}
197
+ onChange={(val) => updateBg({ background_overlay_color: typeof val === "string" ? val : "" })}
198
+ swatches={paletteSwatches}
199
+ />
200
+ </SettingsField>
201
+
202
+ <SettingsField label="Opacity">
203
+ <div className="flex items-center gap-2">
204
+ <input
205
+ type="range"
206
+ min={0}
207
+ max={100}
208
+ step={5}
209
+ value={overlayOpacity}
210
+ onChange={(e) => updateBg({ background_overlay_opacity: parseInt(e.target.value) })}
211
+ className="flex-1 accent-[#3580f9]"
212
+ />
213
+ <span className="text-xs text-neutral-900 w-10 text-right tabular-nums">
214
+ {overlayOpacity}%
215
+ </span>
216
+ </div>
217
+ </SettingsField>
218
+ </SettingsSection>
219
+
220
+ {/* Section Height */}
221
+ <SettingsSection title="Height" defaultOpen icon={<SpacingIcon />}>
222
+ <SettingsField label="Height">
223
+ <select
224
+ value={section.height}
225
+ onChange={(e) => store.updateCoverHeight(section._key, e.target.value as CoverSection["height"])}
226
+ className={SELECT_CLASS}
227
+ >
228
+ {HEIGHT_OPTIONS.map((opt) => (
229
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
230
+ ))}
231
+ </select>
232
+ </SettingsField>
233
+ </SettingsSection>
234
+
235
+ {/* Rows */}
236
+ <SettingsSection title="Rows" defaultOpen icon={<GridGapsIcon />}>
237
+ <div className="space-y-2">
238
+ {section.cover_rows.map((row, i) => (
239
+ <div
240
+ key={row._key}
241
+ className="flex items-center gap-2 rounded-lg bg-[#f5f5f5] px-2.5 py-2"
242
+ >
243
+ <span className="text-[10px] font-semibold text-neutral-400 w-6 shrink-0">
244
+ R{i + 1}
245
+ </span>
246
+ <span className="text-xs text-neutral-700 font-medium tabular-nums w-10">
247
+ {row.height_percent}%
248
+ </span>
249
+ <select
250
+ value={row.vertical_align}
251
+ onChange={(e) =>
252
+ store.updateCoverRowAlign(
253
+ section._key,
254
+ row._key,
255
+ e.target.value as "start" | "center" | "end"
256
+ )
257
+ }
258
+ className="flex-1 rounded-md border border-transparent bg-white px-2 py-1 text-[11px] text-neutral-700 outline-none hover:border-[#e5e5e5] focus:border-[#3580f9]"
259
+ >
260
+ {ALIGN_OPTIONS.map((opt) => (
261
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
262
+ ))}
263
+ </select>
264
+ {section.cover_rows.length > 1 && (
265
+ <button
266
+ onClick={() => store.removeCoverRow(section._key, row._key)}
267
+ className="text-neutral-300 hover:text-red-500 transition-colors text-xs shrink-0"
268
+ title="Remove row"
269
+ >
270
+
271
+ </button>
272
+ )}
273
+ </div>
274
+ ))}
275
+ </div>
276
+
277
+ {section.cover_rows.length < 5 && (
278
+ <button
279
+ onClick={() => store.addCoverRow(section._key)}
280
+ className="w-full mt-2 rounded-lg border border-dashed border-neutral-300 py-2 text-[11px] font-medium text-neutral-400 hover:text-neutral-600 hover:border-neutral-400 transition-colors"
281
+ >
282
+ + Add Row
283
+ </button>
284
+ )}
285
+
286
+ <p className="text-[10px] text-neutral-400 leading-snug px-0.5 mt-2">
287
+ Drag the handles between rows in the canvas to resize. Heights always sum to 100%.
288
+ </p>
289
+ </SettingsSection>
290
+
291
+ {/* Grid Gaps */}
292
+ <SettingsSection title="Grid" defaultOpen={false} icon={<GridGapsIcon />}>
293
+ <SettingsField label="Col Gap">
294
+ <div className="flex items-center gap-2">
295
+ <input
296
+ type="range"
297
+ min={0}
298
+ max={60}
299
+ step={2}
300
+ value={section.settings.col_gap ?? 20}
301
+ onChange={(e) => store.updateCoverSettings(section._key, { col_gap: parseInt(e.target.value) })}
302
+ className="flex-1 accent-[#3580f9]"
303
+ />
304
+ <span className="text-xs text-neutral-900 w-10 text-right tabular-nums">
305
+ {section.settings.col_gap ?? 20}px
306
+ </span>
307
+ </div>
308
+ </SettingsField>
309
+
310
+ <SettingsField label="Row Gap">
311
+ <div className="flex items-center gap-2">
312
+ <input
313
+ type="range"
314
+ min={0}
315
+ max={60}
316
+ step={2}
317
+ value={section.settings.row_gap ?? 20}
318
+ onChange={(e) => store.updateCoverSettings(section._key, { row_gap: parseInt(e.target.value) })}
319
+ className="flex-1 accent-[#3580f9]"
320
+ />
321
+ <span className="text-xs text-neutral-900 w-10 text-right tabular-nums">
322
+ {section.settings.row_gap ?? 20}px
323
+ </span>
324
+ </div>
325
+ </SettingsField>
326
+ </SettingsSection>
327
+
328
+ {/* Stagger */}
329
+ <StaggerSettings
330
+ stagger={section.settings.stagger}
331
+ onChange={(s) => store.updateCoverSettings(section._key, { stagger: s })}
332
+ />
333
+ </>
334
+ );
335
+ }