@morphika/andami 0.5.1 → 0.5.3

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 (147) hide show
  1. package/README.md +27 -2
  2. package/app/admin/assets/page.tsx +6 -6
  3. package/app/admin/database/page.tsx +302 -302
  4. package/app/admin/error.tsx +53 -53
  5. package/app/admin/layout.tsx +332 -320
  6. package/app/admin/navigation/page.tsx +255 -255
  7. package/app/admin/pages/[slug]/page.tsx +44 -27
  8. package/app/admin/pages/page.tsx +24 -19
  9. package/app/admin/projects/page.tsx +30 -21
  10. package/app/admin/setup/page.tsx +1 -1
  11. package/app/admin/styles/page.tsx +1 -1
  12. package/app/api/admin/assets/register/route.ts +51 -14
  13. package/app/api/admin/assets/registry/route.ts +4 -1
  14. package/app/api/admin/assets/relink/confirm/route.ts +4 -1
  15. package/app/api/admin/assets/relink/route.ts +4 -1
  16. package/app/api/admin/assets/scan/route.ts +4 -1
  17. package/app/api/admin/backups/restore-data/route.ts +4 -1
  18. package/app/api/admin/r2/connect/route.ts +4 -1
  19. package/app/api/admin/r2/delete/route.ts +4 -1
  20. package/app/api/admin/r2/rename/route.ts +4 -1
  21. package/app/api/admin/r2/upload-url/route.ts +4 -1
  22. package/app/api/admin/revalidate/route.ts +4 -1
  23. package/app/api/admin/storage/switch/route.ts +4 -1
  24. package/app/api/custom-sections/[id]/route.ts +5 -6
  25. package/components/admin/MetadataEditor.tsx +6 -6
  26. package/components/admin/PublishToggle.tsx +2 -2
  27. package/components/admin/nav-builder/NavBuilder.tsx +1 -1
  28. package/components/admin/nav-builder/NavBuilderGrid.tsx +3 -3
  29. package/components/admin/nav-builder/NavGridCell.tsx +48 -48
  30. package/components/admin/nav-builder/NavGridItem.tsx +8 -6
  31. package/components/admin/nav-builder/NavItemSettings.tsx +331 -331
  32. package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -102
  33. package/components/admin/nav-builder/NavLivePreview.tsx +1 -1
  34. package/components/admin/nav-builder/NavMobileLivePreview.tsx +226 -226
  35. package/components/admin/nav-builder/NavMobileSettings.tsx +242 -242
  36. package/components/admin/nav-builder/NavSettingsFields.tsx +518 -514
  37. package/components/admin/setup-wizard/BrandingStep.tsx +3 -3
  38. package/components/admin/setup-wizard/DatabaseStep.tsx +2 -2
  39. package/components/admin/setup-wizard/DoneStep.tsx +1 -1
  40. package/components/admin/setup-wizard/SetupWizard.tsx +4 -4
  41. package/components/admin/setup-wizard/StorageStep.tsx +2 -2
  42. package/components/admin/setup-wizard/WelcomeStep.tsx +2 -2
  43. package/components/admin/styles/ColorsEditor.tsx +9 -8
  44. package/components/admin/styles/FontsEditor.tsx +9 -7
  45. package/components/admin/styles/GridLayoutEditor.tsx +9 -9
  46. package/components/admin/styles/LinksButtonsEditor.tsx +5 -5
  47. package/components/admin/styles/TypographyEditor.tsx +6 -6
  48. package/components/admin/styles/shared.tsx +68 -68
  49. package/components/blocks/AudioBlockRenderer.tsx +286 -286
  50. package/components/blocks/CoverSectionRenderer.tsx +7 -1
  51. package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
  52. package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
  53. package/components/blocks/SectionV2Renderer.tsx +8 -1
  54. package/components/builder/BlockCardIcons.tsx +316 -316
  55. package/components/builder/BlockTypePicker.tsx +1 -1
  56. package/components/builder/BubbleIcons.tsx +104 -0
  57. package/components/builder/BuilderCanvas.tsx +2 -0
  58. package/components/builder/CanvasMinimap.tsx +66 -49
  59. package/components/builder/CanvasToolbar.tsx +31 -41
  60. package/components/builder/CoverSectionCanvas.tsx +363 -363
  61. package/components/builder/DeviceFrame.tsx +1 -1
  62. package/components/builder/DndWrapper.tsx +3 -3
  63. package/components/builder/InsertionLines.tsx +1 -1
  64. package/components/builder/SectionCardIcons.tsx +421 -320
  65. package/components/builder/SectionEditorBar.tsx +5 -3
  66. package/components/builder/SectionTypePicker.tsx +7 -5
  67. package/components/builder/SectionV2Canvas.tsx +1 -1
  68. package/components/builder/SectionV2Column.tsx +82 -68
  69. package/components/builder/SettingsPanel.tsx +21 -17
  70. package/components/builder/SortableBlock.tsx +93 -73
  71. package/components/builder/SortableRow.tsx +33 -35
  72. package/components/builder/VirtualAssetGrid.tsx +10 -4
  73. package/components/builder/asset-browser/R2BrowserContent.tsx +18 -14
  74. package/components/builder/blockStyles.tsx +192 -185
  75. package/components/builder/color-picker/AlphaSlider.tsx +141 -141
  76. package/components/builder/color-picker/ColorInputs.tsx +105 -105
  77. package/components/builder/color-picker/EyedropperButton.tsx +75 -74
  78. package/components/builder/color-picker/HueSlider.tsx +124 -124
  79. package/components/builder/color-picker/SaturationCanvas.tsx +142 -142
  80. package/components/builder/color-picker/SwatchBar.tsx +98 -93
  81. package/components/builder/color-picker/UnifiedColorPicker.tsx +11 -6
  82. package/components/builder/editors/AudioBlockEditor.tsx +242 -242
  83. package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -360
  84. package/components/builder/editors/ButtonBlockEditor.tsx +4 -4
  85. package/components/builder/editors/EnterAnimationPicker.tsx +2 -2
  86. package/components/builder/editors/HoverEffectPicker.tsx +2 -2
  87. package/components/builder/editors/ImageBlockEditor.tsx +2 -2
  88. package/components/builder/editors/ImageGridBlockEditor.tsx +8 -6
  89. package/components/builder/editors/MarqueeBlockEditor.tsx +622 -0
  90. package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
  91. package/components/builder/editors/ProjectGridEditor.tsx +21 -16
  92. package/components/builder/editors/SpacerBlockEditor.tsx +29 -27
  93. package/components/builder/editors/StaggerSettings.tsx +109 -109
  94. package/components/builder/editors/TextBlockEditor.tsx +22 -17
  95. package/components/builder/editors/TextStylePicker.tsx +1 -1
  96. package/components/builder/editors/VideoBlockEditor.tsx +2 -2
  97. package/components/builder/editors/index.ts +11 -10
  98. package/components/builder/editors/shared.tsx +10 -8
  99. package/components/builder/live-preview/LiveAudioPreview.tsx +120 -120
  100. package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +1 -1
  101. package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
  102. package/components/builder/live-preview/LiveImagePreview.tsx +4 -2
  103. package/components/builder/live-preview/LiveMarqueePreview.tsx +39 -0
  104. package/components/builder/live-preview/LiveProjectCarouselPreview.tsx +1 -1
  105. package/components/builder/live-preview/LiveVideoPreview.tsx +1 -1
  106. package/components/builder/live-preview/ProjectCardWrapper.tsx +293 -291
  107. package/components/builder/live-preview/RichTextBubbleMenu.tsx +10 -6
  108. package/components/builder/live-preview/shared.tsx +5 -2
  109. package/components/builder/settings-panel/AnimationTab.tsx +138 -138
  110. package/components/builder/settings-panel/BlockLayoutTab.tsx +11 -9
  111. package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
  112. package/components/builder/settings-panel/ColumnV2LayoutTab.tsx +242 -0
  113. package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
  114. package/components/builder/settings-panel/CoverSectionLayoutTab.tsx +71 -71
  115. package/components/builder/settings-panel/CoverSectionSettings.tsx +337 -335
  116. package/components/builder/settings-panel/PageSettings.tsx +3 -3
  117. package/components/builder/settings-panel/ParallaxSlideSettings.tsx +2 -2
  118. package/components/builder/settings-panel/SectionV2AnimationTab.tsx +4 -4
  119. package/components/builder/settings-panel/SectionV2LayoutTab.tsx +356 -356
  120. package/components/builder/settings-panel/SectionV2Settings.tsx +25 -20
  121. package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
  122. package/components/builder/settings-panel/index.ts +1 -0
  123. package/lib/animation/enter-types.ts +1 -0
  124. package/lib/animation/hover-effect-presets.ts +210 -210
  125. package/lib/animation/hover-effect-types.ts +1 -0
  126. package/lib/builder/block-registrations.ts +468 -417
  127. package/lib/builder/constants.ts +111 -111
  128. package/lib/builder/serializer/normalizers.ts +14 -0
  129. package/lib/builder/serializer/serializers.ts +27 -0
  130. package/lib/builder/store-sections.ts +23 -2
  131. package/lib/builder/types-slices.ts +428 -414
  132. package/lib/builder/types.ts +4 -1
  133. package/lib/config/index.ts +27 -27
  134. package/lib/sanity/queries.ts +48 -0
  135. package/lib/sanity/types.ts +112 -1
  136. package/lib/version.ts +1 -1
  137. package/package.json +7 -5
  138. package/sanity/schemas/blocks/audioBlock.ts +69 -69
  139. package/sanity/schemas/blocks/index.ts +12 -11
  140. package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
  141. package/sanity/schemas/index.ts +120 -117
  142. package/sanity/schemas/objects/coverSection.ts +32 -0
  143. package/sanity/schemas/objects/parallaxSlide.ts +32 -0
  144. package/sanity/schemas/pageSectionV2.ts +32 -0
  145. package/styles/admin.css +85 -85
  146. package/styles/animations.css +237 -237
  147. package/styles/base.css +114 -114
@@ -1,242 +1,242 @@
1
- "use client";
2
-
3
- import { useCallback } from "react";
4
- import type { NavItem, NavDesign, MobileNavDesign } from "../../../lib/sanity/types";
5
- import NavMobileLivePreview from "./NavMobileLivePreview";
6
- import ColorSwatchPicker, { usePaletteSwatches } from "../../builder/ColorSwatchPicker";
7
- import {
8
- Field,
9
- TextInput,
10
- SelectInput,
11
- SegmentedControl,
12
- RangeSlider,
13
- CardSection,
14
- Divider,
15
- } from "./NavSettingsFields";
16
-
17
- // ============================================
18
- // NavMobileSettings — standalone panel for mobile menu customization
19
- // Session 158: Independent mobile menu styles that are NOT affected
20
- // by page-level nav_color or parallax slide color overrides.
21
- // ============================================
22
-
23
- interface NavMobileSettingsProps {
24
- design: MobileNavDesign;
25
- /** Desktop nav design — needed for fallback values and preview */
26
- desktopDesign: NavDesign;
27
- /** Nav items — needed for preview */
28
- items: NavItem[];
29
- onChange: (design: MobileNavDesign) => void;
30
- onSave: () => void;
31
- saving: boolean;
32
- hasChanges: boolean;
33
- }
34
-
35
- export default function NavMobileSettings({
36
- design,
37
- desktopDesign,
38
- items,
39
- onChange,
40
- onSave,
41
- saving,
42
- hasChanges,
43
- }: NavMobileSettingsProps) {
44
- const swatches = usePaletteSwatches();
45
- const update = useCallback(
46
- (partial: Partial<MobileNavDesign>) => onChange({ ...design, ...partial }),
47
- [design, onChange]
48
- );
49
-
50
- // ── Icon components for card sections ──
51
- const OverlayIcon = (
52
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#7c3aed" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
53
- <rect x="2" y="3" width="20" height="14" rx="2" /><line x1="8" y1="21" x2="16" y2="21" /><line x1="12" y1="17" x2="12" y2="21" />
54
- </svg>
55
- );
56
- const NavbarBarIcon = (
57
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#2563eb" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
58
- <rect x="3" y="3" width="18" height="18" rx="2" /><line x1="3" y1="9" x2="21" y2="9" />
59
- </svg>
60
- );
61
-
62
- return (
63
- <div className="bg-white rounded-2xl overflow-hidden border border-neutral-200 flex flex-col" style={{ minHeight: "calc(100vh - 180px)" }}>
64
- {/* ── Gradient header (matching Desktop panel) ── */}
65
- <div className="relative flex items-center px-4 py-3.5 overflow-hidden" style={{ background: "linear-gradient(135deg, #f3e8ff 0%, #e9d5ff 40%, #ddd6fe 100%)" }}>
66
- <div className="absolute inset-0 pointer-events-none" style={{ background: "linear-gradient(135deg, rgba(255,255,255,0.25) 0%, rgba(255,255,255,0.05) 100%)" }} />
67
- <div className="relative shrink-0 flex items-center justify-center" style={{ width: 34, height: 34, borderRadius: 10, background: "rgba(255,255,255,0.4)", backdropFilter: "blur(8px)", boxShadow: "0 2px 8px rgba(0,0,0,0.06), inset 0 1px 0 rgba(255,255,255,0.5)" }}>
68
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="rgba(0,0,0,0.55)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
69
- <rect x="5" y="2" width="14" height="20" rx="2" /><line x1="12" y1="18" x2="12.01" y2="18" />
70
- </svg>
71
- </div>
72
- <div className="relative z-10 ml-2.5 min-w-0 flex-1">
73
- <h3 className="text-[13px] font-semibold truncate" style={{ color: "rgba(0,0,0,0.72)", textShadow: "0 1px 0 rgba(255,255,255,0.3)" }}>
74
- Mobile Menu
75
- </h3>
76
- <p className="text-[10px] mt-0.5" style={{ color: "rgba(0,0,0,0.38)" }}>
77
- Independent from the desktop navbar
78
- </p>
79
- </div>
80
- <div className="relative z-10 flex items-center gap-3">
81
- {hasChanges && (
82
- <span className="text-[11px] text-amber-600 font-medium">Unsaved</span>
83
- )}
84
- <button
85
- onClick={onSave}
86
- disabled={saving || !hasChanges}
87
- className={`px-4 py-1.5 text-[11px] font-medium rounded-lg transition-all ${
88
- saving || !hasChanges
89
- ? "bg-white/40 text-neutral-400 cursor-not-allowed"
90
- : "bg-white text-[#076bff] shadow-sm hover:shadow-md"
91
- }`}
92
- >
93
- {saving ? "Saving..." : "Save"}
94
- </button>
95
- </div>
96
- </div>
97
-
98
- {/* Side-by-side: settings left, preview right — flex-1 fills remaining height */}
99
- <div className="flex flex-1 min-h-0">
100
- {/* Settings content */}
101
- <div className="flex-1 min-w-0 py-2 border-r border-neutral-200 overflow-y-auto">
102
- {/* ── Menu Overlay card ── */}
103
- <CardSection title="Overlay" icon={OverlayIcon} iconBg="#ede9fe">
104
- <Field label="BG">
105
- <ColorSwatchPicker
106
- value={design.overlay_bg || ""}
107
- onChange={(v) => update({ overlay_bg: typeof v === "string" ? v : "" })}
108
- swatches={swatches}
109
- />
110
- </Field>
111
- <Field label="Text">
112
- <ColorSwatchPicker
113
- value={design.text_color || ""}
114
- onChange={(v) => update({ text_color: typeof v === "string" ? v : "" })}
115
- swatches={swatches}
116
- />
117
- </Field>
118
- <Divider />
119
- <Field label="Size">
120
- <TextInput
121
- value={design.font_size ?? 24}
122
- onChange={(v) =>
123
- update({
124
- font_size: Math.max(12, Math.min(72, parseInt(v) || 24)),
125
- })
126
- }
127
- type="number"
128
- />
129
- </Field>
130
- <Field label="Case">
131
- <SelectInput
132
- value={design.text_transform || "uppercase"}
133
- onChange={(v) =>
134
- update({
135
- text_transform: v as MobileNavDesign["text_transform"],
136
- })
137
- }
138
- options={[
139
- { value: "uppercase", label: "AA" },
140
- { value: "capitalize", label: "Aa" },
141
- { value: "lowercase", label: "aa" },
142
- { value: "none", label: "—" },
143
- ]}
144
- />
145
- </Field>
146
- <Field label="Align">
147
- <SegmentedControl
148
- value={design.items_align || "center"}
149
- onChange={(v) =>
150
- update({ items_align: v as MobileNavDesign["items_align"] })
151
- }
152
- options={[
153
- { value: "left", label: "Left" },
154
- { value: "center", label: "Center" },
155
- { value: "right", label: "Right" },
156
- ]}
157
- />
158
- </Field>
159
- <Divider />
160
- <Field label="Gap">
161
- <RangeSlider
162
- value={design.items_gap ?? 32}
163
- onChange={(v) => update({ items_gap: v })}
164
- min={8}
165
- max={80}
166
- suffix="px"
167
- />
168
- </Field>
169
- </CardSection>
170
-
171
- {/* ── Navbar Bar card ── */}
172
- <CardSection title="Top Bar" icon={NavbarBarIcon} iconBg="#dbeafe">
173
- <Field label="BG">
174
- <ColorSwatchPicker
175
- value={design.navbar_bg || ""}
176
- onChange={(v) => update({ navbar_bg: typeof v === "string" ? v : "" })}
177
- swatches={swatches}
178
- />
179
- </Field>
180
- {design.navbar_bg && (
181
- <Field label="Opacity">
182
- <RangeSlider
183
- value={design.navbar_bg_opacity ?? 100}
184
- onChange={(v) => update({ navbar_bg_opacity: v })}
185
- min={0}
186
- max={100}
187
- suffix="%"
188
- />
189
- </Field>
190
- )}
191
- <Field label="Icon">
192
- <ColorSwatchPicker
193
- value={design.hamburger_color || ""}
194
- onChange={(v) => update({ hamburger_color: typeof v === "string" ? v : "" })}
195
- swatches={swatches}
196
- />
197
- </Field>
198
- <Divider />
199
- <Field label="Pad H">
200
- <RangeSlider
201
- value={design.padding_h ?? 24}
202
- onChange={(v) => update({ padding_h: v })}
203
- min={0}
204
- max={60}
205
- suffix="px"
206
- />
207
- </Field>
208
- <Field label="Pad V">
209
- <RangeSlider
210
- value={design.padding_v ?? 27}
211
- onChange={(v) => update({ padding_v: v })}
212
- min={0}
213
- max={60}
214
- suffix="px"
215
- />
216
- </Field>
217
- </CardSection>
218
-
219
- {/* Info notice — card style */}
220
- <div className="mx-2.5 my-1 px-3 py-2.5 bg-blue-50/60 rounded-[10px] border border-blue-100">
221
- <p className="text-[10px] text-blue-500 leading-relaxed">
222
- <strong>Independent from page overrides.</strong> Page-level{" "}
223
- <code className="bg-blue-100 px-1 rounded text-[9px]">nav_color</code>{" "}
224
- and parallax color changes only affect the desktop navbar.
225
- </p>
226
- </div>
227
-
228
- <div className="h-2" />
229
- </div>
230
-
231
- {/* Live preview — stretches full height */}
232
- <div className="w-[340px] shrink-0 sticky top-0 self-stretch">
233
- <NavMobileLivePreview
234
- items={items}
235
- design={desktopDesign}
236
- mobileDesign={design}
237
- />
238
- </div>
239
- </div>
240
- </div>
241
- );
242
- }
1
+ "use client";
2
+
3
+ import { useCallback } from "react";
4
+ import type { NavItem, NavDesign, MobileNavDesign } from "../../../lib/sanity/types";
5
+ import NavMobileLivePreview from "./NavMobileLivePreview";
6
+ import ColorSwatchPicker, { usePaletteSwatches } from "../../builder/ColorSwatchPicker";
7
+ import {
8
+ Field,
9
+ TextInput,
10
+ SelectInput,
11
+ SegmentedControl,
12
+ RangeSlider,
13
+ CardSection,
14
+ Divider,
15
+ } from "./NavSettingsFields";
16
+
17
+ // ============================================
18
+ // NavMobileSettings — standalone panel for mobile menu customization
19
+ // Session 158: Independent mobile menu styles that are NOT affected
20
+ // by page-level nav_color or parallax slide color overrides.
21
+ // ============================================
22
+
23
+ interface NavMobileSettingsProps {
24
+ design: MobileNavDesign;
25
+ /** Desktop nav design — needed for fallback values and preview */
26
+ desktopDesign: NavDesign;
27
+ /** Nav items — needed for preview */
28
+ items: NavItem[];
29
+ onChange: (design: MobileNavDesign) => void;
30
+ onSave: () => void;
31
+ saving: boolean;
32
+ hasChanges: boolean;
33
+ }
34
+
35
+ export default function NavMobileSettings({
36
+ design,
37
+ desktopDesign,
38
+ items,
39
+ onChange,
40
+ onSave,
41
+ saving,
42
+ hasChanges,
43
+ }: NavMobileSettingsProps) {
44
+ const swatches = usePaletteSwatches();
45
+ const update = useCallback(
46
+ (partial: Partial<MobileNavDesign>) => onChange({ ...design, ...partial }),
47
+ [design, onChange]
48
+ );
49
+
50
+ // ── Icon components for card sections ──
51
+ const OverlayIcon = (
52
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#7c3aed" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
53
+ <rect x="2" y="3" width="20" height="14" rx="2" /><line x1="8" y1="21" x2="16" y2="21" /><line x1="12" y1="17" x2="12" y2="21" />
54
+ </svg>
55
+ );
56
+ const NavbarBarIcon = (
57
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#2563eb" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
58
+ <rect x="3" y="3" width="18" height="18" rx="2" /><line x1="3" y1="9" x2="21" y2="9" />
59
+ </svg>
60
+ );
61
+
62
+ return (
63
+ <div className="bg-white rounded-2xl overflow-hidden border border-neutral-200 flex flex-col" style={{ minHeight: "calc(100vh - 180px)" }}>
64
+ {/* ── Gradient header (matching Desktop panel) ── */}
65
+ <div className="relative flex items-center px-4 py-3.5 overflow-hidden" style={{ background: "linear-gradient(135deg, #f3e8ff 0%, #e9d5ff 40%, #ddd6fe 100%)" }}>
66
+ <div className="absolute inset-0 pointer-events-none" style={{ background: "linear-gradient(135deg, rgba(255,255,255,0.25) 0%, rgba(255,255,255,0.05) 100%)" }} />
67
+ <div className="relative shrink-0 flex items-center justify-center" style={{ width: 34, height: 34, borderRadius: 10, background: "rgba(255,255,255,0.4)", backdropFilter: "blur(8px)", boxShadow: "0 2px 8px rgba(0,0,0,0.06), inset 0 1px 0 rgba(255,255,255,0.5)" }}>
68
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="rgba(0,0,0,0.55)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
69
+ <rect x="5" y="2" width="14" height="20" rx="2" /><line x1="12" y1="18" x2="12.01" y2="18" />
70
+ </svg>
71
+ </div>
72
+ <div className="relative z-10 ml-2.5 min-w-0 flex-1">
73
+ <h3 className="text-[13px] font-semibold truncate" style={{ color: "rgba(0,0,0,0.72)", textShadow: "0 1px 0 rgba(255,255,255,0.3)" }}>
74
+ Mobile Menu
75
+ </h3>
76
+ <p className="text-[10px] mt-0.5" style={{ color: "rgba(0,0,0,0.38)" }}>
77
+ Independent from the desktop navbar
78
+ </p>
79
+ </div>
80
+ <div className="relative z-10 flex items-center gap-3">
81
+ {hasChanges && (
82
+ <span className="text-[11px] text-amber-600 font-medium">Unsaved</span>
83
+ )}
84
+ <button
85
+ onClick={onSave}
86
+ disabled={saving || !hasChanges}
87
+ className={`px-4 py-1.5 text-[11px] font-medium rounded-lg transition-all ${
88
+ saving || !hasChanges
89
+ ? "bg-white/40 text-neutral-400 cursor-not-allowed"
90
+ : "bg-white text-[#3580f9] shadow-sm hover:shadow-md"
91
+ }`}
92
+ >
93
+ {saving ? "Saving..." : "Save"}
94
+ </button>
95
+ </div>
96
+ </div>
97
+
98
+ {/* Side-by-side: settings left, preview right — flex-1 fills remaining height */}
99
+ <div className="flex flex-1 min-h-0">
100
+ {/* Settings content */}
101
+ <div className="flex-1 min-w-0 py-2 border-r border-neutral-200 overflow-y-auto">
102
+ {/* ── Menu Overlay card ── */}
103
+ <CardSection title="Overlay" icon={OverlayIcon} iconBg="#ede9fe">
104
+ <Field label="BG">
105
+ <ColorSwatchPicker
106
+ value={design.overlay_bg || ""}
107
+ onChange={(v) => update({ overlay_bg: typeof v === "string" ? v : "" })}
108
+ swatches={swatches}
109
+ />
110
+ </Field>
111
+ <Field label="Text">
112
+ <ColorSwatchPicker
113
+ value={design.text_color || ""}
114
+ onChange={(v) => update({ text_color: typeof v === "string" ? v : "" })}
115
+ swatches={swatches}
116
+ />
117
+ </Field>
118
+ <Divider />
119
+ <Field label="Size">
120
+ <TextInput
121
+ value={design.font_size ?? 24}
122
+ onChange={(v) =>
123
+ update({
124
+ font_size: Math.max(12, Math.min(72, parseInt(v) || 24)),
125
+ })
126
+ }
127
+ type="number"
128
+ />
129
+ </Field>
130
+ <Field label="Case">
131
+ <SelectInput
132
+ value={design.text_transform || "uppercase"}
133
+ onChange={(v) =>
134
+ update({
135
+ text_transform: v as MobileNavDesign["text_transform"],
136
+ })
137
+ }
138
+ options={[
139
+ { value: "uppercase", label: "AA" },
140
+ { value: "capitalize", label: "Aa" },
141
+ { value: "lowercase", label: "aa" },
142
+ { value: "none", label: "—" },
143
+ ]}
144
+ />
145
+ </Field>
146
+ <Field label="Align">
147
+ <SegmentedControl
148
+ value={design.items_align || "center"}
149
+ onChange={(v) =>
150
+ update({ items_align: v as MobileNavDesign["items_align"] })
151
+ }
152
+ options={[
153
+ { value: "left", label: "Left" },
154
+ { value: "center", label: "Center" },
155
+ { value: "right", label: "Right" },
156
+ ]}
157
+ />
158
+ </Field>
159
+ <Divider />
160
+ <Field label="Gap">
161
+ <RangeSlider
162
+ value={design.items_gap ?? 32}
163
+ onChange={(v) => update({ items_gap: v })}
164
+ min={8}
165
+ max={80}
166
+ suffix="px"
167
+ />
168
+ </Field>
169
+ </CardSection>
170
+
171
+ {/* ── Navbar Bar card ── */}
172
+ <CardSection title="Top Bar" icon={NavbarBarIcon} iconBg="#dbeafe">
173
+ <Field label="BG">
174
+ <ColorSwatchPicker
175
+ value={design.navbar_bg || ""}
176
+ onChange={(v) => update({ navbar_bg: typeof v === "string" ? v : "" })}
177
+ swatches={swatches}
178
+ />
179
+ </Field>
180
+ {design.navbar_bg && (
181
+ <Field label="Opacity">
182
+ <RangeSlider
183
+ value={design.navbar_bg_opacity ?? 100}
184
+ onChange={(v) => update({ navbar_bg_opacity: v })}
185
+ min={0}
186
+ max={100}
187
+ suffix="%"
188
+ />
189
+ </Field>
190
+ )}
191
+ <Field label="Icon">
192
+ <ColorSwatchPicker
193
+ value={design.hamburger_color || ""}
194
+ onChange={(v) => update({ hamburger_color: typeof v === "string" ? v : "" })}
195
+ swatches={swatches}
196
+ />
197
+ </Field>
198
+ <Divider />
199
+ <Field label="Pad H">
200
+ <RangeSlider
201
+ value={design.padding_h ?? 24}
202
+ onChange={(v) => update({ padding_h: v })}
203
+ min={0}
204
+ max={60}
205
+ suffix="px"
206
+ />
207
+ </Field>
208
+ <Field label="Pad V">
209
+ <RangeSlider
210
+ value={design.padding_v ?? 27}
211
+ onChange={(v) => update({ padding_v: v })}
212
+ min={0}
213
+ max={60}
214
+ suffix="px"
215
+ />
216
+ </Field>
217
+ </CardSection>
218
+
219
+ {/* Info notice — card style */}
220
+ <div className="mx-2.5 my-1 px-3 py-2.5 bg-blue-50/60 rounded-[10px] border border-blue-100">
221
+ <p className="text-[10px] text-blue-500 leading-relaxed">
222
+ <strong>Independent from page overrides.</strong> Page-level{" "}
223
+ <code className="bg-blue-100 px-1 rounded text-[9px]">nav_color</code>{" "}
224
+ and parallax color changes only affect the desktop navbar.
225
+ </p>
226
+ </div>
227
+
228
+ <div className="h-2" />
229
+ </div>
230
+
231
+ {/* Live preview — stretches full height */}
232
+ <div className="w-[340px] shrink-0 sticky top-0 self-stretch">
233
+ <NavMobileLivePreview
234
+ items={items}
235
+ design={desktopDesign}
236
+ mobileDesign={design}
237
+ />
238
+ </div>
239
+ </div>
240
+ </div>
241
+ );
242
+ }