@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.
- package/README.md +151 -36
- package/app/admin/assets/page.tsx +6 -6
- package/app/admin/database/page.tsx +302 -302
- package/app/admin/error.tsx +53 -53
- package/app/admin/layout.tsx +320 -327
- package/app/admin/navigation/page.tsx +255 -255
- package/app/admin/pages/[slug]/page.tsx +6 -6
- package/app/admin/pages/page.tsx +11 -11
- package/app/admin/projects/page.tsx +14 -14
- package/app/admin/setup/page.tsx +1 -1
- package/app/admin/styles/page.tsx +1 -1
- package/components/admin/MetadataEditor.tsx +6 -6
- package/components/admin/nav-builder/NavBuilder.tsx +1 -1
- package/components/admin/nav-builder/NavBuilderGrid.tsx +3 -3
- package/components/admin/nav-builder/NavGridCell.tsx +48 -48
- package/components/admin/nav-builder/NavGridItem.tsx +4 -4
- package/components/admin/nav-builder/NavItemSettings.tsx +331 -331
- package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -102
- package/components/admin/nav-builder/NavLivePreview.tsx +1 -1
- package/components/admin/nav-builder/NavMobileLivePreview.tsx +226 -226
- package/components/admin/nav-builder/NavMobileSettings.tsx +242 -242
- package/components/admin/nav-builder/NavSettingsFields.tsx +514 -514
- package/components/admin/setup-wizard/BrandingStep.tsx +3 -3
- package/components/admin/setup-wizard/DatabaseStep.tsx +2 -2
- package/components/admin/setup-wizard/DoneStep.tsx +1 -1
- package/components/admin/setup-wizard/SetupWizard.tsx +4 -4
- package/components/admin/setup-wizard/StorageStep.tsx +2 -2
- package/components/admin/setup-wizard/WelcomeStep.tsx +2 -2
- package/components/admin/styles/ColorsEditor.tsx +2 -2
- package/components/admin/styles/FontsEditor.tsx +6 -6
- package/components/admin/styles/GridLayoutEditor.tsx +9 -9
- package/components/admin/styles/LinksButtonsEditor.tsx +5 -5
- package/components/admin/styles/TypographyEditor.tsx +6 -6
- package/components/admin/styles/shared.tsx +68 -68
- package/components/blocks/AudioBlockRenderer.tsx +286 -0
- package/components/blocks/BeforeAfterBlockRenderer.tsx +274 -0
- package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
- package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
- package/components/builder/BlockCardIcons.tsx +316 -227
- package/components/builder/BlockTypePicker.tsx +3 -1
- package/components/builder/BubbleIcons.tsx +90 -0
- package/components/builder/BuilderCanvas.tsx +2 -0
- package/components/builder/CanvasMinimap.tsx +2 -2
- package/components/builder/CoverSectionCanvas.tsx +363 -275
- package/components/builder/DeviceFrame.tsx +1 -1
- package/components/builder/DndWrapper.tsx +3 -3
- package/components/builder/InsertionLines.tsx +1 -1
- package/components/builder/SectionCardIcons.tsx +421 -320
- package/components/builder/SectionEditorBar.tsx +1 -1
- package/components/builder/SectionTypePicker.tsx +4 -4
- package/components/builder/SectionV2Canvas.tsx +20 -4
- package/components/builder/SectionV2Column.tsx +74 -68
- package/components/builder/SortableBlock.tsx +93 -73
- package/components/builder/SortableRow.tsx +27 -26
- package/components/builder/VirtualAssetGrid.tsx +2 -2
- package/components/builder/asset-browser/R2BrowserContent.tsx +34 -17
- package/components/builder/asset-browser/helpers.ts +4 -0
- package/components/builder/asset-browser/types.ts +2 -1
- package/components/builder/blockStyles.tsx +192 -173
- package/components/builder/color-picker/AlphaSlider.tsx +141 -141
- package/components/builder/color-picker/ColorInputs.tsx +105 -105
- package/components/builder/color-picker/EyedropperButton.tsx +74 -74
- package/components/builder/color-picker/HueSlider.tsx +124 -124
- package/components/builder/color-picker/SaturationCanvas.tsx +142 -142
- package/components/builder/color-picker/SwatchBar.tsx +93 -93
- package/components/builder/editors/AudioBlockEditor.tsx +242 -0
- package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -0
- package/components/builder/editors/ButtonBlockEditor.tsx +4 -4
- package/components/builder/editors/EnterAnimationPicker.tsx +2 -2
- package/components/builder/editors/HoverEffectPicker.tsx +2 -2
- package/components/builder/editors/ImageBlockEditor.tsx +2 -2
- package/components/builder/editors/ImageGridBlockEditor.tsx +4 -4
- package/components/builder/editors/MarqueeBlockEditor.tsx +621 -0
- package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
- package/components/builder/editors/ProjectGridEditor.tsx +9 -9
- package/components/builder/editors/SpacerBlockEditor.tsx +5 -5
- package/components/builder/editors/StaggerSettings.tsx +109 -109
- package/components/builder/editors/TextBlockEditor.tsx +3 -3
- package/components/builder/editors/TextStylePicker.tsx +1 -1
- package/components/builder/editors/VideoBlockEditor.tsx +2 -2
- package/components/builder/editors/index.ts +11 -10
- package/components/builder/editors/shared.tsx +7 -7
- package/components/builder/live-preview/LiveAudioPreview.tsx +120 -0
- package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +176 -0
- package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
- package/components/builder/live-preview/LiveImagePreview.tsx +1 -1
- package/components/builder/live-preview/LiveMarqueePreview.tsx +39 -0
- package/components/builder/live-preview/LiveProjectCarouselPreview.tsx +1 -1
- package/components/builder/live-preview/LiveVideoPreview.tsx +1 -1
- package/components/builder/live-preview/ProjectCardWrapper.tsx +291 -291
- package/components/builder/settings-panel/AnimationTab.tsx +138 -138
- package/components/builder/settings-panel/BlockLayoutTab.tsx +7 -7
- package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
- package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
- package/components/builder/settings-panel/CoverSectionLayoutTab.tsx +71 -71
- package/components/builder/settings-panel/CoverSectionSettings.tsx +335 -335
- package/components/builder/settings-panel/PageSettings.tsx +3 -3
- package/components/builder/settings-panel/ParallaxSlideSettings.tsx +2 -2
- package/components/builder/settings-panel/SectionV2AnimationTab.tsx +4 -4
- package/components/builder/settings-panel/SectionV2LayoutTab.tsx +356 -356
- package/components/builder/settings-panel/SectionV2Settings.tsx +14 -14
- package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
- package/lib/animation/enter-types.ts +3 -0
- package/lib/animation/hover-effect-presets.ts +210 -210
- package/lib/animation/hover-effect-types.ts +3 -0
- package/lib/builder/block-registrations.ts +468 -335
- package/lib/builder/constants.ts +111 -111
- package/lib/builder/store-sections.ts +2 -2
- package/lib/builder/types-slices.ts +414 -414
- package/lib/builder/types.ts +6 -1
- package/lib/config/index.ts +27 -27
- package/lib/sanity/types.ts +156 -1
- package/lib/version.ts +1 -1
- package/package.json +1 -1
- package/sanity/schemas/blocks/audioBlock.ts +69 -0
- package/sanity/schemas/blocks/beforeAfterBlock.ts +121 -0
- package/sanity/schemas/blocks/index.ts +12 -9
- package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
- package/sanity/schemas/index.ts +120 -111
- package/styles/admin.css +85 -85
- package/styles/animations.css +237 -237
- 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-[#
|
|
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-[#
|
|
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-[#
|
|
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-[#
|
|
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-[#
|
|
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-[#
|
|
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-[#
|
|
208
|
-
<span className="text-[11px] font-medium text-[#
|
|
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-[#
|
|
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-[#
|
|
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-[#
|
|
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-[#
|
|
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-[#
|
|
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-[#
|
|
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-[#
|
|
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(
|
|
201
|
-
},
|
|
202
|
-
hover: {
|
|
203
|
-
boxShadow: "0 0 8px 4px rgba(
|
|
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 ────────────────────────────────────────────
|