@morphika/andami 0.2.26 → 0.3.1

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 (61) hide show
  1. package/app/admin/pages/[slug]/page.tsx +39 -45
  2. package/app/api/admin/assets/scan/route.ts +40 -13
  3. package/app/api/admin/custom-sections/[slug]/route.ts +4 -1
  4. package/app/api/admin/custom-sections/route.ts +4 -1
  5. package/app/api/admin/pages/[slug]/route.ts +7 -1
  6. package/app/api/admin/pages/route.ts +4 -1
  7. package/app/api/admin/r2/connect/route.ts +19 -1
  8. package/app/api/admin/r2/disconnect/route.ts +3 -0
  9. package/app/api/admin/r2/rename/route.ts +52 -13
  10. package/app/api/admin/r2/upload-url/route.ts +8 -1
  11. package/app/api/admin/settings/route.ts +4 -1
  12. package/app/api/admin/styles/route.ts +4 -1
  13. package/components/admin/styles/GridLayoutEditor.tsx +46 -46
  14. package/components/blocks/BlockRenderer.tsx +11 -2
  15. package/components/blocks/CoverSectionRenderer.tsx +75 -3
  16. package/components/blocks/ImageGridBlockRenderer.tsx +17 -11
  17. package/components/blocks/ParallaxGroupRenderer.tsx +45 -10
  18. package/components/blocks/ShaderCanvas.tsx +10 -6
  19. package/components/builder/BlockCardIcons.tsx +227 -0
  20. package/components/builder/BlockTypePicker.tsx +36 -63
  21. package/components/builder/BuilderCanvas.tsx +6 -2
  22. package/components/builder/ColumnDragOverlay.tsx +3 -3
  23. package/components/builder/CoverRowResizeHandle.tsx +5 -2
  24. package/components/builder/CoverSectionCanvas.tsx +45 -52
  25. package/components/builder/DndWrapper.tsx +1 -1
  26. package/components/builder/InsertionLines.tsx +1 -1
  27. package/components/builder/ParallaxGroupCanvas.tsx +12 -71
  28. package/components/builder/SectionCardIcons.tsx +266 -0
  29. package/components/builder/SectionEditorBar.tsx +17 -12
  30. package/components/builder/SectionTypePicker.tsx +33 -137
  31. package/components/builder/SectionV2Canvas.tsx +1 -1
  32. package/components/builder/SectionV2Column.tsx +19 -30
  33. package/components/builder/SettingsPanel.tsx +8 -32
  34. package/components/builder/SortableBlock.tsx +42 -50
  35. package/components/builder/SortableRow.tsx +207 -19
  36. package/components/builder/blockStyles.tsx +53 -180
  37. package/components/builder/iconPrimitives.tsx +78 -0
  38. package/components/builder/live-preview/LiveImagePreview.tsx +16 -2
  39. package/components/builder/live-preview/LiveVideoPreview.tsx +15 -2
  40. package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
  41. package/components/builder/settings-panel/CoverSectionSettings.tsx +28 -1
  42. package/components/builder/settings-panel/SectionV2Settings.tsx +14 -14
  43. package/lib/assets.ts +17 -2
  44. package/lib/builder/constants.ts +22 -15
  45. package/lib/builder/format.ts +25 -0
  46. package/lib/builder/history.ts +0 -3
  47. package/lib/builder/layout-styles.ts +1 -1
  48. package/lib/builder/section-visibility.ts +36 -0
  49. package/lib/builder/serializer/normalizers.ts +15 -6
  50. package/lib/builder/serializer/serializers.ts +3 -3
  51. package/lib/builder/store-blocks.ts +16 -9
  52. package/lib/builder/store-cover.ts +76 -8
  53. package/lib/builder/store.ts +0 -2
  54. package/lib/builder/types.ts +1 -2
  55. package/lib/csrf.ts +31 -0
  56. package/lib/sanity/types.ts +4 -1
  57. package/lib/security.ts +50 -0
  58. package/lib/version.ts +1 -1
  59. package/package.json +1 -1
  60. package/sanity/schemas/objects/coverSection.ts +35 -3
  61. package/components/builder/ParallaxSlideHeader.tsx +0 -113
@@ -3,87 +3,7 @@
3
3
  import { useState, useEffect } from "react";
4
4
  import { SECTION_TYPE_REGISTRY } from "../../lib/builder/types";
5
5
  import type { CustomSectionListItem } from "../../lib/sanity/types";
6
- import { ProjectGridBlockIcon, ParallaxGroupIcon } from "./blockStyles";
7
- import { BLOCK_GRADIENTS } from "./blockStyles";
8
- import { BUILDER_BLUE, BUILDER_VIOLET } from "../../lib/builder/constants";
9
-
10
- // ── Section card icons ──
11
-
12
- function EmptySectionV2Icon({ size = 28 }: { size?: number }) {
13
- return (
14
- <svg width={size} height={size} viewBox="0 0 40 40" fill="none">
15
- <defs>
16
- <linearGradient id="es2Grad" x1="5" y1="5" x2="35" y2="35">
17
- <stop offset="0%" stopColor={BUILDER_BLUE} />
18
- <stop offset="100%" stopColor="#0550c0" />
19
- </linearGradient>
20
- </defs>
21
- {/* Grid pattern */}
22
- <rect x="3" y="3" width="34" height="34" rx="4" fill="url(#es2Grad)" opacity="0.15" />
23
- <rect x="3" y="3" width="34" height="34" rx="4" stroke="url(#es2Grad)" strokeWidth="2" fill="none" opacity="0.5" />
24
- {/* Grid columns */}
25
- <rect x="6" y="8" width="8" height="24" rx="2" fill="url(#es2Grad)" opacity="0.3" />
26
- <rect x="16" y="8" width="8" height="24" rx="2" fill="url(#es2Grad)" opacity="0.3" />
27
- <rect x="26" y="8" width="8" height="24" rx="2" fill="url(#es2Grad)" opacity="0.3" />
28
- </svg>
29
- );
30
- }
31
-
32
- function CoverSectionIcon({ size = 28 }: { size?: number }) {
33
- const accent = "#0d9488";
34
- return (
35
- <svg width={size} height={size} viewBox="0 0 40 40" fill="none">
36
- <defs>
37
- <linearGradient id="csGrad" x1="5" y1="5" x2="35" y2="35">
38
- <stop offset="0%" stopColor={accent} />
39
- <stop offset="100%" stopColor="#0f766e" />
40
- </linearGradient>
41
- </defs>
42
- <rect x="3" y="3" width="34" height="34" rx="4" fill="url(#csGrad)" opacity="0.15" />
43
- <rect x="3" y="3" width="34" height="34" rx="4" stroke="url(#csGrad)" strokeWidth="2" fill="none" opacity="0.5" />
44
- {/* Top row (large) */}
45
- <rect x="6" y="6" width="28" height="18" rx="2" fill="url(#csGrad)" opacity="0.25" />
46
- {/* Bottom row (small) */}
47
- <rect x="6" y="26" width="28" height="8" rx="2" fill="url(#csGrad)" opacity="0.4" />
48
- {/* Divider line */}
49
- <line x1="8" y1="25" x2="32" y2="25" stroke={accent} strokeWidth="1" opacity="0.5" strokeDasharray="2 2" />
50
- </svg>
51
- );
52
- }
53
-
54
- function SavedSectionIcon({ size = 28 }: { size?: number }) {
55
- return (
56
- <svg width={size} height={size} viewBox="0 0 40 40" fill="none">
57
- <defs>
58
- <linearGradient id="savedGrad" x1="5" y1="5" x2="35" y2="35">
59
- <stop offset="0%" stopColor={BUILDER_VIOLET} />
60
- <stop offset="100%" stopColor="#6d28d9" />
61
- </linearGradient>
62
- </defs>
63
- <rect x="3" y="3" width="34" height="34" rx="4" fill="url(#savedGrad)" opacity="0.15" />
64
- <rect x="3" y="3" width="34" height="34" rx="4" stroke="url(#savedGrad)" strokeWidth="2" fill="none" opacity="0.5" />
65
- {/* Component/puzzle icon */}
66
- <rect x="8" y="8" width="10" height="10" rx="2" fill="url(#savedGrad)" opacity="0.5" />
67
- <rect x="22" y="8" width="10" height="10" rx="2" fill="url(#savedGrad)" opacity="0.5" />
68
- <rect x="8" y="22" width="10" height="10" rx="2" fill="url(#savedGrad)" opacity="0.5" />
69
- <rect x="22" y="22" width="10" height="10" rx="2" fill="url(#savedGrad)" opacity="0.3" />
70
- </svg>
71
- );
72
- }
73
-
74
- const SECTION_ICON_COMPONENTS: Record<string, React.FC<{ size?: number }>> = {
75
- "empty-v2": EmptySectionV2Icon,
76
- coverSection: CoverSectionIcon,
77
- projectGridBlock: ProjectGridBlockIcon,
78
- parallaxGroup: ParallaxGroupIcon,
79
- };
80
-
81
- const SECTION_GRADIENTS: Record<string, string> = {
82
- "empty-v2": "linear-gradient(135deg, #d0e0f8 0%, #b8d0f0 50%, #a0c0e8 100%)",
83
- coverSection: "linear-gradient(135deg, #b2f5ea 0%, #81e6d9 50%, #5eead4 100%)",
84
- projectGridBlock: BLOCK_GRADIENTS.projectGridBlock,
85
- parallaxGroup: BLOCK_GRADIENTS.parallaxGroup,
86
- };
6
+ import { SECTION_CARD_ICONS } from "./SectionCardIcons";
87
7
 
88
8
  // ── V2 layout presets (use cascade preset names) ──
89
9
  type V2Preset = "full" | "halves" | "thirds" | "quarters" | "1/3+2/3" | "2/3+1/3";
@@ -96,10 +16,9 @@ const layoutPresets: { key: V2Preset; label: string; widths: number[] }[] = [
96
16
  { key: "2/3+1/3", label: "2/3 + 1/3", widths: [8, 4] },
97
17
  ];
98
18
 
99
- // ── Shared card component (glass card with gradient, icon, title, subtitle) ──
19
+ // ── Shared flat card (matches Add Block modal style) ──
100
20
 
101
21
  interface SectionCardProps {
102
- gradient: string;
103
22
  icon: React.ReactNode;
104
23
  title: string;
105
24
  subtitle?: string;
@@ -110,7 +29,6 @@ interface SectionCardProps {
110
29
  }
111
30
 
112
31
  function SectionCard({
113
- gradient,
114
32
  icon,
115
33
  title,
116
34
  subtitle,
@@ -124,56 +42,39 @@ function SectionCard({
124
42
  onClick={onClick}
125
43
  onMouseEnter={onMouseEnter}
126
44
  onMouseLeave={onMouseLeave}
127
- className="relative flex items-center gap-3 rounded-2xl px-3.5 py-3 transition-all text-left group overflow-hidden border-0"
45
+ className="relative flex items-center rounded-2xl text-left group overflow-hidden border-0 h-[96px]"
128
46
  style={{
129
- background: gradient,
130
- transform: isHovered ? "translateY(-1px) scale(1.015)" : "translateY(0) scale(1)",
131
- boxShadow: isHovered
132
- ? "0 8px 24px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.3)"
133
- : "0 2px 8px rgba(0,0,0,0.08), inset 0 1px 0 rgba(255,255,255,0.2)",
134
- transition: "all 0.3s cubic-bezier(0.23, 1, 0.32, 1)",
47
+ background: "#f4f4f4",
48
+ transform: isHovered ? "translateY(-1px)" : "translateY(0)",
49
+ transition: "transform 200ms cubic-bezier(0.23, 1, 0.32, 1)",
135
50
  }}
136
51
  >
137
- {/* Glass overlay */}
138
- <div
139
- className="absolute inset-0 rounded-2xl pointer-events-none"
140
- style={{
141
- background: "linear-gradient(135deg, rgba(255,255,255,0.25) 0%, rgba(255,255,255,0.05) 100%)",
142
- }}
143
- />
144
- {/* Icon container */}
145
- <div
146
- className="relative shrink-0 flex items-center justify-center"
147
- style={{
148
- width: 44,
149
- height: 44,
150
- borderRadius: 12,
151
- background: "rgba(255,255,255,0.4)",
152
- backdropFilter: "blur(8px)",
153
- boxShadow: "0 2px 8px rgba(0,0,0,0.06), inset 0 1px 0 rgba(255,255,255,0.5)",
154
- transition: "transform 0.3s",
155
- transform: isHovered ? "scale(1.08)" : "scale(1)",
156
- }}
157
- >
52
+ {/* Icon artwork — full-bleed on the left. */}
53
+ <div className="shrink-0 h-full" style={{ width: 176 }}>
158
54
  {icon}
159
55
  </div>
56
+
160
57
  {/* Text */}
161
- <div className="relative z-10 min-w-0 flex-1">
162
- <p
163
- className="text-sm font-semibold truncate"
164
- style={{ color: "rgba(0,0,0,0.72)", textShadow: "0 1px 0 rgba(255,255,255,0.3)" }}
165
- >
58
+ <div className="min-w-0 pr-5 py-4 flex-1">
59
+ <p className="text-[17px] font-semibold text-[#2b2f38] truncate leading-tight">
166
60
  {title}
167
61
  </p>
168
62
  {subtitle && (
169
- <p
170
- className="text-xs truncate leading-snug mt-0.5"
171
- style={{ color: "rgba(0,0,0,0.42)" }}
172
- >
63
+ <p className="text-[13px] text-[#9096a0] truncate leading-snug mt-1">
173
64
  {subtitle}
174
65
  </p>
175
66
  )}
176
67
  </div>
68
+
69
+ {/* Hover stroke overlay — violet accent to differentiate sections from blocks. */}
70
+ <span
71
+ aria-hidden="true"
72
+ className="absolute inset-0 rounded-2xl pointer-events-none"
73
+ style={{
74
+ boxShadow: isHovered ? "inset 0 0 0 2px #7500D5" : "inset 0 0 0 2px transparent",
75
+ transition: "box-shadow 160ms ease",
76
+ }}
77
+ />
177
78
  </button>
178
79
  );
179
80
  }
@@ -256,7 +157,7 @@ export default function SectionTypePicker({
256
157
  onClick={onClose}
257
158
  >
258
159
  <div
259
- className="w-full max-w-2xl rounded-2xl bg-white max-h-[80vh] flex flex-col shadow-2xl border border-neutral-200/50 overflow-hidden"
160
+ className="w-full max-w-4xl rounded-2xl bg-white max-h-[80vh] flex flex-col shadow-2xl border border-neutral-200/50 overflow-hidden"
260
161
  style={{ fontFamily: "Inter, system-ui, sans-serif" }}
261
162
  onClick={(e) => e.stopPropagation()}
262
163
  >
@@ -311,14 +212,14 @@ export default function SectionTypePicker({
311
212
  }
312
213
  onClose();
313
214
  }}
314
- className="rounded-xl border border-neutral-200 bg-white p-3 hover:border-[#076bff] hover:bg-[#076bff]/5 transition-colors group shadow-sm"
215
+ className="rounded-xl border border-neutral-200 bg-white p-3 hover:border-[#4794e2] hover:bg-[#4794e2]/5 transition-colors group shadow-sm"
315
216
  title={label}
316
217
  >
317
218
  <div className="flex gap-1 h-6">
318
219
  {widths.map((w, i) => (
319
220
  <div
320
221
  key={i}
321
- className="bg-neutral-200 group-hover:bg-[#076bff]/30 rounded-sm transition-colors"
222
+ className="bg-neutral-200 group-hover:bg-[#4794e2]/30 rounded-sm transition-colors"
322
223
  style={{ flex: w }}
323
224
  />
324
225
  ))}
@@ -337,13 +238,11 @@ export default function SectionTypePicker({
337
238
  </p>
338
239
  <div className="grid grid-cols-2 gap-2.5">
339
240
  {SECTION_TYPE_REGISTRY.map((section) => {
340
- const IconComponent = SECTION_ICON_COMPONENTS[section.type];
341
- const gradient = SECTION_GRADIENTS[section.type] || "#f5f5f5";
241
+ const IconComponent = SECTION_CARD_ICONS[section.type];
342
242
 
343
243
  return (
344
244
  <SectionCard
345
245
  key={section.type}
346
- gradient={gradient}
347
246
  icon={IconComponent ? <IconComponent /> : null}
348
247
  title={section.label}
349
248
  subtitle={section.description}
@@ -380,8 +279,7 @@ export default function SectionTypePicker({
380
279
  {/* Create new section button — always visible immediately */}
381
280
  {onCreateCustomSection && (
382
281
  <SectionCard
383
- gradient="linear-gradient(135deg, #f3f0ff 0%, #ede5ff 50%, #e0d4fc 100%)"
384
- icon={<span className="text-[#8b5cf6] text-xl font-light">+</span>}
282
+ icon={(() => { const I = SECTION_CARD_ICONS.createCustom; return I ? <I /> : null; })()}
385
283
  title="Create New"
386
284
  subtitle="Build a reusable section"
387
285
  isHovered={hovered === "create-custom"}
@@ -400,13 +298,12 @@ export default function SectionTypePicker({
400
298
  {[1, 2].map((i) => (
401
299
  <div
402
300
  key={`skeleton-${i}`}
403
- className="flex items-center gap-3 rounded-2xl px-3.5 py-3 animate-pulse"
404
- style={{ background: "linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%)" }}
301
+ className="flex items-center rounded-2xl h-[96px] bg-[#f4f4f4] animate-pulse overflow-hidden"
405
302
  >
406
- <div className="w-11 h-11 rounded-xl bg-white/60" />
407
- <div className="flex-1 space-y-1.5">
408
- <div className="h-3.5 bg-white/60 rounded w-24" />
409
- <div className="h-2.5 bg-white/40 rounded w-16" />
303
+ <div className="w-[176px] h-full bg-white/40" />
304
+ <div className="flex-1 pr-5 py-4 space-y-2">
305
+ <div className="h-4 bg-white/60 rounded w-28" />
306
+ <div className="h-3 bg-white/50 rounded w-20" />
410
307
  </div>
411
308
  </div>
412
309
  ))}
@@ -424,8 +321,7 @@ export default function SectionTypePicker({
424
321
  {!loadingSaved && savedSections.map((section) => (
425
322
  <SectionCard
426
323
  key={section._id}
427
- gradient="linear-gradient(135deg, #ede9fe 0%, #ddd6fe 50%, #c4b5fd 100%)"
428
- icon={<SavedSectionIcon size={20} />}
324
+ icon={(() => { const I = SECTION_CARD_ICONS.savedCustom; return I ? <I /> : null; })()}
429
325
  title={section.title}
430
326
  subtitle={section.description}
431
327
  isHovered={hovered === section._id}
@@ -285,7 +285,7 @@ export default function SectionV2Canvas({
285
285
  : showAsDropTarget
286
286
  ? "border-blue-500/40 text-blue-500/60 bg-blue-500/5 opacity-100"
287
287
  : isSectionHovered
288
- ? "border-[#076bff]/25 text-[#076bff]/50 hover:text-[#076bff] hover:border-[#076bff]/60 hover:bg-[#076bff]/5 opacity-100"
288
+ ? "border-[#4794e2]/25 text-[#4794e2]/50 hover:text-[#4794e2] hover:border-[#4794e2]/60 hover:bg-[#4794e2]/5 opacity-100"
289
289
  : "border-transparent text-transparent opacity-0 pointer-events-none"
290
290
  }`}
291
291
  >
@@ -84,17 +84,17 @@ function ResizeHandle({
84
84
  height: isActive ? 16 : isHoveredEdge ? 56 : showChrome ? 56 : 32,
85
85
  borderRadius: isActive ? "50%" : 999,
86
86
  backgroundColor: isActive
87
- ? "rgba(7, 107, 255, 0.9)"
87
+ ? "rgba(71, 148, 226, 0.9)"
88
88
  : isHoveredEdge
89
- ? "rgba(7, 107, 255, 0.7)"
89
+ ? "rgba(71, 148, 226, 0.7)"
90
90
  : showChrome
91
- ? "rgba(7, 107, 255, 0.5)"
92
- : "rgba(7, 107, 255, 0.2)",
91
+ ? "rgba(71, 148, 226, 0.5)"
92
+ : "rgba(71, 148, 226, 0.2)",
93
93
  transition: "width 150ms ease-out, height 150ms ease-out, border-radius 150ms ease-out, background-color 150ms, box-shadow 150ms",
94
94
  boxShadow: isActive
95
- ? "0 0 10px rgba(7, 107, 255, 0.5)"
95
+ ? "0 0 10px rgba(71, 148, 226, 0.5)"
96
96
  : isHoveredEdge
97
- ? "0 0 6px rgba(7, 107, 255, 0.2)"
97
+ ? "0 0 6px rgba(71, 148, 226, 0.2)"
98
98
  : undefined,
99
99
  }}
100
100
  />
@@ -287,15 +287,15 @@ export default function SectionV2Column({
287
287
  style={{
288
288
  transition: "box-shadow 150ms, border 150ms",
289
289
  ...(isSwapTarget
290
- ? { boxShadow: `inset 0 0 0 2px ${BUILDER_BLUE}`, background: "rgba(7, 107, 255, 0.08)" }
290
+ ? { boxShadow: `inset 0 0 0 2px ${BUILDER_BLUE}`, background: "rgba(71, 148, 226, 0.08)" }
291
291
  : isBlockOver
292
292
  ? { boxShadow: `inset 0 0 0 2px ${BUILDER_BLUE}` }
293
293
  : isSelected
294
- ? { boxShadow: `inset 0 0 0 2px rgba(7, 107, 255, 0.6)` }
294
+ ? { boxShadow: `inset 0 0 0 2px rgba(71, 148, 226, 0.6)` }
295
295
  : isHovered
296
- ? { boxShadow: `inset 0 0 0 1.5px rgba(7, 107, 255, 0.5)` }
296
+ ? { boxShadow: `inset 0 0 0 1.5px rgba(71, 148, 226, 0.5)` }
297
297
  : showFaintOutline
298
- ? { border: `1px dashed rgba(7, 107, 255, 0.2)`, borderRadius: 4 }
298
+ ? { border: `1px dashed rgba(71, 148, 226, 0.2)`, borderRadius: 4 }
299
299
  : undefined),
300
300
  }}
301
301
  />
@@ -350,18 +350,6 @@ export default function SectionV2Column({
350
350
  </>
351
351
  )}
352
352
 
353
- {/* Span badge — top right */}
354
- <div
355
- className={`absolute top-0 right-0 z-[5] transition-opacity ${
356
- isSelected || isHovered ? "opacity-100" : showFaintOutline ? "opacity-40" : "opacity-0"
357
- }`}
358
- style={{ transform: `scale(${1 / canvasZoom})`, transformOrigin: "top right" }}
359
- >
360
- <span className="text-[11px] px-1.5 py-0.5 rounded-bl rounded-tr bg-[#076bff]/80 text-white/90 font-medium tabular-nums">
361
- {column.span}/{gridColumns}
362
- </span>
363
- </div>
364
-
365
353
  {/* Delete button — red circle top right, positioned outside the column box.
366
354
  Nested pattern: outer div positions, inner div counter-scales. */}
367
355
  <div
@@ -378,7 +366,8 @@ export default function SectionV2Column({
378
366
  <div style={{ transform: `scale(${1 / canvasZoom})`, transformOrigin: "center" }}>
379
367
  <button
380
368
  onClick={handleDelete}
381
- className="w-5 h-5 rounded-full bg-red-500 text-white flex items-center justify-center shadow-md transition-transform hover:scale-[1.15] hover:bg-red-600 hover:shadow-red-500/30 hover:shadow-lg"
369
+ className="w-5 h-5 rounded-full text-white flex items-center justify-center transition-transform hover:scale-[1.15]"
370
+ style={{ background: "#ef4848" }}
382
371
  title="Delete column"
383
372
  aria-label="Delete column"
384
373
  >
@@ -403,7 +392,7 @@ export default function SectionV2Column({
403
392
  >
404
393
  <div style={{ transform: `scale(${1 / canvasZoom})`, transformOrigin: "center" }}>
405
394
  <div
406
- className="w-5 h-5 rounded-full bg-[#076bff] text-white flex items-center justify-center shadow-md cursor-grab active:cursor-grabbing transition-transform hover:scale-[1.15] hover:bg-[#0559d4] hover:shadow-blue-500/30 hover:shadow-lg"
395
+ className="w-5 h-5 rounded-full bg-[#4794e2] text-white flex items-center justify-center shadow-md cursor-grab active:cursor-grabbing transition-transform hover:scale-[1.15] hover:bg-[#3578b8] hover:shadow-blue-500/30 hover:shadow-lg"
407
396
  title="Drag to move column"
408
397
  aria-label="Move column"
409
398
  onMouseDown={(e) => {
@@ -479,9 +468,9 @@ export default function SectionV2Column({
479
468
  style={{
480
469
  padding: "5px 16px",
481
470
  pointerEvents: showChrome || showFaintOutline ? "auto" : "none",
482
- background: showChrome ? "rgba(13, 150, 104, 0.12)" : "rgba(13, 150, 104, 0.06)",
483
- color: "#0d9668",
484
- border: `1px dashed ${showChrome ? "rgba(13, 150, 104, 0.5)" : "rgba(13, 150, 104, 0.25)"}`,
471
+ background: "#d2e3ff",
472
+ color: "#4794e2",
473
+ border: "1px dashed #4794e2",
485
474
  }}
486
475
  >
487
476
  + Add Block
@@ -508,9 +497,9 @@ export default function SectionV2Column({
508
497
  style={{
509
498
  padding: "4px 14px",
510
499
  pointerEvents: showChrome ? "auto" : "none",
511
- background: "rgba(13, 150, 104, 0.12)",
512
- color: "#0d9668",
513
- border: "1px dashed rgba(13, 150, 104, 0.5)",
500
+ background: "#d2e3ff",
501
+ color: "#4794e2",
502
+ border: "1px dashed #4794e2",
514
503
  }}
515
504
  >
516
505
  + Add Block
@@ -58,7 +58,6 @@ export default function SettingsPanel() {
58
58
  selectedColumnV2,
59
59
  selectedBlock,
60
60
  panelTitle,
61
- headerGradient,
62
61
  HeaderIconComponent,
63
62
  isColumnOnly,
64
63
  isParallaxGroupOnly,
@@ -93,44 +92,21 @@ export default function SettingsPanel() {
93
92
 
94
93
  return (
95
94
  <div className="w-72 border-l border-[#f0f0f0] bg-white overflow-y-auto shrink-0 flex flex-col">
96
- {/* Panel header — gradient + icon, matching add-block card style */}
97
- <div
98
- className="relative flex items-center px-3.5 py-3 shrink-0 overflow-hidden"
99
- style={{ background: headerGradient }}
100
- >
101
- {/* Glass overlay */}
102
- <div
103
- className="absolute inset-0 pointer-events-none"
104
- style={{
105
- background: "linear-gradient(135deg, rgba(255,255,255,0.25) 0%, rgba(255,255,255,0.05) 100%)",
106
- }}
107
- />
108
-
109
- {/* Icon container — frosted glass */}
110
- <div
111
- className="relative shrink-0 flex items-center justify-center"
112
- style={{
113
- width: 36,
114
- height: 36,
115
- borderRadius: 10,
116
- background: "rgba(255,255,255,0.4)",
117
- backdropFilter: "blur(8px)",
118
- boxShadow: "0 2px 8px rgba(0,0,0,0.06), inset 0 1px 0 rgba(255,255,255,0.5)",
119
- }}
120
- >
121
- {HeaderIconComponent ? <HeaderIconComponent size={22} /> : null}
95
+ {/* Panel header — flat gray, matching the new Add Block card style */}
96
+ <div className="relative flex items-center px-3.5 py-3 shrink-0 bg-[#f4f4f4]">
97
+ {/* Icon landscape aspect (card icon at smaller scale). Bumped to
98
+ size 40 now that the new icons carry less construction detail. */}
99
+ <div className="shrink-0 flex items-center" style={{ height: 44 }}>
100
+ {HeaderIconComponent ? <HeaderIconComponent size={40} /> : null}
122
101
  </div>
123
102
 
124
103
  {/* Title */}
125
- <h3
126
- className="relative z-10 ml-2.5 text-[13px] font-semibold truncate"
127
- style={{ color: "rgba(0,0,0,0.72)", textShadow: "0 1px 0 rgba(255,255,255,0.3)" }}
128
- >
104
+ <h3 className="ml-2.5 text-[13px] font-semibold text-[#2b2f38] truncate">
129
105
  {panelTitle}
130
106
  </h3>
131
107
 
132
108
  {/* Action button — single delete for the active selection (block > column > section priority) */}
133
- <div className="relative z-10 flex items-center gap-0.5 ml-auto">
109
+ <div className="flex items-center gap-0.5 ml-auto">
134
110
  {(() => {
135
111
  // Determine the single delete action based on selection priority: block > column > section
136
112
  let onDelete: (() => void) | null = null;
@@ -11,7 +11,7 @@ import type { ContentBlock, ImageBlock, VideoBlock } from "../../lib/sanity/type
11
11
  import BlockLivePreview from "./BlockLivePreview";
12
12
  import { getBlockAlignmentStyles, hasBlockAlignment } from "../../lib/builder/layout-styles";
13
13
  import type { BlockLayout } from "../../lib/sanity/types";
14
- import { BUILDER_ORANGE } from "../../lib/builder/constants";
14
+ import { BUILDER_BLOCK } from "../../lib/builder/constants";
15
15
 
16
16
  interface SortableBlockProps {
17
17
  block: ContentBlock;
@@ -124,7 +124,7 @@ export default function SortableBlock({
124
124
  style={{ ...style, ...(!isFillBlock ? { position: "relative" as const, zIndex: 1 } : {}) }}
125
125
  className={`transition-[opacity,box-shadow] ${
126
126
  isDragging
127
- ? "ring-2 ring-[#0d9668] ring-offset-1 ring-offset-transparent rounded"
127
+ ? "ring-2 ring-[#4794e2] ring-offset-1 ring-offset-transparent rounded"
128
128
  : ""
129
129
  }`}
130
130
  onClick={(e) => {
@@ -143,9 +143,9 @@ export default function SortableBlock({
143
143
  style={{
144
144
  inset: `${Math.max(2, Math.min(5, 3 / canvasZoom))}px`,
145
145
  ...(isSelected
146
- ? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px ${BUILDER_ORANGE}` }
146
+ ? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px ${BUILDER_BLOCK}` }
147
147
  : isHovered
148
- ? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px rgba(13, 150, 104, 0.4)` }
148
+ ? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px rgba(71, 148, 226, 0.4)` }
149
149
  : {}),
150
150
  }}
151
151
  />
@@ -166,17 +166,37 @@ export default function SortableBlock({
166
166
  style={{ transform: `scale(${Math.min(2, 1 / canvasZoom)})`, transformOrigin: "top center" }}
167
167
  >
168
168
  <div className="flex items-center rounded-[5px] overflow-hidden" style={{
169
- background: "linear-gradient(170deg, rgba(38,38,48,0.97) 0%, rgba(28,28,36,0.98) 100%)",
170
- boxShadow: "0 4px 12px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.06)",
171
- border: "1px solid rgba(255,255,255,0.06)",
169
+ background: "#d2e3ff",
170
+ border: "1px solid #4794e2",
172
171
  }}>
172
+ {/* Block type label — first */}
173
+ <span className="text-[11px] px-1.5 py-0.5 font-medium" style={{ color: "#4794e2" }}>
174
+ {info?.icon || "▪"} {info?.label || block._type}
175
+ </span>
176
+ {/* Enter animation badge */}
177
+ {block.enter_animation?.preset && block.enter_animation.preset !== "none" && (
178
+ <span className="text-[10px] text-[#4794e2]/50 px-1 py-0.5 border-l border-[#4794e2]/25" title={`Animation: ${block.enter_animation.preset}`}>
179
+
180
+ </span>
181
+ )}
182
+ {/* Duplicate */}
183
+ {onDuplicate && (
184
+ <button
185
+ onClick={onDuplicate}
186
+ className="text-[#4794e2]/60 hover:text-[#4794e2] transition-colors px-1.5 py-0.5 text-[11px] border-l border-[#4794e2]/25 hover:bg-[#4794e2]/10"
187
+ title="Duplicate block (Ctrl+D)"
188
+ aria-label="Duplicate block"
189
+ >
190
+
191
+ </button>
192
+ )}
173
193
  {/* Move up arrow */}
174
194
  <button
175
195
  onClick={() => canMoveUp && reorderBlocks(rowKey, colKey, blockIndex, blockIndex - 1)}
176
- className={`transition-colors px-1 py-0.5 text-[11px] ${
196
+ className={`transition-colors px-1 py-0.5 text-[11px] border-l border-[#4794e2]/25 ${
177
197
  canMoveUp
178
- ? "text-white/45 hover:text-white/80 hover:bg-white/10 cursor-pointer"
179
- : "text-white/20 cursor-default"
198
+ ? "text-[#4794e2]/60 hover:text-[#4794e2] hover:bg-[#4794e2]/10 cursor-pointer"
199
+ : "text-[#4794e2]/25 cursor-default"
180
200
  }`}
181
201
  title="Move block up"
182
202
  aria-label="Move block up"
@@ -189,10 +209,10 @@ export default function SortableBlock({
189
209
  {/* Move down arrow */}
190
210
  <button
191
211
  onClick={() => canMoveDown && reorderBlocks(rowKey, colKey, blockIndex, blockIndex + 1)}
192
- className={`transition-colors px-1 py-0.5 text-[11px] border-l border-white/10 ${
212
+ className={`transition-colors px-1 py-0.5 text-[11px] border-l border-[#4794e2]/25 ${
193
213
  canMoveDown
194
- ? "text-white/45 hover:text-white/80 hover:bg-white/10 cursor-pointer"
195
- : "text-white/20 cursor-default"
214
+ ? "text-[#4794e2]/60 hover:text-[#4794e2] hover:bg-[#4794e2]/10 cursor-pointer"
215
+ : "text-[#4794e2]/25 cursor-default"
196
216
  }`}
197
217
  title="Move block down"
198
218
  aria-label="Move block down"
@@ -202,44 +222,16 @@ export default function SortableBlock({
202
222
  <path d="M5 8L2 4h6L5 8z" fill="currentColor" />
203
223
  </svg>
204
224
  </button>
205
- {/* Block type label */}
206
- <span className="text-[11px] px-1.5 py-0.5 border-l border-white/10 font-medium" style={{ color: "rgba(100,220,170,0.9)" }}>
207
- {info?.icon || "▪"} {info?.label || block._type}
208
- </span>
209
- {/* Enter animation badge */}
210
- {block.enter_animation?.preset && block.enter_animation.preset !== "none" && (
211
- <span className="text-[10px] text-white/35 px-1 py-0.5 border-l border-white/10" title={`Animation: ${block.enter_animation.preset}`}>
212
-
213
- </span>
214
- )}
215
- {/* Duplicate */}
216
- {onDuplicate && (
217
- <button
218
- onClick={onDuplicate}
219
- className="text-white/45 hover:text-white/80 transition-colors px-1.5 py-0.5 text-[11px] border-l border-white/10 hover:bg-white/10"
220
- title="Duplicate block (Ctrl+D)"
221
- aria-label="Duplicate block"
222
- >
223
-
224
- </button>
225
- )}
225
+ {/* Delete text inside pill, red hover for destructive signal */}
226
+ <button
227
+ onClick={onDelete}
228
+ className="text-[#4794e2]/60 hover:text-red-500 hover:bg-red-500/10 transition-colors px-1.5 py-0.5 text-[11px] font-medium border-l border-[#4794e2]/25"
229
+ title="Delete block"
230
+ aria-label="Delete block"
231
+ >
232
+ Delete
233
+ </button>
226
234
  </div>
227
- {/* Delete button — inline at end of toolbar with gap separator */}
228
- <button
229
- onClick={onDelete}
230
- className="w-5 h-5 rounded-full bg-red-500 text-white flex items-center justify-center hover:bg-red-600 transition-colors shadow-md shrink-0"
231
- title="Delete block"
232
- aria-label="Delete block"
233
- >
234
- <svg width="10" height="10" viewBox="0 0 10 10">
235
- <path
236
- d="M2 2l6 6M8 2l-6 6"
237
- stroke="currentColor"
238
- strokeWidth="1.5"
239
- strokeLinecap="round"
240
- />
241
- </svg>
242
- </button>
243
235
  </div>
244
236
  </div>
245
237