@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.
- package/README.md +27 -2
- 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 +332 -320
- package/app/admin/navigation/page.tsx +255 -255
- package/app/admin/pages/[slug]/page.tsx +44 -27
- package/app/admin/pages/page.tsx +24 -19
- package/app/admin/projects/page.tsx +30 -21
- package/app/admin/setup/page.tsx +1 -1
- package/app/admin/styles/page.tsx +1 -1
- package/app/api/admin/assets/register/route.ts +51 -14
- package/app/api/admin/assets/registry/route.ts +4 -1
- package/app/api/admin/assets/relink/confirm/route.ts +4 -1
- package/app/api/admin/assets/relink/route.ts +4 -1
- package/app/api/admin/assets/scan/route.ts +4 -1
- package/app/api/admin/backups/restore-data/route.ts +4 -1
- package/app/api/admin/r2/connect/route.ts +4 -1
- package/app/api/admin/r2/delete/route.ts +4 -1
- package/app/api/admin/r2/rename/route.ts +4 -1
- package/app/api/admin/r2/upload-url/route.ts +4 -1
- package/app/api/admin/revalidate/route.ts +4 -1
- package/app/api/admin/storage/switch/route.ts +4 -1
- package/app/api/custom-sections/[id]/route.ts +5 -6
- package/components/admin/MetadataEditor.tsx +6 -6
- package/components/admin/PublishToggle.tsx +2 -2
- 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 +8 -6
- 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 +518 -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 +9 -8
- package/components/admin/styles/FontsEditor.tsx +9 -7
- 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 -286
- package/components/blocks/CoverSectionRenderer.tsx +7 -1
- package/components/blocks/MarqueeBlockRenderer.tsx +316 -0
- package/components/blocks/ProjectCarouselBlockRenderer.tsx +1 -1
- package/components/blocks/SectionV2Renderer.tsx +8 -1
- package/components/builder/BlockCardIcons.tsx +316 -316
- package/components/builder/BlockTypePicker.tsx +1 -1
- package/components/builder/BubbleIcons.tsx +104 -0
- package/components/builder/BuilderCanvas.tsx +2 -0
- package/components/builder/CanvasMinimap.tsx +66 -49
- package/components/builder/CanvasToolbar.tsx +31 -41
- package/components/builder/CoverSectionCanvas.tsx +363 -363
- 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 +5 -3
- package/components/builder/SectionTypePicker.tsx +7 -5
- package/components/builder/SectionV2Canvas.tsx +1 -1
- package/components/builder/SectionV2Column.tsx +82 -68
- package/components/builder/SettingsPanel.tsx +21 -17
- package/components/builder/SortableBlock.tsx +93 -73
- package/components/builder/SortableRow.tsx +33 -35
- package/components/builder/VirtualAssetGrid.tsx +10 -4
- package/components/builder/asset-browser/R2BrowserContent.tsx +18 -14
- package/components/builder/blockStyles.tsx +192 -185
- 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 +75 -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 +98 -93
- package/components/builder/color-picker/UnifiedColorPicker.tsx +11 -6
- package/components/builder/editors/AudioBlockEditor.tsx +242 -242
- package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -360
- 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 +8 -6
- package/components/builder/editors/MarqueeBlockEditor.tsx +622 -0
- package/components/builder/editors/ProjectCarouselBlockEditor.tsx +443 -443
- package/components/builder/editors/ProjectGridEditor.tsx +21 -16
- package/components/builder/editors/SpacerBlockEditor.tsx +29 -27
- package/components/builder/editors/StaggerSettings.tsx +109 -109
- package/components/builder/editors/TextBlockEditor.tsx +22 -17
- 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 +10 -8
- package/components/builder/live-preview/LiveAudioPreview.tsx +120 -120
- package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +1 -1
- package/components/builder/live-preview/LiveImageGridPreview.tsx +10 -2
- package/components/builder/live-preview/LiveImagePreview.tsx +4 -2
- 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 +293 -291
- package/components/builder/live-preview/RichTextBubbleMenu.tsx +10 -6
- package/components/builder/live-preview/shared.tsx +5 -2
- package/components/builder/settings-panel/AnimationTab.tsx +138 -138
- package/components/builder/settings-panel/BlockLayoutTab.tsx +11 -9
- package/components/builder/settings-panel/CardEntranceSection.tsx +114 -114
- package/components/builder/settings-panel/ColumnV2LayoutTab.tsx +242 -0
- 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 +337 -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 +25 -20
- package/components/builder/settings-panel/TRBLInputs.tsx +1 -1
- package/components/builder/settings-panel/index.ts +1 -0
- package/lib/animation/enter-types.ts +1 -0
- package/lib/animation/hover-effect-presets.ts +210 -210
- package/lib/animation/hover-effect-types.ts +1 -0
- package/lib/builder/block-registrations.ts +468 -417
- package/lib/builder/constants.ts +111 -111
- package/lib/builder/serializer/normalizers.ts +14 -0
- package/lib/builder/serializer/serializers.ts +27 -0
- package/lib/builder/store-sections.ts +23 -2
- package/lib/builder/types-slices.ts +428 -414
- package/lib/builder/types.ts +4 -1
- package/lib/config/index.ts +27 -27
- package/lib/sanity/queries.ts +48 -0
- package/lib/sanity/types.ts +112 -1
- package/lib/version.ts +1 -1
- package/package.json +7 -5
- package/sanity/schemas/blocks/audioBlock.ts +69 -69
- package/sanity/schemas/blocks/index.ts +12 -11
- package/sanity/schemas/blocks/marqueeBlock.ts +292 -0
- package/sanity/schemas/index.ts +120 -117
- package/sanity/schemas/objects/coverSection.ts +32 -0
- package/sanity/schemas/objects/parallaxSlide.ts +32 -0
- package/sanity/schemas/pageSectionV2.ts +32 -0
- package/styles/admin.css +85 -85
- package/styles/animations.css +237 -237
- 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-[#
|
|
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
|
-
|
|
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-[#
|
|
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-[#
|
|
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-[#
|
|
144
|
-
|
|
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-[#
|
|
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-[#
|
|
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-[#
|
|
208
|
-
<span className="text-[11px] font-medium text-[#
|
|
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-[#
|
|
222
|
-
|
|
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
|
-
|
|
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-[#
|
|
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-[#
|
|
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-[#
|
|
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-[#
|
|
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-[#
|
|
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-[#
|
|
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(
|
|
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
|
+
}
|
|
@@ -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 ────────────────────────────────────────────
|