@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
@@ -28,6 +28,7 @@ import {
28
28
  buildStackOverride,
29
29
  buildColumnV2Overrides,
30
30
  } from "./responsive-helpers";
31
+ import { BubbleTooltip } from "../BubbleIcons";
31
32
 
32
33
  // ============================================
33
34
  // Preset definitions for the picker grid
@@ -99,14 +100,14 @@ function PresetGrid({ section }: { section: PageSectionV2 }) {
99
100
  <button
100
101
  key={preset.id}
101
102
  onClick={() => !isCustom && applyPresetV2(section._key, preset.id)}
102
- className={`flex flex-col items-center gap-1 p-2 rounded-lg border transition-all ${
103
+ className={`group/bb relative flex flex-col items-center gap-1 p-2 rounded-lg border transition-all ${
103
104
  isActive
104
- ? "border-[#4794e2] bg-[#4794e2]/5"
105
+ ? "border-[#3580f9] bg-[#3580f9]/5"
105
106
  : isCustom
106
107
  ? "border-neutral-200 bg-neutral-50 opacity-60 cursor-default"
107
108
  : "border-neutral-200 bg-white hover:border-neutral-300 hover:bg-neutral-50"
108
109
  }`}
109
- title={isCustom ? "Layout doesn't match any preset" : preset.label}
110
+ aria-label={isCustom ? "Layout doesn't match any preset" : preset.label}
110
111
  >
111
112
  {/* Visual representation */}
112
113
  <div className="flex gap-0.5 w-full h-4">
@@ -121,7 +122,7 @@ function PresetGrid({ section }: { section: PageSectionV2 }) {
121
122
  <div
122
123
  key={i}
123
124
  className={`rounded-sm transition-colors ${
124
- isActive ? "bg-[#4794e2]" : "bg-neutral-300"
125
+ isActive ? "bg-[#3580f9]" : "bg-neutral-300"
125
126
  }`}
126
127
  style={{ flex: span }}
127
128
  />
@@ -129,10 +130,11 @@ function PresetGrid({ section }: { section: PageSectionV2 }) {
129
130
  )}
130
131
  </div>
131
132
  <span className={`text-[9px] font-medium ${
132
- isActive ? "text-[#4794e2]" : "text-neutral-500"
133
+ isActive ? "text-[#3580f9]" : "text-neutral-500"
133
134
  }`}>
134
135
  {preset.label}
135
136
  </span>
137
+ <BubbleTooltip>{isCustom ? "Layout doesn't match any preset" : preset.label}</BubbleTooltip>
136
138
  </button>
137
139
  );
138
140
  })}
@@ -140,17 +142,18 @@ function PresetGrid({ section }: { section: PageSectionV2 }) {
140
142
  {/* + Add Column button */}
141
143
  <button
142
144
  onClick={handleAddColumn}
143
- className="flex flex-col items-center gap-1 p-2 rounded-lg border border-dashed border-neutral-300 transition-all hover:border-[#4794e2] hover:bg-[#4794e2]/5 group"
144
- title="Add a column (fills first gap, or adds new row below)"
145
+ className="group/bb relative flex flex-col items-center gap-1 p-2 rounded-lg border border-dashed border-neutral-300 transition-all hover:border-[#3580f9] hover:bg-[#3580f9]/5 group"
146
+ aria-label="Add a column (fills first gap, or adds new row below)"
145
147
  >
146
148
  <div className="flex items-center justify-center w-full h-4">
147
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" className="text-neutral-400 group-hover:text-[#4794e2] transition-colors">
149
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" className="text-neutral-400 group-hover:text-[#3580f9] transition-colors">
148
150
  <path d="M12 5v14M5 12h14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
149
151
  </svg>
150
152
  </div>
151
- <span className="text-[9px] font-medium text-neutral-400 group-hover:text-[#4794e2] transition-colors">
153
+ <span className="text-[9px] font-medium text-neutral-400 group-hover:text-[#3580f9] transition-colors">
152
154
  Add Col
153
155
  </span>
156
+ <BubbleTooltip>Add a column (fills first gap, or adds new row below)</BubbleTooltip>
154
157
  </button>
155
158
  </div>
156
159
  );
@@ -204,8 +207,8 @@ export default function SectionV2Settings({ section }: { section: PageSectionV2
204
207
  {/* Responsive info banner */}
205
208
  {isResponsive && (
206
209
  <div className="px-4 pt-3">
207
- <div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#4794e2]/8 border border-[#4794e2]/15">
208
- <span className="text-[11px] font-medium text-[#4794e2]">
210
+ <div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#3580f9]/8 border border-[#3580f9]/15">
211
+ <span className="text-[11px] font-medium text-[#3580f9]">
209
212
  Editing {activeViewport === "tablet" ? "Tablet" : "Phone"} overrides
210
213
  </span>
211
214
  </div>
@@ -218,26 +221,28 @@ export default function SectionV2Settings({ section }: { section: PageSectionV2
218
221
  <div className="flex gap-2">
219
222
  <button
220
223
  onClick={handleStack}
221
- className="flex-1 rounded-lg bg-[#4794e2]/8 border border-[#4794e2]/20 py-2 text-xs font-medium text-[#4794e2] hover:bg-[#4794e2]/15 transition-colors"
222
- title="Stack all columns vertically (full width, one per row)"
224
+ className="group/bb relative flex-1 rounded-lg bg-[#3580f9]/8 border border-[#3580f9]/20 py-2 text-xs font-medium text-[#3580f9] hover:bg-[#3580f9]/15 transition-colors"
225
+ aria-label="Stack all columns vertically (full width, one per row)"
223
226
  >
224
227
  Stack Columns
228
+ <BubbleTooltip>Stack all columns vertically (full width, one per row)</BubbleTooltip>
225
229
  </button>
226
230
  <button
227
231
  onClick={handleReset}
228
232
  disabled={!hasAnyOverrides}
229
- className={`flex-1 rounded-lg border py-2 text-xs font-medium transition-colors ${
233
+ className={`group/bb relative flex-1 rounded-lg border py-2 text-xs font-medium transition-colors ${
230
234
  hasAnyOverrides
231
235
  ? "bg-neutral-100 border-neutral-200 text-neutral-600 hover:bg-neutral-200"
232
236
  : "bg-neutral-50 border-neutral-100 text-neutral-300 cursor-not-allowed"
233
237
  }`}
234
- title="Reset all responsive overrides for this viewport"
238
+ aria-label="Reset all responsive overrides for this viewport"
235
239
  >
236
240
  Reset Overrides
241
+ <BubbleTooltip>Reset all responsive overrides for this viewport</BubbleTooltip>
237
242
  </button>
238
243
  </div>
239
244
  {hasAnyOverrides && (
240
- <p className="text-[10px] text-[#4794e2]/60 mt-1.5">
245
+ <p className="text-[10px] text-[#3580f9]/60 mt-1.5">
241
246
  {hasColOverrides ? "Column layout" : ""}
242
247
  {hasColOverrides && hasSettingsOverrides ? " + " : ""}
243
248
  {hasSettingsOverrides ? "settings" : ""}
@@ -266,7 +271,7 @@ export default function SectionV2Settings({ section }: { section: PageSectionV2
266
271
  <span>
267
272
  Col Gap
268
273
  {isResponsive && hasSectionV2SettingOverride(section, activeViewport, "col_gap") && (
269
- <span className="ml-1 text-[9px] text-[#4794e2]">overridden</span>
274
+ <span className="ml-1 text-[9px] text-[#3580f9]">overridden</span>
270
275
  )}
271
276
  </span>
272
277
  }>
@@ -278,7 +283,7 @@ export default function SectionV2Settings({ section }: { section: PageSectionV2
278
283
  step={4}
279
284
  value={getGapValue("col_gap", 20)}
280
285
  onChange={(e) => updateSettingResponsive("col_gap", parseInt(e.target.value))}
281
- className="flex-1 accent-[#4794e2]"
286
+ className="flex-1 accent-[#3580f9]"
282
287
  />
283
288
  <span className="text-xs text-neutral-900 w-12 text-right">
284
289
  {getGapValue("col_gap", 20)}px
@@ -298,7 +303,7 @@ export default function SectionV2Settings({ section }: { section: PageSectionV2
298
303
  <span>
299
304
  Row Gap
300
305
  {isResponsive && hasSectionV2SettingOverride(section, activeViewport, "row_gap") && (
301
- <span className="ml-1 text-[9px] text-[#4794e2]">overridden</span>
306
+ <span className="ml-1 text-[9px] text-[#3580f9]">overridden</span>
302
307
  )}
303
308
  </span>
304
309
  }>
@@ -310,7 +315,7 @@ export default function SectionV2Settings({ section }: { section: PageSectionV2
310
315
  step={4}
311
316
  value={getGapValue("row_gap", 20)}
312
317
  onChange={(e) => updateSettingResponsive("row_gap", parseInt(e.target.value))}
313
- className="flex-1 accent-[#4794e2]"
318
+ className="flex-1 accent-[#3580f9]"
314
319
  />
315
320
  <span className="text-xs text-neutral-900 w-12 text-right">
316
321
  {getGapValue("row_gap", 20)}px
@@ -42,7 +42,7 @@ export function TRBLInputs({
42
42
  value={f.value || "0"}
43
43
  onFocus={() => store._pushSnapshot()}
44
44
  onChange={(e) => onChange(f.key, e.target.value)}
45
- className="w-full rounded-lg border border-transparent bg-[#f5f5f5] px-1.5 py-[6px] text-xs text-neutral-900 text-center 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)]"
45
+ className="w-full rounded-lg border border-transparent bg-[#f5f5f5] px-1.5 py-[6px] text-xs text-neutral-900 text-center 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)]"
46
46
  />
47
47
  </div>
48
48
  ))}
@@ -21,5 +21,6 @@ export { useSettingsPanelSelection } from "./useSettingsPanelSelection";
21
21
  export type { SelectedBlockInfo, SelectedParallaxSlideInfo } from "./useSettingsPanelSelection";
22
22
  export { AnimationTab, getBlockHoverEffect } from "./AnimationTab";
23
23
  export { ColumnV2AnimationTab } from "./ColumnV2AnimationTab";
24
+ export { ColumnV2LayoutTab } from "./ColumnV2LayoutTab";
24
25
  export { CardEntranceSection, ENTRANCE_PRESETS, CARD_ENTRANCE_SELECT_CLASS, CARD_ENTRANCE_SLIDER_CLASS } from "./CardEntranceSection";
25
26
  export { CustomSectionSettings } from "./CustomSectionSettings";
@@ -78,6 +78,7 @@ export const BLOCK_ENTER_PRESETS: Record<BlockType, readonly EnterPreset[]> = {
78
78
  spacerBlock: [], // invisible — no animation
79
79
  projectGridBlock: [], // uses card_entrance system
80
80
  projectCarouselBlock: [], // uses card_entrance system
81
+ marqueeBlock: ["fade", "slide-up", "scale"],
81
82
  };
82
83
 
83
84
  // ── Enter animation config ─────────────────────────────────────────
@@ -1,210 +1,210 @@
1
- /**
2
- * Hover Effect Presets
3
- *
4
- * Unified registry of CSS-based and shader-based hover effects.
5
- * The builder UI uses this for dropdowns. The public site uses
6
- * getHoverEffectStyles() for CSS hovers and delegates to
7
- * ShaderCanvas for shader hovers.
8
- *
9
- * Session 116 — Animation UX Refactor.
10
- */
11
-
12
- import type {
13
- HoverPreset,
14
- CSSHoverPreset,
15
- ShaderHoverPreset,
16
- HoverEasing,
17
- HoverEffectConfig,
18
- ShaderSmoothness,
19
- } from "./hover-effect-types";
20
- import { HOVER_EFFECT_DEFAULTS } from "./hover-effect-types";
21
-
22
- // ── Default config ─────────────────────────────────────────────────
23
-
24
- export const DEFAULT_HOVER_EFFECT: Required<HoverEffectConfig> = {
25
- preset: HOVER_EFFECT_DEFAULTS.preset,
26
- duration: HOVER_EFFECT_DEFAULTS.duration,
27
- easing: HOVER_EFFECT_DEFAULTS.easing,
28
- shader_speed: HOVER_EFFECT_DEFAULTS.shader_speed,
29
- shader_smoothness: HOVER_EFFECT_DEFAULTS.shader_smoothness,
30
- };
31
-
32
- // ── Preset metadata (for builder UI) ───────────────────────────────
33
-
34
- export interface HoverPresetInfo {
35
- id: HoverPreset;
36
- label: string;
37
- description: string;
38
- /** Whether this preset uses WebGL shaders */
39
- isShader: boolean;
40
- }
41
-
42
- export const HOVER_PRESET_INFO: HoverPresetInfo[] = [
43
- // CSS-based presets
44
- { id: "none", label: "None", description: "No hover effect", isShader: false },
45
- { id: "scale-up", label: "Scale Up", description: "Zoom in on hover", isShader: false },
46
- { id: "scale-down", label: "Scale Down", description: "Zoom out on hover", isShader: false },
47
- { id: "lift", label: "Lift", description: "Lifts element with shadow", isShader: false },
48
- { id: "tilt-3d", label: "Tilt 3D", description: "Perspective tilt following cursor", isShader: false },
49
- { id: "color-shift", label: "Color Shift", description: "Brightness and saturation change", isShader: false },
50
- { id: "blur-reveal", label: "Blur Reveal", description: "Blurred until hovered", isShader: false },
51
- { id: "border-glow", label: "Border Glow", description: "Glowing border on hover", isShader: false },
52
- // Shader-based presets
53
- { id: "ripple", label: "Ripple", description: "Ripple distortion from cursor", isShader: true },
54
- { id: "rgb-shift", label: "RGB Shift", description: "Chromatic aberration on hover", isShader: true },
55
- { id: "pixelate", label: "Pixelate", description: "Pixelation dissolve effect", isShader: true },
56
- ];
57
-
58
- /** Quick lookup: preset ID → metadata */
59
- export const HOVER_PRESET_MAP: Record<HoverPreset, HoverPresetInfo> = Object.fromEntries(
60
- HOVER_PRESET_INFO.map((p) => [p.id, p])
61
- ) as Record<HoverPreset, HoverPresetInfo>;
62
-
63
- // ── Shader detection ───────────────────────────────────────────────
64
-
65
- const SHADER_PRESETS: ReadonlySet<string> = new Set<ShaderHoverPreset>([
66
- "ripple",
67
- "rgb-shift",
68
- "pixelate",
69
- ]);
70
-
71
- /**
72
- * Check if a hover preset uses WebGL shaders.
73
- * Used by the builder to show/hide shader-specific controls
74
- * and by the renderer to delegate to ShaderCanvas.
75
- */
76
- export function isShaderPreset(preset: HoverPreset): preset is ShaderHoverPreset {
77
- return SHADER_PRESETS.has(preset);
78
- }
79
-
80
- // ── Easing options (for builder UI) ────────────────────────────────
81
-
82
- export const HOVER_EFFECT_EASINGS: { id: HoverEasing; label: string }[] = [
83
- { id: "ease-out", label: "Ease Out" },
84
- { id: "ease", label: "Ease" },
85
- { id: "ease-in", label: "Ease In" },
86
- { id: "ease-in-out", label: "Ease In Out" },
87
- { id: "linear", label: "Linear" },
88
- ];
89
-
90
- // ── Duration range (for builder UI) ────────────────────────────────
91
-
92
- export const HOVER_DURATION_RANGE = { min: 50, max: 5000, step: 25, default: 300 } as const;
93
-
94
- // ── Shader speed / smoothness ranges (for builder UI) ──────────────
95
-
96
- export const SHADER_SPEED_RANGE = { min: 0.5, max: 3.0, step: 0.1, default: 1.0 } as const;
97
-
98
- export const SHADER_SMOOTHNESS_OPTIONS: { id: ShaderSmoothness; label: string }[] = [
99
- { id: "snappy", label: "Snappy" },
100
- { id: "normal", label: "Normal" },
101
- { id: "smooth", label: "Smooth" },
102
- ];
103
-
104
- /** Smoothness label → lerp factor (used by ShaderCanvas renderer) */
105
- export const SMOOTHNESS_VALUES: Record<ShaderSmoothness, number> = {
106
- snappy: 0.15,
107
- normal: 0.05,
108
- smooth: 0.02,
109
- };
110
-
111
- // ── CSS hover styles ───────────────────────────────────────────────
112
-
113
- export interface HoverStyles {
114
- /** Base state CSS (always applied) */
115
- base: Record<string, string>;
116
- /** Hover state CSS (applied on :hover) */
117
- hover: Record<string, string>;
118
- /** Whether this preset needs JS mouse tracking (tilt-3d) */
119
- needsJS?: boolean;
120
- }
121
-
122
- /**
123
- * Returns CSS properties for a CSS-based hover preset.
124
- * Returns null for "none" and shader presets (they use WebGL, not CSS).
125
- *
126
- * Intensity is removed in the new system — presets use fixed,
127
- * sensible values. The old intensity multiplier created confusion
128
- * without meaningful user value.
129
- */
130
- export function getHoverEffectStyles(
131
- preset: HoverPreset,
132
- duration: number = 300,
133
- easing: string = "ease-out",
134
- ): HoverStyles | null {
135
- // Shader presets don't use CSS
136
- if (isShaderPreset(preset)) return null;
137
-
138
- const cssPreset = preset as CSSHoverPreset;
139
- const transition = `all ${duration}ms ${easing}`;
140
-
141
- switch (cssPreset) {
142
- case "none":
143
- return null;
144
-
145
- case "scale-up":
146
- return {
147
- base: { transition, transform: "scale(1)" },
148
- hover: { transform: "scale(1.05)" },
149
- };
150
-
151
- case "scale-down":
152
- return {
153
- base: { transition, transform: "scale(1)" },
154
- hover: { transform: "scale(0.95)" },
155
- };
156
-
157
- case "lift":
158
- return {
159
- base: {
160
- transition,
161
- transform: "translateY(0)",
162
- boxShadow: "0 0 0 rgba(0,0,0,0)",
163
- },
164
- hover: {
165
- transform: "translateY(-4px)",
166
- boxShadow: "0 8px 20px rgba(0,0,0,0.15)",
167
- },
168
- };
169
-
170
- case "tilt-3d":
171
- return {
172
- base: {
173
- transition,
174
- transform: "perspective(800px) rotateX(0deg) rotateY(0deg)",
175
- transformStyle: "preserve-3d",
176
- },
177
- hover: {
178
- // Overridden by JS mousemove handler
179
- transform: "perspective(800px) rotateX(0deg) rotateY(0deg)",
180
- },
181
- needsJS: true,
182
- };
183
-
184
- case "color-shift":
185
- return {
186
- base: { transition, filter: "brightness(1) saturate(1)" },
187
- hover: { filter: "brightness(1.10) saturate(1.20)" },
188
- };
189
-
190
- case "blur-reveal":
191
- return {
192
- base: { transition, filter: "blur(3px)", opacity: "0.85" },
193
- hover: { filter: "blur(0px)", opacity: "1" },
194
- };
195
-
196
- case "border-glow":
197
- return {
198
- base: {
199
- transition,
200
- boxShadow: "0 0 0 0 rgba(7,107,255,0)",
201
- },
202
- hover: {
203
- boxShadow: "0 0 8px 4px rgba(7,107,255,0.40)",
204
- },
205
- };
206
-
207
- default:
208
- return null;
209
- }
210
- }
1
+ /**
2
+ * Hover Effect Presets
3
+ *
4
+ * Unified registry of CSS-based and shader-based hover effects.
5
+ * The builder UI uses this for dropdowns. The public site uses
6
+ * getHoverEffectStyles() for CSS hovers and delegates to
7
+ * ShaderCanvas for shader hovers.
8
+ *
9
+ * Session 116 — Animation UX Refactor.
10
+ */
11
+
12
+ import type {
13
+ HoverPreset,
14
+ CSSHoverPreset,
15
+ ShaderHoverPreset,
16
+ HoverEasing,
17
+ HoverEffectConfig,
18
+ ShaderSmoothness,
19
+ } from "./hover-effect-types";
20
+ import { HOVER_EFFECT_DEFAULTS } from "./hover-effect-types";
21
+
22
+ // ── Default config ─────────────────────────────────────────────────
23
+
24
+ export const DEFAULT_HOVER_EFFECT: Required<HoverEffectConfig> = {
25
+ preset: HOVER_EFFECT_DEFAULTS.preset,
26
+ duration: HOVER_EFFECT_DEFAULTS.duration,
27
+ easing: HOVER_EFFECT_DEFAULTS.easing,
28
+ shader_speed: HOVER_EFFECT_DEFAULTS.shader_speed,
29
+ shader_smoothness: HOVER_EFFECT_DEFAULTS.shader_smoothness,
30
+ };
31
+
32
+ // ── Preset metadata (for builder UI) ───────────────────────────────
33
+
34
+ export interface HoverPresetInfo {
35
+ id: HoverPreset;
36
+ label: string;
37
+ description: string;
38
+ /** Whether this preset uses WebGL shaders */
39
+ isShader: boolean;
40
+ }
41
+
42
+ export const HOVER_PRESET_INFO: HoverPresetInfo[] = [
43
+ // CSS-based presets
44
+ { id: "none", label: "None", description: "No hover effect", isShader: false },
45
+ { id: "scale-up", label: "Scale Up", description: "Zoom in on hover", isShader: false },
46
+ { id: "scale-down", label: "Scale Down", description: "Zoom out on hover", isShader: false },
47
+ { id: "lift", label: "Lift", description: "Lifts element with shadow", isShader: false },
48
+ { id: "tilt-3d", label: "Tilt 3D", description: "Perspective tilt following cursor", isShader: false },
49
+ { id: "color-shift", label: "Color Shift", description: "Brightness and saturation change", isShader: false },
50
+ { id: "blur-reveal", label: "Blur Reveal", description: "Blurred until hovered", isShader: false },
51
+ { id: "border-glow", label: "Border Glow", description: "Glowing border on hover", isShader: false },
52
+ // Shader-based presets
53
+ { id: "ripple", label: "Ripple", description: "Ripple distortion from cursor", isShader: true },
54
+ { id: "rgb-shift", label: "RGB Shift", description: "Chromatic aberration on hover", isShader: true },
55
+ { id: "pixelate", label: "Pixelate", description: "Pixelation dissolve effect", isShader: true },
56
+ ];
57
+
58
+ /** Quick lookup: preset ID → metadata */
59
+ export const HOVER_PRESET_MAP: Record<HoverPreset, HoverPresetInfo> = Object.fromEntries(
60
+ HOVER_PRESET_INFO.map((p) => [p.id, p])
61
+ ) as Record<HoverPreset, HoverPresetInfo>;
62
+
63
+ // ── Shader detection ───────────────────────────────────────────────
64
+
65
+ const SHADER_PRESETS: ReadonlySet<string> = new Set<ShaderHoverPreset>([
66
+ "ripple",
67
+ "rgb-shift",
68
+ "pixelate",
69
+ ]);
70
+
71
+ /**
72
+ * Check if a hover preset uses WebGL shaders.
73
+ * Used by the builder to show/hide shader-specific controls
74
+ * and by the renderer to delegate to ShaderCanvas.
75
+ */
76
+ export function isShaderPreset(preset: HoverPreset): preset is ShaderHoverPreset {
77
+ return SHADER_PRESETS.has(preset);
78
+ }
79
+
80
+ // ── Easing options (for builder UI) ────────────────────────────────
81
+
82
+ export const HOVER_EFFECT_EASINGS: { id: HoverEasing; label: string }[] = [
83
+ { id: "ease-out", label: "Ease Out" },
84
+ { id: "ease", label: "Ease" },
85
+ { id: "ease-in", label: "Ease In" },
86
+ { id: "ease-in-out", label: "Ease In Out" },
87
+ { id: "linear", label: "Linear" },
88
+ ];
89
+
90
+ // ── Duration range (for builder UI) ────────────────────────────────
91
+
92
+ export const HOVER_DURATION_RANGE = { min: 50, max: 5000, step: 25, default: 300 } as const;
93
+
94
+ // ── Shader speed / smoothness ranges (for builder UI) ──────────────
95
+
96
+ export const SHADER_SPEED_RANGE = { min: 0.5, max: 3.0, step: 0.1, default: 1.0 } as const;
97
+
98
+ export const SHADER_SMOOTHNESS_OPTIONS: { id: ShaderSmoothness; label: string }[] = [
99
+ { id: "snappy", label: "Snappy" },
100
+ { id: "normal", label: "Normal" },
101
+ { id: "smooth", label: "Smooth" },
102
+ ];
103
+
104
+ /** Smoothness label → lerp factor (used by ShaderCanvas renderer) */
105
+ export const SMOOTHNESS_VALUES: Record<ShaderSmoothness, number> = {
106
+ snappy: 0.15,
107
+ normal: 0.05,
108
+ smooth: 0.02,
109
+ };
110
+
111
+ // ── CSS hover styles ───────────────────────────────────────────────
112
+
113
+ export interface HoverStyles {
114
+ /** Base state CSS (always applied) */
115
+ base: Record<string, string>;
116
+ /** Hover state CSS (applied on :hover) */
117
+ hover: Record<string, string>;
118
+ /** Whether this preset needs JS mouse tracking (tilt-3d) */
119
+ needsJS?: boolean;
120
+ }
121
+
122
+ /**
123
+ * Returns CSS properties for a CSS-based hover preset.
124
+ * Returns null for "none" and shader presets (they use WebGL, not CSS).
125
+ *
126
+ * Intensity is removed in the new system — presets use fixed,
127
+ * sensible values. The old intensity multiplier created confusion
128
+ * without meaningful user value.
129
+ */
130
+ export function getHoverEffectStyles(
131
+ preset: HoverPreset,
132
+ duration: number = 300,
133
+ easing: string = "ease-out",
134
+ ): HoverStyles | null {
135
+ // Shader presets don't use CSS
136
+ if (isShaderPreset(preset)) return null;
137
+
138
+ const cssPreset = preset as CSSHoverPreset;
139
+ const transition = `all ${duration}ms ${easing}`;
140
+
141
+ switch (cssPreset) {
142
+ case "none":
143
+ return null;
144
+
145
+ case "scale-up":
146
+ return {
147
+ base: { transition, transform: "scale(1)" },
148
+ hover: { transform: "scale(1.05)" },
149
+ };
150
+
151
+ case "scale-down":
152
+ return {
153
+ base: { transition, transform: "scale(1)" },
154
+ hover: { transform: "scale(0.95)" },
155
+ };
156
+
157
+ case "lift":
158
+ return {
159
+ base: {
160
+ transition,
161
+ transform: "translateY(0)",
162
+ boxShadow: "0 0 0 rgba(0,0,0,0)",
163
+ },
164
+ hover: {
165
+ transform: "translateY(-4px)",
166
+ boxShadow: "0 8px 20px rgba(0,0,0,0.15)",
167
+ },
168
+ };
169
+
170
+ case "tilt-3d":
171
+ return {
172
+ base: {
173
+ transition,
174
+ transform: "perspective(800px) rotateX(0deg) rotateY(0deg)",
175
+ transformStyle: "preserve-3d",
176
+ },
177
+ hover: {
178
+ // Overridden by JS mousemove handler
179
+ transform: "perspective(800px) rotateX(0deg) rotateY(0deg)",
180
+ },
181
+ needsJS: true,
182
+ };
183
+
184
+ case "color-shift":
185
+ return {
186
+ base: { transition, filter: "brightness(1) saturate(1)" },
187
+ hover: { filter: "brightness(1.10) saturate(1.20)" },
188
+ };
189
+
190
+ case "blur-reveal":
191
+ return {
192
+ base: { transition, filter: "blur(3px)", opacity: "0.85" },
193
+ hover: { filter: "blur(0px)", opacity: "1" },
194
+ };
195
+
196
+ case "border-glow":
197
+ return {
198
+ base: {
199
+ transition,
200
+ boxShadow: "0 0 0 0 rgba(53, 128, 249,0)",
201
+ },
202
+ hover: {
203
+ boxShadow: "0 0 8px 4px rgba(53, 128, 249,0.40)",
204
+ },
205
+ };
206
+
207
+ default:
208
+ return null;
209
+ }
210
+ }
@@ -71,6 +71,7 @@ export const BLOCK_HOVER_PRESETS: Record<BlockType, readonly HoverPreset[]> = {
71
71
  spacerBlock: [], // invisible
72
72
  projectGridBlock: ["scale-up", "lift"], // per-card effect
73
73
  projectCarouselBlock: [], // uses per-card hover_effect field directly
74
+ marqueeBlock: [], // already animating — hover effects fight the motion
74
75
  };
75
76
 
76
77
  // ── Hover effect config ────────────────────────────────────────────