@morphika/andami 0.5.0 → 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 (122) hide show
  1. package/README.md +151 -36
  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 +320 -327
  6. package/app/admin/navigation/page.tsx +255 -255
  7. package/app/admin/pages/[slug]/page.tsx +6 -6
  8. package/app/admin/pages/page.tsx +11 -11
  9. package/app/admin/projects/page.tsx +14 -14
  10. package/app/admin/setup/page.tsx +1 -1
  11. package/app/admin/styles/page.tsx +1 -1
  12. package/components/admin/MetadataEditor.tsx +6 -6
  13. package/components/admin/nav-builder/NavBuilder.tsx +1 -1
  14. package/components/admin/nav-builder/NavBuilderGrid.tsx +3 -3
  15. package/components/admin/nav-builder/NavGridCell.tsx +48 -48
  16. package/components/admin/nav-builder/NavGridItem.tsx +4 -4
  17. package/components/admin/nav-builder/NavItemSettings.tsx +331 -331
  18. package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -102
  19. package/components/admin/nav-builder/NavLivePreview.tsx +1 -1
  20. package/components/admin/nav-builder/NavMobileLivePreview.tsx +226 -226
  21. package/components/admin/nav-builder/NavMobileSettings.tsx +242 -242
  22. package/components/admin/nav-builder/NavSettingsFields.tsx +514 -514
  23. package/components/admin/setup-wizard/BrandingStep.tsx +3 -3
  24. package/components/admin/setup-wizard/DatabaseStep.tsx +2 -2
  25. package/components/admin/setup-wizard/DoneStep.tsx +1 -1
  26. package/components/admin/setup-wizard/SetupWizard.tsx +4 -4
  27. package/components/admin/setup-wizard/StorageStep.tsx +2 -2
  28. package/components/admin/setup-wizard/WelcomeStep.tsx +2 -2
  29. package/components/admin/styles/ColorsEditor.tsx +2 -2
  30. package/components/admin/styles/FontsEditor.tsx +6 -6
  31. package/components/admin/styles/GridLayoutEditor.tsx +9 -9
  32. package/components/admin/styles/LinksButtonsEditor.tsx +5 -5
  33. package/components/admin/styles/TypographyEditor.tsx +6 -6
  34. package/components/admin/styles/shared.tsx +68 -68
  35. package/components/blocks/AudioBlockRenderer.tsx +286 -0
  36. package/components/blocks/BeforeAfterBlockRenderer.tsx +274 -0
  37. package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
  38. package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
  39. package/components/builder/BlockCardIcons.tsx +316 -227
  40. package/components/builder/BlockTypePicker.tsx +3 -1
  41. package/components/builder/BubbleIcons.tsx +90 -0
  42. package/components/builder/BuilderCanvas.tsx +2 -0
  43. package/components/builder/CanvasMinimap.tsx +2 -2
  44. package/components/builder/CoverSectionCanvas.tsx +363 -275
  45. package/components/builder/DeviceFrame.tsx +1 -1
  46. package/components/builder/DndWrapper.tsx +3 -3
  47. package/components/builder/InsertionLines.tsx +1 -1
  48. package/components/builder/SectionCardIcons.tsx +421 -320
  49. package/components/builder/SectionEditorBar.tsx +1 -1
  50. package/components/builder/SectionTypePicker.tsx +4 -4
  51. package/components/builder/SectionV2Canvas.tsx +20 -4
  52. package/components/builder/SectionV2Column.tsx +74 -68
  53. package/components/builder/SortableBlock.tsx +93 -73
  54. package/components/builder/SortableRow.tsx +27 -26
  55. package/components/builder/VirtualAssetGrid.tsx +2 -2
  56. package/components/builder/asset-browser/R2BrowserContent.tsx +34 -17
  57. package/components/builder/asset-browser/helpers.ts +4 -0
  58. package/components/builder/asset-browser/types.ts +2 -1
  59. package/components/builder/blockStyles.tsx +192 -173
  60. package/components/builder/color-picker/AlphaSlider.tsx +141 -141
  61. package/components/builder/color-picker/ColorInputs.tsx +105 -105
  62. package/components/builder/color-picker/EyedropperButton.tsx +74 -74
  63. package/components/builder/color-picker/HueSlider.tsx +124 -124
  64. package/components/builder/color-picker/SaturationCanvas.tsx +142 -142
  65. package/components/builder/color-picker/SwatchBar.tsx +93 -93
  66. package/components/builder/editors/AudioBlockEditor.tsx +242 -0
  67. package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -0
  68. package/components/builder/editors/ButtonBlockEditor.tsx +4 -4
  69. package/components/builder/editors/EnterAnimationPicker.tsx +2 -2
  70. package/components/builder/editors/HoverEffectPicker.tsx +2 -2
  71. package/components/builder/editors/ImageBlockEditor.tsx +2 -2
  72. package/components/builder/editors/ImageGridBlockEditor.tsx +4 -4
  73. package/components/builder/editors/MarqueeBlockEditor.tsx +621 -0
  74. package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
  75. package/components/builder/editors/ProjectGridEditor.tsx +9 -9
  76. package/components/builder/editors/SpacerBlockEditor.tsx +5 -5
  77. package/components/builder/editors/StaggerSettings.tsx +109 -109
  78. package/components/builder/editors/TextBlockEditor.tsx +3 -3
  79. package/components/builder/editors/TextStylePicker.tsx +1 -1
  80. package/components/builder/editors/VideoBlockEditor.tsx +2 -2
  81. package/components/builder/editors/index.ts +11 -10
  82. package/components/builder/editors/shared.tsx +7 -7
  83. package/components/builder/live-preview/LiveAudioPreview.tsx +120 -0
  84. package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +176 -0
  85. package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
  86. package/components/builder/live-preview/LiveImagePreview.tsx +1 -1
  87. package/components/builder/live-preview/LiveMarqueePreview.tsx +39 -0
  88. package/components/builder/live-preview/LiveProjectCarouselPreview.tsx +1 -1
  89. package/components/builder/live-preview/LiveVideoPreview.tsx +1 -1
  90. package/components/builder/live-preview/ProjectCardWrapper.tsx +291 -291
  91. package/components/builder/settings-panel/AnimationTab.tsx +138 -138
  92. package/components/builder/settings-panel/BlockLayoutTab.tsx +7 -7
  93. package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
  94. package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
  95. package/components/builder/settings-panel/CoverSectionLayoutTab.tsx +71 -71
  96. package/components/builder/settings-panel/CoverSectionSettings.tsx +335 -335
  97. package/components/builder/settings-panel/PageSettings.tsx +3 -3
  98. package/components/builder/settings-panel/ParallaxSlideSettings.tsx +2 -2
  99. package/components/builder/settings-panel/SectionV2AnimationTab.tsx +4 -4
  100. package/components/builder/settings-panel/SectionV2LayoutTab.tsx +356 -356
  101. package/components/builder/settings-panel/SectionV2Settings.tsx +14 -14
  102. package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
  103. package/lib/animation/enter-types.ts +3 -0
  104. package/lib/animation/hover-effect-presets.ts +210 -210
  105. package/lib/animation/hover-effect-types.ts +3 -0
  106. package/lib/builder/block-registrations.ts +468 -335
  107. package/lib/builder/constants.ts +111 -111
  108. package/lib/builder/store-sections.ts +2 -2
  109. package/lib/builder/types-slices.ts +414 -414
  110. package/lib/builder/types.ts +6 -1
  111. package/lib/config/index.ts +27 -27
  112. package/lib/sanity/types.ts +156 -1
  113. package/lib/version.ts +1 -1
  114. package/package.json +1 -1
  115. package/sanity/schemas/blocks/audioBlock.ts +69 -0
  116. package/sanity/schemas/blocks/beforeAfterBlock.ts +121 -0
  117. package/sanity/schemas/blocks/index.ts +12 -9
  118. package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
  119. package/sanity/schemas/index.ts +120 -111
  120. package/styles/admin.css +85 -85
  121. package/styles/animations.css +237 -237
  122. package/styles/base.css +114 -114
@@ -101,7 +101,7 @@ function PresetGrid({ section }: { section: PageSectionV2 }) {
101
101
  onClick={() => !isCustom && applyPresetV2(section._key, preset.id)}
102
102
  className={`flex flex-col items-center gap-1 p-2 rounded-lg border transition-all ${
103
103
  isActive
104
- ? "border-[#4794e2] bg-[#4794e2]/5"
104
+ ? "border-[#3580f9] bg-[#3580f9]/5"
105
105
  : isCustom
106
106
  ? "border-neutral-200 bg-neutral-50 opacity-60 cursor-default"
107
107
  : "border-neutral-200 bg-white hover:border-neutral-300 hover:bg-neutral-50"
@@ -121,7 +121,7 @@ function PresetGrid({ section }: { section: PageSectionV2 }) {
121
121
  <div
122
122
  key={i}
123
123
  className={`rounded-sm transition-colors ${
124
- isActive ? "bg-[#4794e2]" : "bg-neutral-300"
124
+ isActive ? "bg-[#3580f9]" : "bg-neutral-300"
125
125
  }`}
126
126
  style={{ flex: span }}
127
127
  />
@@ -129,7 +129,7 @@ function PresetGrid({ section }: { section: PageSectionV2 }) {
129
129
  )}
130
130
  </div>
131
131
  <span className={`text-[9px] font-medium ${
132
- isActive ? "text-[#4794e2]" : "text-neutral-500"
132
+ isActive ? "text-[#3580f9]" : "text-neutral-500"
133
133
  }`}>
134
134
  {preset.label}
135
135
  </span>
@@ -140,15 +140,15 @@ function PresetGrid({ section }: { section: PageSectionV2 }) {
140
140
  {/* + Add Column button */}
141
141
  <button
142
142
  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"
143
+ className="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"
144
144
  title="Add a column (fills first gap, or adds new row below)"
145
145
  >
146
146
  <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">
147
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" className="text-neutral-400 group-hover:text-[#3580f9] transition-colors">
148
148
  <path d="M12 5v14M5 12h14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
149
149
  </svg>
150
150
  </div>
151
- <span className="text-[9px] font-medium text-neutral-400 group-hover:text-[#4794e2] transition-colors">
151
+ <span className="text-[9px] font-medium text-neutral-400 group-hover:text-[#3580f9] transition-colors">
152
152
  Add Col
153
153
  </span>
154
154
  </button>
@@ -204,8 +204,8 @@ export default function SectionV2Settings({ section }: { section: PageSectionV2
204
204
  {/* Responsive info banner */}
205
205
  {isResponsive && (
206
206
  <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]">
207
+ <div className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#3580f9]/8 border border-[#3580f9]/15">
208
+ <span className="text-[11px] font-medium text-[#3580f9]">
209
209
  Editing {activeViewport === "tablet" ? "Tablet" : "Phone"} overrides
210
210
  </span>
211
211
  </div>
@@ -218,7 +218,7 @@ export default function SectionV2Settings({ section }: { section: PageSectionV2
218
218
  <div className="flex gap-2">
219
219
  <button
220
220
  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"
221
+ className="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"
222
222
  title="Stack all columns vertically (full width, one per row)"
223
223
  >
224
224
  Stack Columns
@@ -237,7 +237,7 @@ export default function SectionV2Settings({ section }: { section: PageSectionV2
237
237
  </button>
238
238
  </div>
239
239
  {hasAnyOverrides && (
240
- <p className="text-[10px] text-[#4794e2]/60 mt-1.5">
240
+ <p className="text-[10px] text-[#3580f9]/60 mt-1.5">
241
241
  {hasColOverrides ? "Column layout" : ""}
242
242
  {hasColOverrides && hasSettingsOverrides ? " + " : ""}
243
243
  {hasSettingsOverrides ? "settings" : ""}
@@ -266,7 +266,7 @@ export default function SectionV2Settings({ section }: { section: PageSectionV2
266
266
  <span>
267
267
  Col Gap
268
268
  {isResponsive && hasSectionV2SettingOverride(section, activeViewport, "col_gap") && (
269
- <span className="ml-1 text-[9px] text-[#4794e2]">overridden</span>
269
+ <span className="ml-1 text-[9px] text-[#3580f9]">overridden</span>
270
270
  )}
271
271
  </span>
272
272
  }>
@@ -278,7 +278,7 @@ export default function SectionV2Settings({ section }: { section: PageSectionV2
278
278
  step={4}
279
279
  value={getGapValue("col_gap", 20)}
280
280
  onChange={(e) => updateSettingResponsive("col_gap", parseInt(e.target.value))}
281
- className="flex-1 accent-[#4794e2]"
281
+ className="flex-1 accent-[#3580f9]"
282
282
  />
283
283
  <span className="text-xs text-neutral-900 w-12 text-right">
284
284
  {getGapValue("col_gap", 20)}px
@@ -298,7 +298,7 @@ export default function SectionV2Settings({ section }: { section: PageSectionV2
298
298
  <span>
299
299
  Row Gap
300
300
  {isResponsive && hasSectionV2SettingOverride(section, activeViewport, "row_gap") && (
301
- <span className="ml-1 text-[9px] text-[#4794e2]">overridden</span>
301
+ <span className="ml-1 text-[9px] text-[#3580f9]">overridden</span>
302
302
  )}
303
303
  </span>
304
304
  }>
@@ -310,7 +310,7 @@ export default function SectionV2Settings({ section }: { section: PageSectionV2
310
310
  step={4}
311
311
  value={getGapValue("row_gap", 20)}
312
312
  onChange={(e) => updateSettingResponsive("row_gap", parseInt(e.target.value))}
313
- className="flex-1 accent-[#4794e2]"
313
+ className="flex-1 accent-[#3580f9]"
314
314
  />
315
315
  <span className="text-xs text-neutral-900 w-12 text-right">
316
316
  {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
  ))}
@@ -73,9 +73,12 @@ export const BLOCK_ENTER_PRESETS: Record<BlockType, readonly EnterPreset[]> = {
73
73
  imageGridBlock: ["fade", "scale", "slide-up"],
74
74
  videoBlock: ["fade", "slide-up"],
75
75
  buttonBlock: ["fade", "slide-up", "scale"],
76
+ beforeAfterBlock: ["fade", "slide-up", "scale"],
77
+ audioBlock: ["fade", "slide-up", "scale"],
76
78
  spacerBlock: [], // invisible — no animation
77
79
  projectGridBlock: [], // uses card_entrance system
78
80
  projectCarouselBlock: [], // uses card_entrance system
81
+ marqueeBlock: ["fade", "slide-up", "scale"],
79
82
  };
80
83
 
81
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
+ }
@@ -66,9 +66,12 @@ export const BLOCK_HOVER_PRESETS: Record<BlockType, readonly HoverPreset[]> = {
66
66
  imageGridBlock: ["tilt-3d"],
67
67
  videoBlock: [], // video has play/pause interaction
68
68
  buttonBlock: ["scale-up", "lift", "border-glow"],
69
+ beforeAfterBlock: [], // slider handles its own drag interaction
70
+ audioBlock: [], // audio has play/pause interaction
69
71
  spacerBlock: [], // invisible
70
72
  projectGridBlock: ["scale-up", "lift"], // per-card effect
71
73
  projectCarouselBlock: [], // uses per-card hover_effect field directly
74
+ marqueeBlock: [], // already animating — hover effects fight the motion
72
75
  };
73
76
 
74
77
  // ── Hover effect config ────────────────────────────────────────────