@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
@@ -3,6 +3,7 @@
3
3
  import { useState, useEffect } from "react";
4
4
  import { useThumbStatus } from "../../../lib/contexts/ThumbStatusContext";
5
5
  import { adminAssetUrl, adminThumbUrl } from "../../../lib/assets";
6
+ import { BubbleTooltip } from "../BubbleIcons";
6
7
 
7
8
  // ============================================
8
9
  // Thumbnail status badge (builder-only)
@@ -14,10 +15,11 @@ export function ThumbBadge({ assetPath }: { assetPath: string }) {
14
15
  const status = hasThumb(assetPath);
15
16
  // undefined = not raster or unknown — don't show badge
16
17
  if (status === undefined) return null;
18
+ const label = status ? "Thumbnail available" : "No thumbnail — full resolution";
17
19
  return (
18
20
  <span
19
- title={status ? "Thumbnail available" : "No thumbnail — full resolution"}
20
- className="absolute bottom-1.5 right-1.5 z-10 flex items-center justify-center"
21
+ aria-label={label}
22
+ className="group/bb absolute bottom-1.5 right-1.5 z-10 flex items-center justify-center"
21
23
  style={{
22
24
  width: 16,
23
25
  height: 16,
@@ -38,6 +40,7 @@ export function ThumbBadge({ assetPath }: { assetPath: string }) {
38
40
  <rect x="4.25" y="5" width="1.5" height="3" rx="0.5" fill="white" />
39
41
  </svg>
40
42
  )}
43
+ <BubbleTooltip>{label}</BubbleTooltip>
41
44
  </span>
42
45
  );
43
46
  }
@@ -1,138 +1,138 @@
1
- "use client";
2
-
3
- /**
4
- * AnimationTab — Routes to EnterAnimationPicker / HoverEffectPicker based on selection.
5
- *
6
- * Extracted from SettingsPanel.tsx in Session C (refactor split).
7
- */
8
-
9
- import { useBuilderStore } from "../../../lib/builder/store";
10
- import type { ContentBlock, ProjectGridBlock } from "../../../lib/sanity/types";
11
- import type { HoverEffectConfig } from "../../../lib/animation/hover-effect-types";
12
- import EnterAnimationPicker from "../editors/EnterAnimationPicker";
13
- import HoverEffectPicker from "../editors/HoverEffectPicker";
14
- import {
15
- getBlockAnimationValue,
16
- hasBlockAnimationOverride,
17
- setBlockAnimationOverride,
18
- } from "./responsive-helpers";
19
- import { CardEntranceSection } from "./CardEntranceSection";
20
-
21
- /** Safely extract hover_effect (new unified type) from any content block.
22
- * ProjectGridBlock has a legacy `hover_effect: "3d" | "scale" | "none"` string field
23
- * that collides — skip it (returns undefined). Other blocks have HoverEffectConfig. */
24
- export function getBlockHoverEffect(block: ContentBlock): HoverEffectConfig | undefined {
25
- // ProjectGridBlock hover_effect is the old per-card string — not HoverEffectConfig
26
- if (block._type === "projectGridBlock") return undefined;
27
- const val = (block as unknown as Record<string, unknown>).hover_effect;
28
- if (val === undefined || val === null) return undefined;
29
- if (typeof val === "object") return val as HoverEffectConfig;
30
- return undefined;
31
- }
32
-
33
- interface AnimationTabProps {
34
- selectedBlock: { block: ContentBlock; rowKey: string; colKey: string; isSection: boolean } | null;
35
- }
36
-
37
- export function AnimationTab({ selectedBlock }: AnimationTabProps) {
38
- const store = useBuilderStore();
39
-
40
- // Block level: type-specific enter picker + card entrance for projectGrid
41
- if (selectedBlock) {
42
- const isProjectGrid = selectedBlock.block._type === "projectGridBlock";
43
- const pgBlock = isProjectGrid ? (selectedBlock.block as ProjectGridBlock) : null;
44
- const bvp = store.activeViewport;
45
- const isBlockResponsive = bvp !== "desktop";
46
-
47
- const effectiveEnterAnim = getBlockAnimationValue(
48
- selectedBlock.block, bvp, "enter_animation", undefined
49
- );
50
- const effectiveHoverEffect = getBlockAnimationValue(
51
- selectedBlock.block, bvp, "hover_effect", undefined
52
- ) as HoverEffectConfig | undefined;
53
-
54
- const hasEnterOverride = hasBlockAnimationOverride(selectedBlock.block, bvp, "enter_animation");
55
- const hasHoverOverride = hasBlockAnimationOverride(selectedBlock.block, bvp, "hover_effect");
56
-
57
- return (
58
- <>
59
- {isBlockResponsive && (
60
- <div className="px-4 pt-3">
61
- <div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#076bff]/8 border border-[#076bff]/15">
62
- <span className="text-[11px] font-medium text-[#076bff]">
63
- Editing {bvp === "tablet" ? "Tablet" : "Phone"} overrides
64
- </span>
65
- </div>
66
- </div>
67
- )}
68
- <div className="relative">
69
- {hasEnterOverride && (
70
- <div className="flex items-center justify-between px-4 pt-2">
71
- <span className="text-[9px] text-[#076bff] font-medium">overridden</span>
72
- <button
73
- onClick={() => {
74
- const updates = setBlockAnimationOverride(selectedBlock.block, bvp, "enter_animation", undefined);
75
- store.updateBlock(selectedBlock.block._key, updates);
76
- }}
77
- className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors"
78
- >
79
- Reset
80
- </button>
81
- </div>
82
- )}
83
- <EnterAnimationPicker
84
- mode={{ level: "block", blockType: selectedBlock.block._type }}
85
- config={effectiveEnterAnim}
86
- onChange={(cfg) => {
87
- const updates = setBlockAnimationOverride(selectedBlock.block, bvp, "enter_animation", cfg);
88
- store.updateBlock(selectedBlock.block._key, updates);
89
- }}
90
- />
91
- </div>
92
- {/* Hover Effect — block-level only, shown if block type has hover presets */}
93
- <div className="border-t border-neutral-200 my-1" />
94
- <div className="relative">
95
- {hasHoverOverride && (
96
- <div className="flex items-center justify-between px-4 pt-2">
97
- <span className="text-[9px] text-[#076bff] font-medium">overridden</span>
98
- <button
99
- onClick={() => {
100
- const updates = setBlockAnimationOverride(selectedBlock.block, bvp, "hover_effect", undefined);
101
- store.updateBlock(selectedBlock.block._key, updates);
102
- }}
103
- className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors"
104
- >
105
- Reset
106
- </button>
107
- </div>
108
- )}
109
- <HoverEffectPicker
110
- blockType={selectedBlock.block._type}
111
- config={effectiveHoverEffect ?? getBlockHoverEffect(selectedBlock.block)}
112
- onChange={(cfg) => {
113
- const updates = setBlockAnimationOverride(selectedBlock.block, bvp, "hover_effect", cfg);
114
- store.updateBlock(selectedBlock.block._key, updates);
115
- }}
116
- />
117
- </div>
118
- {isProjectGrid && pgBlock && (
119
- <>
120
- <div className="border-t border-neutral-200 my-1" />
121
- <CardEntranceSection block={pgBlock} />
122
- </>
123
- )}
124
- </>
125
- );
126
- }
127
-
128
- // Page-level: generic enter animation (no hover at page level)
129
- return (
130
- <EnterAnimationPicker
131
- mode={{ level: "page" }}
132
- config={store.pageSettings.enter_animation}
133
- onChange={(cfg) => {
134
- store.updatePageSettings({ enter_animation: cfg });
135
- }}
136
- />
137
- );
138
- }
1
+ "use client";
2
+
3
+ /**
4
+ * AnimationTab — Routes to EnterAnimationPicker / HoverEffectPicker based on selection.
5
+ *
6
+ * Extracted from SettingsPanel.tsx in Session C (refactor split).
7
+ */
8
+
9
+ import { useBuilderStore } from "../../../lib/builder/store";
10
+ import type { ContentBlock, ProjectGridBlock } from "../../../lib/sanity/types";
11
+ import type { HoverEffectConfig } from "../../../lib/animation/hover-effect-types";
12
+ import EnterAnimationPicker from "../editors/EnterAnimationPicker";
13
+ import HoverEffectPicker from "../editors/HoverEffectPicker";
14
+ import {
15
+ getBlockAnimationValue,
16
+ hasBlockAnimationOverride,
17
+ setBlockAnimationOverride,
18
+ } from "./responsive-helpers";
19
+ import { CardEntranceSection } from "./CardEntranceSection";
20
+
21
+ /** Safely extract hover_effect (new unified type) from any content block.
22
+ * ProjectGridBlock has a legacy `hover_effect: "3d" | "scale" | "none"` string field
23
+ * that collides — skip it (returns undefined). Other blocks have HoverEffectConfig. */
24
+ export function getBlockHoverEffect(block: ContentBlock): HoverEffectConfig | undefined {
25
+ // ProjectGridBlock hover_effect is the old per-card string — not HoverEffectConfig
26
+ if (block._type === "projectGridBlock") return undefined;
27
+ const val = (block as unknown as Record<string, unknown>).hover_effect;
28
+ if (val === undefined || val === null) return undefined;
29
+ if (typeof val === "object") return val as HoverEffectConfig;
30
+ return undefined;
31
+ }
32
+
33
+ interface AnimationTabProps {
34
+ selectedBlock: { block: ContentBlock; rowKey: string; colKey: string; isSection: boolean } | null;
35
+ }
36
+
37
+ export function AnimationTab({ selectedBlock }: AnimationTabProps) {
38
+ const store = useBuilderStore();
39
+
40
+ // Block level: type-specific enter picker + card entrance for projectGrid
41
+ if (selectedBlock) {
42
+ const isProjectGrid = selectedBlock.block._type === "projectGridBlock";
43
+ const pgBlock = isProjectGrid ? (selectedBlock.block as ProjectGridBlock) : null;
44
+ const bvp = store.activeViewport;
45
+ const isBlockResponsive = bvp !== "desktop";
46
+
47
+ const effectiveEnterAnim = getBlockAnimationValue(
48
+ selectedBlock.block, bvp, "enter_animation", undefined
49
+ );
50
+ const effectiveHoverEffect = getBlockAnimationValue(
51
+ selectedBlock.block, bvp, "hover_effect", undefined
52
+ ) as HoverEffectConfig | undefined;
53
+
54
+ const hasEnterOverride = hasBlockAnimationOverride(selectedBlock.block, bvp, "enter_animation");
55
+ const hasHoverOverride = hasBlockAnimationOverride(selectedBlock.block, bvp, "hover_effect");
56
+
57
+ return (
58
+ <>
59
+ {isBlockResponsive && (
60
+ <div className="px-4 pt-3">
61
+ <div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#3580f9]/8 border border-[#3580f9]/15">
62
+ <span className="text-[11px] font-medium text-[#3580f9]">
63
+ Editing {bvp === "tablet" ? "Tablet" : "Phone"} overrides
64
+ </span>
65
+ </div>
66
+ </div>
67
+ )}
68
+ <div className="relative">
69
+ {hasEnterOverride && (
70
+ <div className="flex items-center justify-between px-4 pt-2">
71
+ <span className="text-[9px] text-[#3580f9] font-medium">overridden</span>
72
+ <button
73
+ onClick={() => {
74
+ const updates = setBlockAnimationOverride(selectedBlock.block, bvp, "enter_animation", undefined);
75
+ store.updateBlock(selectedBlock.block._key, updates);
76
+ }}
77
+ className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors"
78
+ >
79
+ Reset
80
+ </button>
81
+ </div>
82
+ )}
83
+ <EnterAnimationPicker
84
+ mode={{ level: "block", blockType: selectedBlock.block._type }}
85
+ config={effectiveEnterAnim}
86
+ onChange={(cfg) => {
87
+ const updates = setBlockAnimationOverride(selectedBlock.block, bvp, "enter_animation", cfg);
88
+ store.updateBlock(selectedBlock.block._key, updates);
89
+ }}
90
+ />
91
+ </div>
92
+ {/* Hover Effect — block-level only, shown if block type has hover presets */}
93
+ <div className="border-t border-neutral-200 my-1" />
94
+ <div className="relative">
95
+ {hasHoverOverride && (
96
+ <div className="flex items-center justify-between px-4 pt-2">
97
+ <span className="text-[9px] text-[#3580f9] font-medium">overridden</span>
98
+ <button
99
+ onClick={() => {
100
+ const updates = setBlockAnimationOverride(selectedBlock.block, bvp, "hover_effect", undefined);
101
+ store.updateBlock(selectedBlock.block._key, updates);
102
+ }}
103
+ className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors"
104
+ >
105
+ Reset
106
+ </button>
107
+ </div>
108
+ )}
109
+ <HoverEffectPicker
110
+ blockType={selectedBlock.block._type}
111
+ config={effectiveHoverEffect ?? getBlockHoverEffect(selectedBlock.block)}
112
+ onChange={(cfg) => {
113
+ const updates = setBlockAnimationOverride(selectedBlock.block, bvp, "hover_effect", cfg);
114
+ store.updateBlock(selectedBlock.block._key, updates);
115
+ }}
116
+ />
117
+ </div>
118
+ {isProjectGrid && pgBlock && (
119
+ <>
120
+ <div className="border-t border-neutral-200 my-1" />
121
+ <CardEntranceSection block={pgBlock} />
122
+ </>
123
+ )}
124
+ </>
125
+ );
126
+ }
127
+
128
+ // Page-level: generic enter animation (no hover at page level)
129
+ return (
130
+ <EnterAnimationPicker
131
+ mode={{ level: "page" }}
132
+ config={store.pageSettings.enter_animation}
133
+ onChange={(cfg) => {
134
+ store.updatePageSettings({ enter_animation: cfg });
135
+ }}
136
+ />
137
+ );
138
+ }
@@ -24,6 +24,7 @@ import {
24
24
  import ColorSwatchPicker, { usePaletteSwatches } from "../ColorSwatchPicker";
25
25
  import { serializeColorField, parseColorField, isGradient } from "../../../lib/color-utils";
26
26
  import { TRBLInputs } from "./TRBLInputs";
27
+ import { BubbleTooltip } from "../BubbleIcons";
27
28
  import {
28
29
  getBlockLayoutValue,
29
30
  hasBlockLayoutOverride,
@@ -129,15 +130,16 @@ function AlignmentButtons<T extends string>({
129
130
  return (
130
131
  <button
131
132
  key={opt.value}
132
- title={opt.label}
133
+ aria-label={opt.label}
133
134
  onClick={() => onChange(opt.value)}
134
- className={`flex items-center justify-center w-[34px] h-[30px] rounded-md border transition-all ${
135
+ className={`group/bb relative flex items-center justify-center w-[34px] h-[30px] rounded-md border transition-all ${
135
136
  isActive
136
- ? "bg-[#076bff]/10 border-[#076bff]/30 text-[#076bff]"
137
+ ? "bg-[#3580f9]/10 border-[#3580f9]/30 text-[#3580f9]"
137
138
  : "bg-[#f5f5f5] border-transparent text-neutral-400 hover:bg-[#efefef] hover:text-neutral-600"
138
139
  }`}
139
140
  >
140
141
  {renderIcon(opt.value)}
142
+ <BubbleTooltip>{opt.label}</BubbleTooltip>
141
143
  </button>
142
144
  );
143
145
  })}
@@ -172,7 +174,7 @@ function OverrideBadge({
172
174
  if (hasAny) {
173
175
  return (
174
176
  <div className="flex items-center gap-2 mt-1">
175
- <span className="text-[9px] text-[#076bff]">overridden</span>
177
+ <span className="text-[9px] text-[#3580f9]">overridden</span>
176
178
  <button
177
179
  onClick={onReset}
178
180
  className="text-[10px] text-neutral-400 hover:text-[var(--admin-error)] transition-colors"
@@ -233,8 +235,8 @@ export function BlockLayoutTab({ block }: { block: ContentBlock }) {
233
235
  <>
234
236
  {viewportLabel && (
235
237
  <div className="px-4 pt-3">
236
- <div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#076bff]/8 border border-[#076bff]/15">
237
- <span className="text-[11px] font-medium text-[#076bff]">
238
+ <div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#3580f9]/8 border border-[#3580f9]/15">
239
+ <span className="text-[11px] font-medium text-[#3580f9]">
238
240
  Editing {viewportLabel} overrides
239
241
  </span>
240
242
  </div>
@@ -345,7 +347,7 @@ export function BlockLayoutTab({ block }: { block: ContentBlock }) {
345
347
  max={100}
346
348
  value={bgOpacity}
347
349
  onChange={(e) => updateLayout("background_opacity", parseInt(e.target.value))}
348
- className={`flex-1 accent-[#076bff] ${bgIsGradient ? "opacity-40 pointer-events-none" : ""}`}
350
+ className={`flex-1 accent-[#3580f9] ${bgIsGradient ? "opacity-40 pointer-events-none" : ""}`}
349
351
  disabled={bgIsGradient}
350
352
  />
351
353
  <span className="text-xs text-neutral-900 w-10 text-right">
@@ -444,7 +446,7 @@ export function BlockLayoutTab({ block }: { block: ContentBlock }) {
444
446
  max={20}
445
447
  value={parseInt(getBlockLayoutValue<string>(block, activeViewport, "border_width", "0"))}
446
448
  onChange={(e) => updateLayout("border_width", e.target.value)}
447
- className="flex-1 accent-[#076bff]"
449
+ className="flex-1 accent-[#3580f9]"
448
450
  />
449
451
  <span className="text-xs text-neutral-900 w-10 text-right">
450
452
  {getBlockLayoutValue<string>(block, activeViewport, "border_width", "0")}px
@@ -489,7 +491,7 @@ export function BlockLayoutTab({ block }: { block: ContentBlock }) {
489
491
  max={50}
490
492
  value={parseInt(getBlockLayoutValue<string>(block, activeViewport, "border_radius", "0"))}
491
493
  onChange={(e) => updateLayout("border_radius", e.target.value)}
492
- className="flex-1 accent-[#076bff]"
494
+ className="flex-1 accent-[#3580f9]"
493
495
  />
494
496
  <span className="text-xs text-neutral-900 w-10 text-right">
495
497
  {getBlockLayoutValue<string>(block, activeViewport, "border_radius", "0")}px
@@ -1,114 +1,114 @@
1
- "use client";
2
-
3
- /**
4
- * CardEntranceSection — Toggle + preset/stagger/duration controls for
5
- * ProjectGrid card entrance animations.
6
- *
7
- * Extracted from SettingsPanel.tsx in Session C (refactor split).
8
- */
9
-
10
- import { useBuilderStore } from "../../../lib/builder/store";
11
- import type { ProjectGridBlock, CardEntranceConfig } from "../../../lib/sanity/types";
12
-
13
- export const ENTRANCE_PRESETS = [
14
- { value: "fade", label: "Fade" },
15
- { value: "slide-up", label: "Slide Up" },
16
- { value: "scale", label: "Scale" },
17
- ] as const;
18
-
19
- export const CARD_ENTRANCE_SELECT_CLASS =
20
- "w-full rounded-lg border border-transparent bg-[#f5f5f5] px-2.5 py-[7px] text-xs text-neutral-900 font-normal outline-none transition-all hover:bg-[#efefef] focus:bg-white focus:border-[#076bff] focus:shadow-[0_0_0_3px_rgba(7,107,255,0.06)]";
21
-
22
- export const CARD_ENTRANCE_SLIDER_CLASS =
23
- "w-full h-1.5 rounded-full bg-[#e5e5e5] appearance-none cursor-pointer accent-[#076bff] [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3.5 [&::-webkit-slider-thumb]:h-3.5 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-[#076bff] [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-white [&::-webkit-slider-thumb]:shadow-sm";
24
-
25
- export function CardEntranceSection({ block }: { block: ProjectGridBlock }) {
26
- const updateBlock = useBuilderStore((s) => s.updateBlock);
27
- const entrance = block.card_entrance;
28
- const enabled = entrance?.enabled ?? false;
29
-
30
- const update = (updates: Partial<CardEntranceConfig>) => {
31
- updateBlock(block._key, {
32
- card_entrance: { ...entrance, ...updates },
33
- } as Partial<ProjectGridBlock>);
34
- };
35
-
36
- return (
37
- <div className="px-4 py-3">
38
- <div className="flex items-center justify-between mb-2.5">
39
- <span className="text-xs font-medium text-neutral-700">Card Entrance</span>
40
- <button
41
- type="button"
42
- onClick={() => update({ enabled: !enabled })}
43
- className={`relative w-8 h-[18px] rounded-full transition-colors ${
44
- enabled ? "bg-[#076bff]" : "bg-neutral-300"
45
- }`}
46
- >
47
- <span
48
- className={`absolute top-[2px] w-[14px] h-[14px] rounded-full bg-white shadow transition-transform ${
49
- enabled ? "translate-x-[16px]" : "translate-x-[2px]"
50
- }`}
51
- />
52
- </button>
53
- </div>
54
-
55
- {enabled && (
56
- <div className="space-y-3">
57
- {/* Preset — dropdown instead of segmented buttons */}
58
- <div>
59
- <label className="text-[11px] text-neutral-500 mb-1 block">Preset</label>
60
- <select
61
- value={entrance?.preset || "slide-up"}
62
- onChange={(e) => update({ preset: e.target.value as "fade" | "slide-up" | "scale" })}
63
- className={CARD_ENTRANCE_SELECT_CLASS}
64
- >
65
- {ENTRANCE_PRESETS.map((opt) => (
66
- <option key={opt.value} value={opt.value}>
67
- {opt.label}
68
- </option>
69
- ))}
70
- </select>
71
- </div>
72
-
73
- {/* Stagger delay */}
74
- <div>
75
- <div className="flex items-center justify-between mb-1">
76
- <label className="text-[11px] text-neutral-500">Stagger</label>
77
- <span className="text-[11px] text-neutral-500 tabular-nums">
78
- {entrance?.stagger_delay ?? 80}ms
79
- </span>
80
- </div>
81
- <input
82
- type="range"
83
- min={0}
84
- max={5000}
85
- step={10}
86
- value={entrance?.stagger_delay ?? 80}
87
- onChange={(e) => update({ stagger_delay: Number(e.target.value) })}
88
- className={CARD_ENTRANCE_SLIDER_CLASS}
89
- />
90
- </div>
91
-
92
- {/* Duration */}
93
- <div>
94
- <div className="flex items-center justify-between mb-1">
95
- <label className="text-[11px] text-neutral-500">Duration</label>
96
- <span className="text-[11px] text-neutral-500 tabular-nums">
97
- {entrance?.duration ?? 500}ms
98
- </span>
99
- </div>
100
- <input
101
- type="range"
102
- min={200}
103
- max={5000}
104
- step={50}
105
- value={entrance?.duration ?? 500}
106
- onChange={(e) => update({ duration: Number(e.target.value) })}
107
- className={CARD_ENTRANCE_SLIDER_CLASS}
108
- />
109
- </div>
110
- </div>
111
- )}
112
- </div>
113
- );
114
- }
1
+ "use client";
2
+
3
+ /**
4
+ * CardEntranceSection — Toggle + preset/stagger/duration controls for
5
+ * ProjectGrid card entrance animations.
6
+ *
7
+ * Extracted from SettingsPanel.tsx in Session C (refactor split).
8
+ */
9
+
10
+ import { useBuilderStore } from "../../../lib/builder/store";
11
+ import type { ProjectGridBlock, CardEntranceConfig } from "../../../lib/sanity/types";
12
+
13
+ export const ENTRANCE_PRESETS = [
14
+ { value: "fade", label: "Fade" },
15
+ { value: "slide-up", label: "Slide Up" },
16
+ { value: "scale", label: "Scale" },
17
+ ] as const;
18
+
19
+ export const CARD_ENTRANCE_SELECT_CLASS =
20
+ "w-full rounded-lg border border-transparent bg-[#f5f5f5] px-2.5 py-[7px] text-xs text-neutral-900 font-normal outline-none transition-all hover:bg-[#efefef] focus:bg-white focus:border-[#3580f9] focus:shadow-[0_0_0_3px_rgba(53, 128, 249,0.06)]";
21
+
22
+ export const CARD_ENTRANCE_SLIDER_CLASS =
23
+ "w-full h-1.5 rounded-full bg-[#e5e5e5] appearance-none cursor-pointer accent-[#3580f9] [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3.5 [&::-webkit-slider-thumb]:h-3.5 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-[#3580f9] [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-white [&::-webkit-slider-thumb]:shadow-sm";
24
+
25
+ export function CardEntranceSection({ block }: { block: ProjectGridBlock }) {
26
+ const updateBlock = useBuilderStore((s) => s.updateBlock);
27
+ const entrance = block.card_entrance;
28
+ const enabled = entrance?.enabled ?? false;
29
+
30
+ const update = (updates: Partial<CardEntranceConfig>) => {
31
+ updateBlock(block._key, {
32
+ card_entrance: { ...entrance, ...updates },
33
+ } as Partial<ProjectGridBlock>);
34
+ };
35
+
36
+ return (
37
+ <div className="px-4 py-3">
38
+ <div className="flex items-center justify-between mb-2.5">
39
+ <span className="text-xs font-medium text-neutral-700">Card Entrance</span>
40
+ <button
41
+ type="button"
42
+ onClick={() => update({ enabled: !enabled })}
43
+ className={`relative w-8 h-[18px] rounded-full transition-colors ${
44
+ enabled ? "bg-[#3580f9]" : "bg-neutral-300"
45
+ }`}
46
+ >
47
+ <span
48
+ className={`absolute top-[2px] w-[14px] h-[14px] rounded-full bg-white shadow transition-transform ${
49
+ enabled ? "translate-x-[16px]" : "translate-x-[2px]"
50
+ }`}
51
+ />
52
+ </button>
53
+ </div>
54
+
55
+ {enabled && (
56
+ <div className="space-y-3">
57
+ {/* Preset — dropdown instead of segmented buttons */}
58
+ <div>
59
+ <label className="text-[11px] text-neutral-500 mb-1 block">Preset</label>
60
+ <select
61
+ value={entrance?.preset || "slide-up"}
62
+ onChange={(e) => update({ preset: e.target.value as "fade" | "slide-up" | "scale" })}
63
+ className={CARD_ENTRANCE_SELECT_CLASS}
64
+ >
65
+ {ENTRANCE_PRESETS.map((opt) => (
66
+ <option key={opt.value} value={opt.value}>
67
+ {opt.label}
68
+ </option>
69
+ ))}
70
+ </select>
71
+ </div>
72
+
73
+ {/* Stagger delay */}
74
+ <div>
75
+ <div className="flex items-center justify-between mb-1">
76
+ <label className="text-[11px] text-neutral-500">Stagger</label>
77
+ <span className="text-[11px] text-neutral-500 tabular-nums">
78
+ {entrance?.stagger_delay ?? 80}ms
79
+ </span>
80
+ </div>
81
+ <input
82
+ type="range"
83
+ min={0}
84
+ max={5000}
85
+ step={10}
86
+ value={entrance?.stagger_delay ?? 80}
87
+ onChange={(e) => update({ stagger_delay: Number(e.target.value) })}
88
+ className={CARD_ENTRANCE_SLIDER_CLASS}
89
+ />
90
+ </div>
91
+
92
+ {/* Duration */}
93
+ <div>
94
+ <div className="flex items-center justify-between mb-1">
95
+ <label className="text-[11px] text-neutral-500">Duration</label>
96
+ <span className="text-[11px] text-neutral-500 tabular-nums">
97
+ {entrance?.duration ?? 500}ms
98
+ </span>
99
+ </div>
100
+ <input
101
+ type="range"
102
+ min={200}
103
+ max={5000}
104
+ step={50}
105
+ value={entrance?.duration ?? 500}
106
+ onChange={(e) => update({ duration: Number(e.target.value) })}
107
+ className={CARD_ENTRANCE_SLIDER_CLASS}
108
+ />
109
+ </div>
110
+ </div>
111
+ )}
112
+ </div>
113
+ );
114
+ }