@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
@@ -155,7 +155,7 @@ export default function SectionEditorBar({ onSaveComplete }: SectionEditorBarPro
155
155
  value={name}
156
156
  onChange={(e) => setName(e.target.value)}
157
157
  placeholder="Section name..."
158
- className="bg-[#2a2a2a] text-white text-sm px-3 py-1.5 rounded border border-[#3a3a3a] focus:border-[#076bff] focus:outline-none w-64 transition-colors text-center"
158
+ className="bg-[#2a2a2a] text-white text-sm px-3 py-1.5 rounded border border-[#3a3a3a] focus:border-[#3580f9] focus:outline-none w-64 transition-colors text-center"
159
159
  />
160
160
  </div>
161
161
 
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useState, useEffect } from "react";
4
- import { SECTION_TYPE_REGISTRY } from "../../lib/builder/types";
4
+ import { SECTION_TYPE_REGISTRY, type SectionBlockType } from "../../lib/builder/types";
5
5
  import type { CustomSectionListItem } from "../../lib/sanity/types";
6
6
  import { SECTION_CARD_ICONS } from "./SectionCardIcons";
7
7
 
@@ -83,7 +83,7 @@ function SectionCard({
83
83
 
84
84
  interface SectionTypePickerProps {
85
85
  onSelectEmptyV2?: (preset: "full" | "halves" | "thirds" | "quarters" | "1/3+2/3" | "2/3+1/3") => void;
86
- onSelectSection: (blockType: "projectGridBlock" | "projectCarouselBlock") => void;
86
+ onSelectSection: (blockType: SectionBlockType) => void;
87
87
  onSelectParallaxGroup?: () => void;
88
88
  onSelectCoverSection?: () => void;
89
89
  onSelectCustomSection?: (section: CustomSectionListItem) => void;
@@ -212,14 +212,14 @@ export default function SectionTypePicker({
212
212
  }
213
213
  onClose();
214
214
  }}
215
- className="rounded-xl border border-neutral-200 bg-white p-3 hover:border-[#4794e2] hover:bg-[#4794e2]/5 transition-colors group shadow-sm"
215
+ className="rounded-xl border border-neutral-200 bg-white p-3 hover:border-[#3580f9] hover:bg-[#3580f9]/5 transition-colors group shadow-sm"
216
216
  title={label}
217
217
  >
218
218
  <div className="flex gap-1 h-6">
219
219
  {widths.map((w, i) => (
220
220
  <div
221
221
  key={i}
222
- className="bg-neutral-200 group-hover:bg-[#4794e2]/30 rounded-sm transition-colors"
222
+ className="bg-neutral-200 group-hover:bg-[#3580f9]/30 rounded-sm transition-colors"
223
223
  style={{ flex: w }}
224
224
  />
225
225
  ))}
@@ -310,7 +310,7 @@ export default function SectionV2Canvas({
310
310
  : showAsDropTarget
311
311
  ? "border-blue-500/40 text-blue-500/60 bg-blue-500/5 opacity-100"
312
312
  : isSectionHovered
313
- ? "border-[#4794e2]/25 text-[#4794e2]/50 hover:text-[#4794e2] hover:border-[#4794e2]/60 hover:bg-[#4794e2]/5 opacity-100"
313
+ ? "border-[#3580f9]/25 text-[#3580f9]/50 hover:text-[#3580f9] hover:border-[#3580f9]/60 hover:bg-[#3580f9]/5 opacity-100"
314
314
  : "border-transparent text-transparent opacity-0 pointer-events-none"
315
315
  }`}
316
316
  >
@@ -12,6 +12,7 @@ import type { SectionColumn, ContentBlock, PageSectionV2 } from "../../lib/sanit
12
12
  import { getColumnVerticalAlign } from "../../lib/builder/layout-styles";
13
13
  import { isSectionBlockType } from "../../lib/builder/types";
14
14
  import { BUILDER_BLUE } from "../../lib/builder/constants";
15
+ import { BubbleTooltip, CloseIcon, DragDropIcon } from "./BubbleIcons";
15
16
 
16
17
  // ============================================
17
18
  // SectionV2Column — Individual column in a V2 section grid
@@ -84,17 +85,17 @@ function ResizeHandle({
84
85
  height: isActive ? 16 : isHoveredEdge ? 56 : showChrome ? 56 : 32,
85
86
  borderRadius: isActive ? "50%" : 999,
86
87
  backgroundColor: isActive
87
- ? "rgba(71, 148, 226, 0.9)"
88
+ ? "rgba(53, 128, 249, 0.9)"
88
89
  : isHoveredEdge
89
- ? "rgba(71, 148, 226, 0.7)"
90
+ ? "rgba(53, 128, 249, 0.7)"
90
91
  : showChrome
91
- ? "rgba(71, 148, 226, 0.5)"
92
- : "rgba(71, 148, 226, 0.2)",
92
+ ? "rgba(53, 128, 249, 0.5)"
93
+ : "rgba(53, 128, 249, 0.2)",
93
94
  transition: "width 150ms ease-out, height 150ms ease-out, border-radius 150ms ease-out, background-color 150ms, box-shadow 150ms",
94
95
  boxShadow: isActive
95
- ? "0 0 10px rgba(71, 148, 226, 0.5)"
96
+ ? "0 0 10px rgba(53, 128, 249, 0.5)"
96
97
  : isHoveredEdge
97
- ? "0 0 6px rgba(71, 148, 226, 0.2)"
98
+ ? "0 0 6px rgba(53, 128, 249, 0.2)"
98
99
  : undefined,
99
100
  }}
100
101
  />
@@ -289,17 +290,17 @@ export default function SectionV2Column({
289
290
  <div
290
291
  className="pointer-events-none absolute inset-0 z-[1] rounded"
291
292
  style={{
292
- transition: "box-shadow 150ms, border 150ms",
293
+ transition: "box-shadow 150ms",
293
294
  ...(isSwapTarget
294
- ? { boxShadow: `inset 0 0 0 2px ${BUILDER_BLUE}`, background: "rgba(71, 148, 226, 0.08)" }
295
+ ? { boxShadow: `inset 0 0 0 2px ${BUILDER_BLUE}`, background: "rgba(53, 128, 249, 0.08)" }
295
296
  : isBlockOver
296
297
  ? { boxShadow: `inset 0 0 0 2px ${BUILDER_BLUE}` }
297
298
  : isSelected
298
- ? { boxShadow: `inset 0 0 0 2px rgba(71, 148, 226, 0.6)` }
299
+ ? { boxShadow: `inset 0 0 0 2px rgba(53, 128, 249, 0.6)` }
299
300
  : isHovered
300
- ? { boxShadow: `inset 0 0 0 1.5px rgba(71, 148, 226, 0.5)` }
301
+ ? { boxShadow: `inset 0 0 0 1.5px rgba(53, 128, 249, 0.5)` }
301
302
  : showFaintOutline
302
- ? { border: `1px dashed rgba(71, 148, 226, 0.2)`, borderRadius: 4 }
303
+ ? { boxShadow: `inset 0 0 0 1px rgba(53, 128, 249, 0.2)`, borderRadius: 4 }
303
304
  : undefined),
304
305
  }}
305
306
  />
@@ -354,64 +355,65 @@ export default function SectionV2Column({
354
355
  </>
355
356
  )}
356
357
 
357
- {/* Delete buttonred circle top right, positioned outside the column box.
358
+ {/* Column pillsingle horizontal pill sitting OUTSIDE the column top-left edge,
359
+ with a 2px gap so its border never touches the column outline. Structure:
360
+ [drag-drop] | Col | [X]. Aligns horizontally with the block pill when the
361
+ first block in the column has no vertical offset.
358
362
  Nested pattern: outer div positions, inner div counter-scales. */}
359
363
  <div
360
- className={`absolute z-[6] transition-opacity ${
364
+ className={`absolute top-0 left-0 z-[6] transition-opacity ${
361
365
  showChrome ? "opacity-100" : "opacity-0 pointer-events-none"
362
366
  }`}
363
- style={{
364
- top: 0,
365
- right: 0,
366
- transform: "translate(40%, -40%)",
367
- }}
367
+ style={{ transform: "translateY(calc(-100% - 2px))" }}
368
368
  onClick={(e) => e.stopPropagation()}
369
369
  >
370
- <div style={{ transform: `scale(${1 / canvasZoom})`, transformOrigin: "center" }}>
371
- <button
372
- onClick={handleDelete}
373
- className="w-5 h-5 rounded-full text-white flex items-center justify-center transition-transform hover:scale-[1.15]"
374
- style={{ background: "#ef4848" }}
375
- title="Delete column"
376
- aria-label="Delete column"
377
- >
378
- <svg width="10" height="10" viewBox="0 0 10 10">
379
- <path d="M2 2l6 6M8 2l-6 6" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
380
- </svg>
381
- </button>
382
- </div>
383
- </div>
384
-
385
- {/* Drag grip — top left corner, uses @dnd-kit useDraggable.
386
- Nested pattern: outer div positions, inner div counter-scales. */}
387
- <div
388
- className={`absolute z-[6] transition-opacity ${
389
- showChrome ? "opacity-100" : "opacity-0 pointer-events-none"
390
- }`}
391
- style={{
392
- top: 0,
393
- left: 0,
394
- transform: "translate(-30%, -30%)",
395
- }}
396
- >
397
- <div style={{ transform: `scale(${1 / canvasZoom})`, transformOrigin: "center" }}>
398
- <div
399
- 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"
400
- title="Drag to move column"
401
- aria-label="Move column"
402
- onMouseDown={(e) => {
403
- e.stopPropagation();
404
- onStartDrag?.(e);
405
- }}
406
- onClick={(e) => {
407
- e.stopPropagation();
408
- onSelect();
409
- }}
410
- >
411
- <svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor">
412
- <path d="M8 0l2.5 3h-2v4.5H13v-2L16 8l-3 2.5v-2H8.5V13h2L8 16l-2.5-3h2V8.5H3v2L0 8l3-2.5v2h4.5V3h-2L8 0z" />
413
- </svg>
414
- </div>
370
+ <div style={{ transform: `scale(${Math.min(2, 1 / canvasZoom)})`, transformOrigin: "bottom left" }}>
371
+ <div
372
+ className="flex items-center rounded-[5px]"
373
+ style={{
374
+ background: "#c0d7ff",
375
+ border: "1.5px solid #3580f9",
376
+ }}
377
+ >
378
+ {/* Drag handle starts a column drag via @dnd-kit, selects on click */}
379
+ <button
380
+ type="button"
381
+ className="group/bb relative text-[#3580f9] hover:bg-[#3580f9]/15 transition-colors px-1.5 py-1 flex items-center justify-center cursor-grab active:cursor-grabbing rounded-l-[3px]"
382
+ aria-label="Move column"
383
+ onMouseDown={(e) => {
384
+ e.stopPropagation();
385
+ onStartDrag?.(e);
386
+ }}
387
+ onClick={(e) => {
388
+ e.stopPropagation();
389
+ onSelect();
390
+ }}
391
+ >
392
+ <DragDropIcon size={14} />
393
+ <BubbleTooltip>Drag to move</BubbleTooltip>
394
+ </button>
395
+ <div className="w-px self-stretch my-1" style={{ background: "#3580f9" }} />
396
+ {/* Label — clicking selects the column */}
397
+ <button
398
+ type="button"
399
+ onClick={(e) => { e.stopPropagation(); onSelect(); }}
400
+ className="text-[11px] px-2 py-0.5 font-medium hover:bg-[#3580f9]/15 transition-colors"
401
+ style={{ color: "#3580f9" }}
402
+ aria-label="Select column"
403
+ >
404
+ Col
405
+ </button>
406
+ <div className="w-px self-stretch my-1" style={{ background: "#3580f9" }} />
407
+ {/* Delete */}
408
+ <button
409
+ onClick={handleDelete}
410
+ className="group/bb relative text-[#3580f9] hover:text-red-500 hover:bg-red-500/10 transition-colors px-1.5 py-1 flex items-center justify-center rounded-r-[3px]"
411
+ aria-label="Delete column"
412
+ >
413
+ <CloseIcon size={14} />
414
+ <BubbleTooltip>Delete</BubbleTooltip>
415
+ </button>
416
+ </div>
415
417
  </div>
416
418
  </div>
417
419
 
@@ -473,8 +475,8 @@ export default function SectionV2Column({
473
475
  padding: "5px 16px",
474
476
  pointerEvents: showChrome || showFaintOutline ? "auto" : "none",
475
477
  background: "#d2e3ff",
476
- color: "#4794e2",
477
- border: "1px dashed #4794e2",
478
+ color: "#3580f9",
479
+ border: "1.5px dashed #3580f9",
478
480
  }}
479
481
  >
480
482
  + Add Block
@@ -502,8 +504,8 @@ export default function SectionV2Column({
502
504
  padding: "4px 14px",
503
505
  pointerEvents: showChrome ? "auto" : "none",
504
506
  background: "#d2e3ff",
505
- color: "#4794e2",
506
- border: "1px dashed #4794e2",
507
+ color: "#3580f9",
508
+ border: "1.5px dashed #3580f9",
507
509
  }}
508
510
  >
509
511
  + Add Block
@@ -12,6 +12,7 @@ import BlockLivePreview from "./BlockLivePreview";
12
12
  import { getBlockAlignmentStyles, hasBlockAlignment } from "../../lib/builder/layout-styles";
13
13
  import type { BlockLayout } from "../../lib/sanity/types";
14
14
  import { BUILDER_BLOCK } from "../../lib/builder/constants";
15
+ import { ArrowDownIcon, ArrowUpIcon, BubbleTooltip, CloseIcon, CopyIcon } from "./BubbleIcons";
15
16
 
16
17
  interface SortableBlockProps {
17
18
  block: ContentBlock;
@@ -124,7 +125,7 @@ export default function SortableBlock({
124
125
  style={{ ...style, ...(!isFillBlock ? { position: "relative" as const, zIndex: 1 } : {}) }}
125
126
  className={`transition-[opacity,box-shadow] ${
126
127
  isDragging
127
- ? "ring-2 ring-[#4794e2] ring-offset-1 ring-offset-transparent rounded"
128
+ ? "ring-2 ring-[#3580f9] ring-offset-1 ring-offset-transparent rounded"
128
129
  : ""
129
130
  }`}
130
131
  onClick={(e) => {
@@ -145,93 +146,112 @@ export default function SortableBlock({
145
146
  ...(isSelected
146
147
  ? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px ${BUILDER_BLOCK}` }
147
148
  : isHovered
148
- ? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px rgba(71, 148, 226, 0.4)` }
149
+ ? { boxShadow: `inset 0 0 0 ${Math.max(2, Math.min(5, 3 / canvasZoom))}px rgba(53, 128, 249, 0.4)` }
149
150
  : {}),
150
151
  }}
151
152
  />
152
153
 
153
- {/* Floating toolbar — centered INSIDE top of block, appears on hover or when selected.
154
+ {/* Floating toolbar — top-right of block, sitting OUTSIDE (above) with a 2px gap
155
+ so the pill border never overlaps the column/block outlines.
156
+ When the block has no vertical offset, this pill naturally aligns horizontally
157
+ with the column pill (both hover just above the column's top edge).
154
158
  Positioning (translate) is separated from counter-scaling (scale) into nested
155
- elements so that zoom changes don't shift the anchor point.
156
- Delete button is integrated at the end of the toolbar with a gap separator. */}
159
+ elements so zoom changes don't shift the anchor point. */}
157
160
  <div
158
- className={`absolute top-0 left-1/2 z-[6] transition-opacity ${
161
+ className={`absolute top-0 right-0 z-[6] transition-opacity ${
159
162
  showToolbar ? "opacity-100" : "opacity-0 pointer-events-none"
160
163
  }`}
161
- style={{ transform: "translateX(-50%)", marginTop: 4 }}
164
+ style={{ transform: "translateY(calc(-100% - 2px))" }}
162
165
  onClick={(e) => e.stopPropagation()}
163
166
  >
164
167
  <div
165
- className="flex items-center gap-1.5"
166
- style={{ transform: `scale(${Math.min(2, 1 / canvasZoom)})`, transformOrigin: "top center" }}
168
+ style={{ transform: `scale(${Math.min(2, 1 / canvasZoom)})`, transformOrigin: "bottom right" }}
167
169
  >
168
- <div className="flex items-center rounded-[5px] overflow-hidden" style={{
169
- background: "#d2e3ff",
170
- border: "1px solid #4794e2",
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 && (
170
+ <div
171
+ className="flex items-center rounded-[5px]"
172
+ style={{
173
+ background: "#c0d7ff",
174
+ border: "1.5px solid #3580f9",
175
+ }}
176
+ >
177
+ {/* Block type label — clicking selects the block */}
184
178
  <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"
179
+ type="button"
180
+ onClick={(e) => { e.stopPropagation(); onSelect(); }}
181
+ className="text-[11px] px-2 py-0.5 font-medium rounded-l-[3px] hover:bg-[#3580f9]/15 transition-colors"
182
+ style={{ color: "#3580f9" }}
183
+ aria-label="Select block"
189
184
  >
190
-
185
+ {info?.label || block._type}
191
186
  </button>
192
- )}
193
- {/* Move up arrow */}
194
- <button
195
- onClick={() => canMoveUp && reorderBlocks(rowKey, colKey, blockIndex, blockIndex - 1)}
196
- className={`transition-colors px-1 py-0.5 text-[11px] border-l border-[#4794e2]/25 ${
197
- canMoveUp
198
- ? "text-[#4794e2]/60 hover:text-[#4794e2] hover:bg-[#4794e2]/10 cursor-pointer"
199
- : "text-[#4794e2]/25 cursor-default"
200
- }`}
201
- title="Move block up"
202
- aria-label="Move block up"
203
- disabled={!canMoveUp}
204
- >
205
- <svg width="10" height="10" viewBox="0 0 10 10" fill="none">
206
- <path d="M5 2L2 6h6L5 2z" fill="currentColor" />
207
- </svg>
208
- </button>
209
- {/* Move down arrow */}
210
- <button
211
- onClick={() => canMoveDown && reorderBlocks(rowKey, colKey, blockIndex, blockIndex + 1)}
212
- className={`transition-colors px-1 py-0.5 text-[11px] border-l border-[#4794e2]/25 ${
213
- canMoveDown
214
- ? "text-[#4794e2]/60 hover:text-[#4794e2] hover:bg-[#4794e2]/10 cursor-pointer"
215
- : "text-[#4794e2]/25 cursor-default"
216
- }`}
217
- title="Move block down"
218
- aria-label="Move block down"
219
- disabled={!canMoveDown}
220
- >
221
- <svg width="10" height="10" viewBox="0 0 10 10" fill="none">
222
- <path d="M5 8L2 4h6L5 8z" fill="currentColor" />
223
- </svg>
224
- </button>
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>
234
- </div>
187
+ {/* Enter animation badge */}
188
+ {block.enter_animation?.preset && block.enter_animation.preset !== "none" && (
189
+ <>
190
+ <div className="w-px self-stretch my-1" style={{ background: "#3580f9" }} />
191
+ <span
192
+ className="text-[10px] px-1 py-0.5"
193
+ style={{ color: "#3580f9" }}
194
+ title={`Animation: ${block.enter_animation.preset}`}
195
+ >
196
+
197
+ </span>
198
+ </>
199
+ )}
200
+ {/* Duplicate */}
201
+ {onDuplicate && (
202
+ <>
203
+ <div className="w-px self-stretch my-1" style={{ background: "#3580f9" }} />
204
+ <button
205
+ onClick={onDuplicate}
206
+ className="group/bb relative text-[#3580f9] hover:bg-[#3580f9]/15 transition-colors px-1.5 py-1 flex items-center justify-center"
207
+ aria-label="Duplicate block"
208
+ >
209
+ <CopyIcon size={14} />
210
+ <BubbleTooltip>Duplicate (⌘D)</BubbleTooltip>
211
+ </button>
212
+ </>
213
+ )}
214
+ {/* Move up */}
215
+ <div className="w-px self-stretch my-1" style={{ background: "#3580f9" }} />
216
+ <button
217
+ onClick={() => canMoveUp && reorderBlocks(rowKey, colKey, blockIndex, blockIndex - 1)}
218
+ className={`group/bb relative transition-colors px-1.5 py-1 flex items-center justify-center ${
219
+ canMoveUp
220
+ ? "text-[#3580f9] hover:bg-[#3580f9]/15 cursor-pointer"
221
+ : "text-[#3580f9]/40 cursor-default"
222
+ }`}
223
+ aria-label="Move block up"
224
+ disabled={!canMoveUp}
225
+ >
226
+ <ArrowUpIcon size={14} />
227
+ {canMoveUp && <BubbleTooltip>Move up</BubbleTooltip>}
228
+ </button>
229
+ {/* Move down */}
230
+ <div className="w-px self-stretch my-1" style={{ background: "#3580f9" }} />
231
+ <button
232
+ onClick={() => canMoveDown && reorderBlocks(rowKey, colKey, blockIndex, blockIndex + 1)}
233
+ className={`group/bb relative transition-colors px-1.5 py-1 flex items-center justify-center ${
234
+ canMoveDown
235
+ ? "text-[#3580f9] hover:bg-[#3580f9]/15 cursor-pointer"
236
+ : "text-[#3580f9]/40 cursor-default"
237
+ }`}
238
+ aria-label="Move block down"
239
+ disabled={!canMoveDown}
240
+ >
241
+ <ArrowDownIcon size={14} />
242
+ {canMoveDown && <BubbleTooltip>Move down</BubbleTooltip>}
243
+ </button>
244
+ {/* Delete — red hover for destructive cue */}
245
+ <div className="w-px self-stretch my-1" style={{ background: "#3580f9" }} />
246
+ <button
247
+ onClick={onDelete}
248
+ className="group/bb relative text-[#3580f9] hover:text-red-500 hover:bg-red-500/10 transition-colors px-1.5 py-1 flex items-center justify-center rounded-r-[3px]"
249
+ aria-label="Delete block"
250
+ >
251
+ <CloseIcon size={14} />
252
+ <BubbleTooltip>Delete</BubbleTooltip>
253
+ </button>
254
+ </div>
235
255
  </div>
236
256
  </div>
237
257
 
@@ -14,6 +14,7 @@ import { getRowLayoutStyles } from "../../lib/builder/layout-styles";
14
14
  import { normalizeMinHeight } from "../../lib/builder/utils";
15
15
  import { getSectionV2SettingValue } from "./settings-panel/responsive-helpers";
16
16
  import { formatRowPercent } from "../../lib/builder/format";
17
+ import { ArrowDownIcon, ArrowUpIcon, BubbleTooltip, CloseIcon, CopyIcon } from "./BubbleIcons";
17
18
 
18
19
  /**
19
20
  * Convert vh-based CSS values to pixels using the simulated device viewport height.
@@ -261,7 +262,7 @@ export default function SortableRow({
261
262
  showToolbar ? "opacity-100" : "opacity-0 pointer-events-none"
262
263
  }`}
263
264
  style={{
264
- transform: `translateX(calc(-100% - 8px)) scale(${Math.min(1.5, 1 / canvasZoom)})`,
265
+ transform: `translateX(calc(-100% - 8px)) scale(${Math.min(2, 1 / canvasZoom)})`,
265
266
  transformOrigin: "top right",
266
267
  width: "90px",
267
268
  }}
@@ -279,7 +280,7 @@ export default function SortableRow({
279
280
  className="flex flex-col items-stretch rounded-lg py-2 px-2.5 gap-1 cursor-grab active:cursor-grabbing"
280
281
  style={{
281
282
  background: "#e0daff",
282
- border: "1px solid #7500d5",
283
+ border: "1.5px solid #7500d5",
283
284
  }}
284
285
  {...attributes}
285
286
  {...listeners}
@@ -294,40 +295,40 @@ export default function SortableRow({
294
295
  <button
295
296
  onClick={(e) => { e.stopPropagation(); onDuplicate(); }}
296
297
  onPointerDown={(e) => e.stopPropagation()}
297
- className="flex items-center justify-center text-[12px] transition-colors"
298
+ className="group/bb relative flex items-center justify-center transition-colors"
298
299
  style={{ color: "rgba(117, 0, 213, 0.6)" }}
299
300
  onMouseEnter={(e) => { e.currentTarget.style.color = "#7500d5"; }}
300
301
  onMouseLeave={(e) => { e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
301
- title="Duplicate section"
302
302
  aria-label="Duplicate section"
303
303
  >
304
-
304
+ <CopyIcon size={14} />
305
+ <BubbleTooltip>Duplicate</BubbleTooltip>
305
306
  </button>
306
307
  <button
307
308
  onClick={(e) => { e.stopPropagation(); onMoveUp(); }}
308
309
  onPointerDown={(e) => e.stopPropagation()}
309
310
  disabled={isFirst}
310
- className="flex items-center justify-center text-[12px] transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
311
+ className="group/bb relative flex items-center justify-center transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
311
312
  style={{ color: "rgba(117, 0, 213, 0.6)" }}
312
313
  onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
313
314
  onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
314
- title="Move up"
315
315
  aria-label="Move section up"
316
316
  >
317
-
317
+ <ArrowUpIcon size={14} />
318
+ {!isFirst && <BubbleTooltip>Move up</BubbleTooltip>}
318
319
  </button>
319
320
  <button
320
321
  onClick={(e) => { e.stopPropagation(); onMoveDown(); }}
321
322
  onPointerDown={(e) => e.stopPropagation()}
322
323
  disabled={isLast}
323
- className="flex items-center justify-center text-[12px] transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
324
+ className="group/bb relative flex items-center justify-center transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
324
325
  style={{ color: "rgba(117, 0, 213, 0.6)" }}
325
326
  onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
326
327
  onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
327
- title="Move down"
328
328
  aria-label="Move section down"
329
329
  >
330
-
330
+ <ArrowDownIcon size={14} />
331
+ {!isLast && <BubbleTooltip>Move down</BubbleTooltip>}
331
332
  </button>
332
333
  </div>
333
334
 
@@ -358,7 +359,7 @@ export default function SortableRow({
358
359
  title="Delete section"
359
360
  aria-label="Delete section"
360
361
  >
361
- <span style={{ color: "rgba(117, 0, 213, 0.4)" }}>-</span> Delete
362
+ <CloseIcon size={12} /> Delete
362
363
  </button>
363
364
  </div>
364
365
 
@@ -375,7 +376,7 @@ export default function SortableRow({
375
376
  className="flex flex-col items-stretch rounded-lg py-1.5 px-2 mt-2 gap-0.5"
376
377
  style={{
377
378
  background: "#e0daff",
378
- border: "1px solid #7500d5",
379
+ border: "1.5px solid #7500d5",
379
380
  }}
380
381
  onClick={(e) => e.stopPropagation()}
381
382
  >
@@ -391,14 +392,14 @@ export default function SortableRow({
391
392
  }}
392
393
  onPointerDown={(e) => e.stopPropagation()}
393
394
  disabled={!canRemoveRow}
394
- className="flex items-center justify-center text-[12px] leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
395
+ className="group/bb relative flex items-center justify-center leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
395
396
  style={{ color: "rgba(117, 0, 213, 0.6)" }}
396
397
  onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
397
398
  onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
398
- title={canRemoveRow ? "Remove row" : "Cover must have at least 1 row"}
399
399
  aria-label="Remove row"
400
400
  >
401
- ×
401
+ <CloseIcon size={12} />
402
+ {canRemoveRow && <BubbleTooltip>Remove row</BubbleTooltip>}
402
403
  </button>
403
404
  </div>
404
405
  ))}
@@ -436,7 +437,7 @@ export default function SortableRow({
436
437
  className="flex flex-col items-stretch rounded-lg py-1.5 px-2 mt-2 gap-0.5"
437
438
  style={{
438
439
  background: "#e0daff",
439
- border: "1px solid #7500d5",
440
+ border: "1.5px solid #7500d5",
440
441
  }}
441
442
  onClick={(e) => e.stopPropagation()}
442
443
  >
@@ -462,14 +463,14 @@ export default function SortableRow({
462
463
  }}
463
464
  onPointerDown={(e) => e.stopPropagation()}
464
465
  disabled={idx === 0}
465
- className="flex items-center justify-center text-[10px] leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
466
+ className="group/bb relative flex items-center justify-center leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
466
467
  style={{ color: "rgba(117, 0, 213, 0.6)" }}
467
468
  onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
468
469
  onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
469
- title="Move slide up"
470
470
  aria-label="Move slide up"
471
471
  >
472
-
472
+ <ArrowUpIcon size={12} />
473
+ {idx > 0 && <BubbleTooltip>Move up</BubbleTooltip>}
473
474
  </button>
474
475
  <button
475
476
  onClick={(e) => {
@@ -478,14 +479,14 @@ export default function SortableRow({
478
479
  }}
479
480
  onPointerDown={(e) => e.stopPropagation()}
480
481
  disabled={idx === slides.length - 1}
481
- className="flex items-center justify-center text-[10px] leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
482
+ className="group/bb relative flex items-center justify-center leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
482
483
  style={{ color: "rgba(117, 0, 213, 0.6)" }}
483
484
  onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
484
485
  onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
485
- title="Move slide down"
486
486
  aria-label="Move slide down"
487
487
  >
488
-
488
+ <ArrowDownIcon size={12} />
489
+ {idx < slides.length - 1 && <BubbleTooltip>Move down</BubbleTooltip>}
489
490
  </button>
490
491
  <button
491
492
  onClick={(e) => {
@@ -494,14 +495,14 @@ export default function SortableRow({
494
495
  }}
495
496
  onPointerDown={(e) => e.stopPropagation()}
496
497
  disabled={!canRemoveSlide}
497
- className="flex items-center justify-center text-[12px] leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
498
+ className="group/bb relative flex items-center justify-center leading-none transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
498
499
  style={{ color: "rgba(117, 0, 213, 0.6)" }}
499
500
  onMouseEnter={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "#7500d5"; }}
500
501
  onMouseLeave={(e) => { if (!e.currentTarget.disabled) e.currentTarget.style.color = "rgba(117, 0, 213, 0.6)"; }}
501
- title={canRemoveSlide ? "Remove slide" : "Parallax must have at least 1 slide"}
502
502
  aria-label="Remove slide"
503
503
  >
504
- ×
504
+ <CloseIcon size={12} />
505
+ {canRemoveSlide && <BubbleTooltip>Remove slide</BubbleTooltip>}
505
506
  </button>
506
507
  </div>
507
508
  </div>