@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
@@ -51,6 +51,8 @@ export const BLOCK_TYPE_REGISTRY: BlockTypeInfo[] = [
51
51
  { type: "videoBlock", label: "Video", description: "Vimeo, YouTube, or MP4", group: "generic", icon: "▶", category: "content" },
52
52
  { type: "spacerBlock", label: "Spacer", description: "Vertical spacing", group: "generic", icon: "↕", category: "content" },
53
53
  { type: "buttonBlock", label: "Button", description: "Call-to-action button", group: "generic", icon: "▣", category: "content" },
54
+ { type: "beforeAfterBlock", label: "Before / After", description: "Drag-slider comparison between two images or videos", group: "generic", icon: "◫", category: "content" },
55
+ { type: "audioBlock", label: "Audio", description: "Minimal audio player with cover art and metadata", group: "generic", icon: "♪", category: "content" },
54
56
  ];
55
57
 
56
58
  /**
@@ -63,6 +65,7 @@ export const ALL_BLOCK_INFO: BlockTypeInfo[] = [
63
65
  // Section blocks — not in the content picker but still need label/icon lookup
64
66
  { type: "projectGridBlock", label: "Project Grid", description: "Staggered project showcase grid", group: "generic", icon: "⬡", category: "section" },
65
67
  { type: "projectCarouselBlock", label: "Project Carousel", description: "Horizontal carousel of projects — great for end-of-page 'keep browsing'", group: "generic", icon: "▸", category: "section" },
68
+ { type: "marqueeBlock", label: "Marquee", description: "Horizontal scrolling ticker of text and images", group: "generic", icon: "⇄", category: "section" },
66
69
  ];
67
70
 
68
71
  // Parallax group info — used by BuilderCanvas/SortableRow for label/icon lookup (not a block)
@@ -73,12 +76,13 @@ export const PARALLAX_GROUP_INFO = { label: "Parallax Showcase", icon: "▽" };
73
76
  // ============================================
74
77
 
75
78
  /** Section block types that create a full-width row with a pre-populated block */
76
- export type SectionBlockType = "projectGridBlock" | "projectCarouselBlock";
79
+ export type SectionBlockType = "projectGridBlock" | "projectCarouselBlock" | "marqueeBlock";
77
80
 
78
81
  /** Set for fast lookup — used by SortableBlock, ColumnDropZone, SortableRow to suppress inner chrome */
79
82
  const SECTION_BLOCK_TYPES: ReadonlySet<string> = new Set<string>([
80
83
  "projectGridBlock",
81
84
  "projectCarouselBlock",
85
+ "marqueeBlock",
82
86
  ]);
83
87
 
84
88
  /** Check if a block type is a section-level block (should render without block/column chrome) */
@@ -112,6 +116,7 @@ export const SECTION_TYPE_REGISTRY: SectionTypeInfo[] = [
112
116
  { type: "coverSection", label: "Cover Section", description: "Full-viewport section with background and proportional rows", icon: "◆" },
113
117
  { type: "projectGridBlock", label: "Project Grid", description: "Staggered project showcase grid", icon: "⬡", blockType: "projectGridBlock" },
114
118
  { type: "projectCarouselBlock", label: "Project Carousel", description: "Horizontal 'keep browsing' carousel of projects", icon: "▸", blockType: "projectCarouselBlock" },
119
+ { type: "marqueeBlock", label: "Marquee", description: "Horizontal scrolling ticker of text and images", icon: "⇄", blockType: "marqueeBlock" },
115
120
  { type: "parallaxGroup", label: "Parallax Section", description: "Full-screen parallax showcase with V2 slides", icon: "▽" },
116
121
  ];
117
122
 
@@ -1,28 +1,28 @@
1
- /**
2
- * Site configuration accessor.
3
- *
4
- * Uses globalThis + Symbol.for to guarantee a true singleton even when
5
- * the bundler creates multiple module instances of this file.
6
- */
7
-
8
- import type { SiteConfig } from "./types";
9
-
10
- const CONFIG_KEY = Symbol.for("@morphika/andami/siteConfig");
11
- const g = globalThis as unknown as Record<symbol, SiteConfig | undefined>;
12
-
13
- export function registerConfig(config: SiteConfig): void {
14
- g[CONFIG_KEY] = config;
15
- }
16
-
17
- export function getSiteConfig(): SiteConfig {
18
- const cfg = g[CONFIG_KEY];
19
- if (!cfg) {
20
- throw new Error(
21
- "SiteConfig not registered. Call registerConfig(config) in your root layout before using getSiteConfig().\n" +
22
- "See: https://github.com/MorphikaStudio/Morphika_Andami#quick-start",
23
- );
24
- }
25
- return cfg;
26
- }
27
-
1
+ /**
2
+ * Site configuration accessor.
3
+ *
4
+ * Uses globalThis + Symbol.for to guarantee a true singleton even when
5
+ * the bundler creates multiple module instances of this file.
6
+ */
7
+
8
+ import type { SiteConfig } from "./types";
9
+
10
+ const CONFIG_KEY = Symbol.for("@morphika/andami/siteConfig");
11
+ const g = globalThis as unknown as Record<symbol, SiteConfig | undefined>;
12
+
13
+ export function registerConfig(config: SiteConfig): void {
14
+ g[CONFIG_KEY] = config;
15
+ }
16
+
17
+ export function getSiteConfig(): SiteConfig {
18
+ const cfg = g[CONFIG_KEY];
19
+ if (!cfg) {
20
+ throw new Error(
21
+ "SiteConfig not registered. Call registerConfig(config) in your root layout before using getSiteConfig().\n" +
22
+ "See: https://github.com/MorphikaStudio/Morphika_Andami#quick-start",
23
+ );
24
+ }
25
+ return cfg;
26
+ }
27
+
28
28
  export type { SiteConfig } from "./types";
@@ -210,6 +210,62 @@ export interface ButtonBlock {
210
210
  responsive?: ResponsiveOverrides<ButtonBlock>;
211
211
  }
212
212
 
213
+ export interface BeforeAfterBlock {
214
+ _type: "beforeAfterBlock";
215
+ _key: string;
216
+ // Before side
217
+ before_media_type?: "image" | "video";
218
+ before_asset_path: string;
219
+ before_alt?: string;
220
+ // After side
221
+ after_media_type?: "image" | "video";
222
+ after_asset_path: string;
223
+ after_alt?: string;
224
+ // Slider
225
+ orientation?: "horizontal" | "vertical";
226
+ initial_position?: number; // 0–100
227
+ handle_color?: string; // hex
228
+ // Layout
229
+ width?: "full" | "contained" | "small" | "fill";
230
+ aspect_ratio?: "auto" | "16:9" | "4:3" | "1:1" | "21:9";
231
+ // Video playback (applies when either side is video)
232
+ video_autoplay?: boolean;
233
+ video_loop?: boolean;
234
+ video_muted?: boolean;
235
+ // Appearance
236
+ border_radius?: string;
237
+ shadow?: boolean;
238
+ enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
239
+ hover_effect?: import("../../lib/animation/hover-effect-types").HoverEffectConfig;
240
+ layout?: BlockLayout;
241
+ responsive?: ResponsiveOverrides<BeforeAfterBlock>;
242
+ }
243
+
244
+ export interface AudioBlock {
245
+ _type: "audioBlock";
246
+ _key: string;
247
+ // Source
248
+ asset_path: string;
249
+ alt?: string;
250
+ // Metadata
251
+ title?: string;
252
+ artist?: string;
253
+ cover_path?: string;
254
+ // Appearance
255
+ accent_color?: string;
256
+ width?: "full" | "contained" | "small" | "fill";
257
+ border_radius?: string;
258
+ shadow?: boolean;
259
+ // Playback
260
+ autoplay?: boolean;
261
+ loop?: boolean;
262
+ muted?: boolean;
263
+ enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
264
+ hover_effect?: import("../../lib/animation/hover-effect-types").HoverEffectConfig;
265
+ layout?: BlockLayout;
266
+ responsive?: ResponsiveOverrides<AudioBlock>;
267
+ }
268
+
213
269
  // ============================================
214
270
  // Project Grid Block v2 (template-only, Session 105)
215
271
  // ============================================
@@ -312,6 +368,102 @@ export interface ProjectGridBlock {
312
368
  responsive?: ResponsiveOverrides<ProjectGridBlock>;
313
369
  }
314
370
 
371
+ // ============================================
372
+ // Marquee Block — horizontal infinite-scroll ticker (Session 184)
373
+ // ============================================
374
+
375
+ /** Plain-text item in a marquee. */
376
+ export interface MarqueeTextItem {
377
+ _key: string;
378
+ _type: "marqueeText";
379
+ text: string;
380
+ }
381
+
382
+ /** Image item in a marquee — asset_path is relative (resolved by the asset proxy). */
383
+ export interface MarqueeImageItem {
384
+ _key: string;
385
+ _type: "marqueeImage";
386
+ asset_path: string;
387
+ alt?: string;
388
+ /** Per-item override, 0–200 px. */
389
+ border_radius?: number;
390
+ /** Fixed width in px. When absent, derived from row_height × natural aspect ratio. */
391
+ width?: number;
392
+ }
393
+
394
+ /** Separator item — inherits block-level typography (size/color/weight/style). */
395
+ export interface MarqueeSeparatorItem {
396
+ _key: string;
397
+ _type: "marqueeSeparator";
398
+ /** 1–4 chars — glyph or emoji. Common: • · — / ▸ ★ */
399
+ character: string;
400
+ }
401
+
402
+ export type MarqueeItem =
403
+ | MarqueeTextItem
404
+ | MarqueeImageItem
405
+ | MarqueeSeparatorItem;
406
+
407
+ /**
408
+ * Font-size scale for text/separator items in a marquee.
409
+ * Mapped to concrete rem/px values by the renderer.
410
+ */
411
+ export type MarqueeFontSize =
412
+ | "s"
413
+ | "base"
414
+ | "l"
415
+ | "xl"
416
+ | "2xl"
417
+ | "3xl"
418
+ | "4xl"
419
+ | "5xl"
420
+ | "6xl";
421
+
422
+ /**
423
+ * Marquee Block — horizontal infinite-scroll ticker.
424
+ *
425
+ * Section-level block: lives inside a full-width column of a `PageSectionV2`.
426
+ * Available on both pages and projects (unlike projectGrid/projectCarousel
427
+ * which are pages-only) — makes sense as an end-of-project "keep browsing"
428
+ * style band.
429
+ *
430
+ * Motion is driven by CSS `@keyframes` with an IntersectionObserver pause
431
+ * when off-screen. Respects `prefers-reduced-motion`. Hover effects are
432
+ * deliberately not supported — a block that is already animating fights
433
+ * scale/tilt hover presets.
434
+ */
435
+ export interface MarqueeBlock {
436
+ _type: "marqueeBlock";
437
+ _key: string;
438
+
439
+ // ─── Content ───
440
+ items: MarqueeItem[];
441
+
442
+ // ─── Motion ───
443
+ direction?: "left" | "right"; // default "left"
444
+ speed?: number; // px/s, 5–600, default 60
445
+ pause_on_hover?: boolean; // default true
446
+
447
+ // ─── Typography (applies to text + separator items) ───
448
+ font_size?: MarqueeFontSize; // default "3xl"
449
+ font_weight?: "400" | "500" | "700" | "900"; // default "700"
450
+ color?: string; // hex or palette token, default "#111111"
451
+ text_style?: "solid" | "outline" | "italic-outline"; // default "solid"
452
+ letter_spacing?: number; // em, default 0
453
+ text_transform?: "none" | "uppercase" | "lowercase"; // default "uppercase"
454
+
455
+ // ─── Layout ───
456
+ gap?: number; // px between items, default 48
457
+ row_height?: number; // px, default 120 (controls image height)
458
+ padding_y?: number; // px, default 16
459
+ background_color?: string; // hex or palette; empty = transparent
460
+
461
+ // ─── Standard block fields ───
462
+ enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
463
+ layout?: BlockLayout;
464
+ responsive?: ResponsiveOverrides<MarqueeBlock>;
465
+ }
466
+
315
467
  // ============================================
316
468
  // Parallax V2 — Group + Slide types (Session 123)
317
469
  // ============================================
@@ -635,8 +787,11 @@ export type ContentBlock =
635
787
  | VideoBlock
636
788
  | SpacerBlock
637
789
  | ButtonBlock
790
+ | BeforeAfterBlock
791
+ | AudioBlock
638
792
  | ProjectGridBlock
639
- | ProjectCarouselBlock;
793
+ | ProjectCarouselBlock
794
+ | MarqueeBlock;
640
795
 
641
796
  // ============================================
642
797
  // Structural types
package/lib/version.ts CHANGED
@@ -6,4 +6,4 @@
6
6
  * Exposed as a plain constant so it can be imported without reading
7
7
  * package.json at runtime.
8
8
  */
9
- export const ANDAMI_VERSION = "0.5.0";
9
+ export const ANDAMI_VERSION = "0.5.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morphika/andami",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Visual Page Builder — core library. A reusable website builder with visual editing, CMS integration, and asset management.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,69 @@
1
+ import { defineField, defineType } from "sanity";
2
+ import { blockLayoutField, blockAnimationFields } from "./blockLayout";
3
+
4
+ export const audioBlock = defineType({
5
+ name: "audioBlock",
6
+ title: "Audio Block",
7
+ type: "object",
8
+ fields: [
9
+ // ── Source ──
10
+ defineField({
11
+ name: "asset_path",
12
+ title: "Audio File",
13
+ type: "string",
14
+ description: "Relative path to the audio file (mp3, wav, ogg, m4a, aac, flac)",
15
+ validation: (Rule) => Rule.required(),
16
+ }),
17
+ defineField({ name: "alt", title: "Alt Text", type: "string" }),
18
+
19
+ // ── Metadata ──
20
+ defineField({ name: "title", title: "Title", type: "string" }),
21
+ defineField({ name: "artist", title: "Artist", type: "string" }),
22
+ defineField({
23
+ name: "cover_path",
24
+ title: "Cover Art",
25
+ type: "string",
26
+ description: "Optional relative path to a cover image",
27
+ }),
28
+
29
+ // ── Appearance ──
30
+ defineField({
31
+ name: "accent_color",
32
+ title: "Accent Color",
33
+ type: "string",
34
+ description: "Hex color for the play button + progress fill",
35
+ initialValue: "#3580f9",
36
+ }),
37
+ defineField({
38
+ name: "width",
39
+ title: "Width",
40
+ type: "string",
41
+ options: {
42
+ list: [
43
+ { title: "Full", value: "full" },
44
+ { title: "Contained", value: "contained" },
45
+ { title: "Small", value: "small" },
46
+ { title: "Fill", value: "fill" },
47
+ ],
48
+ },
49
+ initialValue: "contained",
50
+ }),
51
+ defineField({ name: "border_radius", title: "Border Radius", type: "string" }),
52
+ defineField({ name: "shadow", title: "Shadow", type: "boolean", initialValue: false }),
53
+
54
+ // ── Playback ──
55
+ defineField({ name: "autoplay", title: "Autoplay", type: "boolean", initialValue: false }),
56
+ defineField({ name: "loop", title: "Loop", type: "boolean", initialValue: false }),
57
+ defineField({ name: "muted", title: "Muted", type: "boolean", initialValue: false }),
58
+
59
+ ...blockAnimationFields,
60
+ blockLayoutField,
61
+ ],
62
+ preview: {
63
+ select: { path: "asset_path", title: "title", artist: "artist" },
64
+ prepare({ path, title, artist }) {
65
+ const label = title ? (artist ? `${title} — ${artist}` : title) : path || "Audio block";
66
+ return { title: label };
67
+ },
68
+ },
69
+ });
@@ -0,0 +1,121 @@
1
+ import { defineField, defineType } from "sanity";
2
+ import { blockLayoutField, blockAnimationFields } from "./blockLayout";
3
+
4
+ export const beforeAfterBlock = defineType({
5
+ name: "beforeAfterBlock",
6
+ title: "Before / After Block",
7
+ type: "object",
8
+ fields: [
9
+ // ── Before side ──
10
+ defineField({
11
+ name: "before_media_type",
12
+ title: "Before Media Type",
13
+ type: "string",
14
+ options: { list: [{ title: "Image", value: "image" }, { title: "Video", value: "video" }] },
15
+ initialValue: "image",
16
+ }),
17
+ defineField({
18
+ name: "before_asset_path",
19
+ title: "Before Asset Path",
20
+ type: "string",
21
+ description: "Relative path to the image or video file shown before the slider",
22
+ validation: (Rule) => Rule.required(),
23
+ }),
24
+ defineField({ name: "before_alt", title: "Before Alt Text", type: "string" }),
25
+
26
+ // ── After side ──
27
+ defineField({
28
+ name: "after_media_type",
29
+ title: "After Media Type",
30
+ type: "string",
31
+ options: { list: [{ title: "Image", value: "image" }, { title: "Video", value: "video" }] },
32
+ initialValue: "image",
33
+ }),
34
+ defineField({
35
+ name: "after_asset_path",
36
+ title: "After Asset Path",
37
+ type: "string",
38
+ description: "Relative path to the image or video file shown after the slider",
39
+ validation: (Rule) => Rule.required(),
40
+ }),
41
+ defineField({ name: "after_alt", title: "After Alt Text", type: "string" }),
42
+
43
+ // ── Slider behavior ──
44
+ defineField({
45
+ name: "orientation",
46
+ title: "Orientation",
47
+ type: "string",
48
+ options: {
49
+ list: [
50
+ { title: "Horizontal (slider left-right)", value: "horizontal" },
51
+ { title: "Vertical (slider top-bottom)", value: "vertical" },
52
+ ],
53
+ },
54
+ initialValue: "horizontal",
55
+ }),
56
+ defineField({
57
+ name: "initial_position",
58
+ title: "Initial Position",
59
+ type: "number",
60
+ description: "Starting split position (0–100%)",
61
+ initialValue: 50,
62
+ validation: (Rule) => Rule.min(0).max(100),
63
+ }),
64
+ defineField({
65
+ name: "handle_color",
66
+ title: "Handle Color",
67
+ type: "string",
68
+ description: "Hex color for the slider line + handle",
69
+ initialValue: "#FFFFFF",
70
+ }),
71
+
72
+ // ── Layout ──
73
+ defineField({
74
+ name: "width",
75
+ title: "Width",
76
+ type: "string",
77
+ options: {
78
+ list: [
79
+ { title: "Full", value: "full" },
80
+ { title: "Contained", value: "contained" },
81
+ { title: "Small", value: "small" },
82
+ { title: "Fill", value: "fill" },
83
+ ],
84
+ },
85
+ initialValue: "full",
86
+ }),
87
+ defineField({
88
+ name: "aspect_ratio",
89
+ title: "Aspect Ratio",
90
+ type: "string",
91
+ options: {
92
+ list: [
93
+ { title: "Auto", value: "auto" },
94
+ { title: "16:9", value: "16:9" },
95
+ { title: "4:3", value: "4:3" },
96
+ { title: "1:1", value: "1:1" },
97
+ { title: "21:9", value: "21:9" },
98
+ ],
99
+ },
100
+ initialValue: "16:9",
101
+ }),
102
+
103
+ // ── Video playback (applies when either side is video) ──
104
+ defineField({ name: "video_autoplay", title: "Video Autoplay", type: "boolean", initialValue: true }),
105
+ defineField({ name: "video_loop", title: "Video Loop", type: "boolean", initialValue: true }),
106
+ defineField({ name: "video_muted", title: "Video Muted", type: "boolean", initialValue: true }),
107
+
108
+ // ── Appearance ──
109
+ defineField({ name: "border_radius", title: "Border Radius", type: "string" }),
110
+ defineField({ name: "shadow", title: "Shadow", type: "boolean", initialValue: false }),
111
+
112
+ ...blockAnimationFields,
113
+ blockLayoutField,
114
+ ],
115
+ preview: {
116
+ select: { before: "before_asset_path", after: "after_asset_path" },
117
+ prepare({ before, after }) {
118
+ return { title: `Before/After: ${before || "—"} ↔ ${after || "—"}` };
119
+ },
120
+ },
121
+ });
@@ -1,9 +1,12 @@
1
- // Block schemas (8)
2
- export { textBlock } from "./textBlock";
3
- export { imageBlock } from "./imageBlock";
4
- export { imageGridBlock } from "./imageGridBlock";
5
- export { videoBlock } from "./videoBlock";
6
- export { spacerBlock } from "./spacerBlock";
7
- export { buttonBlock } from "./buttonBlock";
8
- export { projectGridBlock } from "./projectGridBlock";
9
- export { projectCarouselBlock } from "./projectCarouselBlock";
1
+ // Block schemas (11)
2
+ export { textBlock } from "./textBlock";
3
+ export { imageBlock } from "./imageBlock";
4
+ export { imageGridBlock } from "./imageGridBlock";
5
+ export { videoBlock } from "./videoBlock";
6
+ export { spacerBlock } from "./spacerBlock";
7
+ export { buttonBlock } from "./buttonBlock";
8
+ export { beforeAfterBlock } from "./beforeAfterBlock";
9
+ export { audioBlock } from "./audioBlock";
10
+ export { projectGridBlock } from "./projectGridBlock";
11
+ export { projectCarouselBlock } from "./projectCarouselBlock";
12
+ export { marqueeBlock } from "./marqueeBlock";