@morphika/andami 0.1.9 → 0.2.0

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 (68) hide show
  1. package/app/admin/pages/[slug]/page.tsx +3 -7
  2. package/app/api/admin/pages/[slug]/route.ts +2 -28
  3. package/app/api/admin/settings/route.ts +30 -0
  4. package/components/admin/nav-builder/NavBuilder.tsx +90 -14
  5. package/components/admin/nav-builder/NavGeneralSettings.tsx +521 -271
  6. package/components/admin/nav-builder/NavItemSettings.tsx +331 -312
  7. package/components/admin/nav-builder/NavMobileSettings.tsx +159 -140
  8. package/components/admin/nav-builder/NavSettingsFields.tsx +287 -21
  9. package/components/admin/nav-builder/NavSettingsPanel.tsx +137 -127
  10. package/components/blocks/EnterAnimationWrapper.tsx +19 -4
  11. package/components/blocks/PageRenderer.tsx +2 -15
  12. package/components/blocks/ProjectGridBlockRenderer.tsx +34 -36
  13. package/components/blocks/TextBlockRenderer.tsx +1 -1
  14. package/components/builder/DndWrapper.tsx +2 -24
  15. package/components/builder/InsertionLines.tsx +5 -5
  16. package/components/builder/ReadOnlyFrame.tsx +5 -49
  17. package/components/builder/SectionV2Canvas.tsx +2 -2
  18. package/components/builder/SectionV2Column.tsx +5 -5
  19. package/components/builder/SettingsPanel.tsx +0 -12
  20. package/components/builder/SortableBlock.tsx +3 -3
  21. package/components/builder/SortableRow.tsx +6 -27
  22. package/components/builder/editors/ButtonBlockEditor.tsx +8 -3
  23. package/components/builder/editors/CoverBlockEditor.tsx +14 -6
  24. package/components/builder/editors/ImageBlockEditor.tsx +8 -3
  25. package/components/builder/editors/ImageGridBlockEditor.tsx +8 -3
  26. package/components/builder/editors/ProjectGridEditor.tsx +7 -46
  27. package/components/builder/editors/SpacerBlockEditor.tsx +4 -1
  28. package/components/builder/editors/StaggerSettings.tsx +2 -1
  29. package/components/builder/editors/TextBlockEditor.tsx +8 -3
  30. package/components/builder/editors/VideoBlockEditor.tsx +10 -4
  31. package/components/builder/editors/section-icons.tsx +492 -0
  32. package/components/builder/editors/shared.tsx +23 -4
  33. package/components/builder/live-preview/LiveTextEditor.tsx +1 -1
  34. package/components/builder/live-preview/ProjectCardWrapper.tsx +3 -3
  35. package/components/builder/live-preview/drag-utils.tsx +2 -2
  36. package/components/builder/settings-panel/AnimationTab.tsx +2 -16
  37. package/components/builder/settings-panel/BlockLayoutTab.tsx +13 -58
  38. package/components/builder/settings-panel/ColumnV2Settings.tsx +4 -1
  39. package/components/builder/settings-panel/PageSettings.tsx +10 -4
  40. package/components/builder/settings-panel/ParallaxGroupSettings.tsx +6 -2
  41. package/components/builder/settings-panel/ParallaxSlideSettings.tsx +8 -3
  42. package/components/builder/settings-panel/SectionV2LayoutTab.tsx +11 -47
  43. package/components/builder/settings-panel/SectionV2Settings.tsx +6 -27
  44. package/components/builder/settings-panel/index.ts +0 -1
  45. package/components/builder/settings-panel/responsive-helpers.ts +2 -50
  46. package/components/builder/settings-panel/useSettingsPanelSelection.ts +1 -16
  47. package/components/ui/Navbar.tsx +151 -30
  48. package/lib/builder/constants.ts +5 -4
  49. package/lib/builder/serializer/normalizers.ts +2 -40
  50. package/lib/builder/serializer/serializers.ts +3 -74
  51. package/lib/builder/store-blocks.ts +3 -19
  52. package/lib/builder/store-helpers.ts +2 -2
  53. package/lib/builder/store-sections.ts +26 -64
  54. package/lib/builder/store.ts +3 -6
  55. package/lib/builder/templates.ts +9 -45
  56. package/lib/builder/types.ts +4 -11
  57. package/lib/sanity/queries.ts +6 -29
  58. package/lib/sanity/types.ts +24 -70
  59. package/package.json +4 -1
  60. package/sanity/schemas/index.ts +0 -5
  61. package/sanity/schemas/objects/parallaxGroup.ts +2 -2
  62. package/sanity/schemas/page.ts +1 -1
  63. package/sanity/schemas/pageSectionV2.ts +1 -0
  64. package/sanity/schemas/siteSettings.ts +42 -0
  65. package/styles/base.css +8 -2
  66. package/components/blocks/SectionRenderer.tsx +0 -171
  67. package/components/builder/settings-panel/LayoutTab.tsx +0 -382
  68. package/sanity/schemas/pageSection.ts +0 -157
@@ -4,10 +4,9 @@
4
4
  // Each template defines a set of pre-built rows/columns/blocks
5
5
  // that get injected as content_rows when creating a new page.
6
6
  //
7
- // BUG-004 fix (Session 78): Templates now create first-class PageSection
8
- // objects for section blocks instead of the old matryoshka format.
7
+ // All templates use PageSectionV2 (V1 PageSection removed in cleanup).
9
8
 
10
- import type { ContentBlock, ContentItem, PageSection, PageSectionV2, SectionBlock, SectionColumn, ParallaxGroup, ParallaxSlideV2 } from "../../lib/sanity/types";
9
+ import type { ContentBlock, ContentItem, PageSectionV2, SectionColumn, ParallaxGroup, ParallaxSlideV2 } from "../../lib/sanity/types";
11
10
  import { generateKey } from "./utils";
12
11
 
13
12
  // ============================================
@@ -84,43 +83,20 @@ function makeV2Section(blocks: ContentBlock[], settings?: Partial<PageSectionV2[
84
83
  };
85
84
  }
86
85
 
87
- /**
88
- * Create a first-class PageSection with a section block.
89
- * BUG-004 fix: replaces the old pattern of wrapping section blocks in makeRow().
90
- */
91
- function makeSection(
92
- blockType: "projectGridBlock",
93
- blockDefaults: Record<string, unknown>,
94
- ): PageSection {
95
- const block = makeBlock({ _type: blockType, ...blockDefaults }) as SectionBlock;
96
- return {
97
- _type: "pageSection",
98
- _key: generateKey(),
99
- section_type: "projectGrid" as import("../../lib/sanity/types").PageSectionType,
100
- block: [block] as [SectionBlock],
101
- settings: {
102
- spacing_top: "32",
103
- spacing_bottom: "32",
104
- },
105
- };
106
- }
107
-
108
86
  // ============================================
109
- // Template: Project Grid (staggered project showcase)
87
+ // Template: Portfolio (content showcase)
110
88
  // ============================================
111
89
 
112
- const projectGridTemplate: PageTemplate = {
113
- id: "project-grid",
114
- label: "Project Grid",
115
- description: "Staggered project showcase with configurable layouts",
90
+ const portfolioTemplate: PageTemplate = {
91
+ id: "portfolio",
92
+ label: "Portfolio",
93
+ description: "Portfolio showcase with flexible layout sections",
116
94
  icon: "⬡",
117
95
  preview: [
118
96
  { type: "spacer", height: 0.5 },
119
97
  { type: "text", height: 0.5 },
120
98
  { type: "spacer", height: 0.3 },
121
- { type: "cover", height: 2 },
122
99
  { type: "columns", height: 1.5 },
123
- { type: "cover", height: 2 },
124
100
  { type: "columns", height: 1.5 },
125
101
  ],
126
102
  buildRows: () => [
@@ -136,19 +112,7 @@ const projectGridTemplate: PageTemplate = {
136
112
  ]),
137
113
  // Section 3: Spacer
138
114
  makeV2Section([makeBlock({ _type: "spacerBlock", height: "medium" })]),
139
- // Section 4: Project Grid (first-class PageSection)
140
- makeSection("projectGridBlock", {
141
- columns: 3,
142
- aspect_ratios: ["16/9"],
143
- gap_v: 16,
144
- gap_h: 16,
145
- hover_effect: "scale",
146
- show_subtitle: true,
147
- border_radius: 0,
148
- video_mode: "off",
149
- projects: [],
150
- }),
151
- // Section 5: Bottom spacer
115
+ // Section 4: Bottom spacer
152
116
  makeV2Section([makeBlock({ _type: "spacerBlock", height: "xlarge" })]),
153
117
  ],
154
118
  };
@@ -285,7 +249,7 @@ const parallaxHomeTemplate: PageTemplate = {
285
249
 
286
250
  export const PAGE_TEMPLATES: PageTemplate[] = [
287
251
  blankTemplate,
288
- projectGridTemplate,
252
+ portfolioTemplate,
289
253
  parallaxHomeTemplate,
290
254
  ];
291
255
 
@@ -9,9 +9,7 @@ import type {
9
9
  PageType,
10
10
  ContentBlock,
11
11
  ContentItem,
12
- PageSection,
13
12
  PageSectionV2,
14
- SectionSettings,
15
13
  SectionV2Settings,
16
14
  SectionV2Preset,
17
15
  SectionColumn,
@@ -178,7 +176,7 @@ export interface BuilderState {
178
176
  publishedAt: string | null;
179
177
  draftMode: boolean;
180
178
 
181
- // Content — page sections (V1 + V2)
179
+ // Content — V2 sections, custom sections, parallax groups
182
180
  rows: ContentItem[];
183
181
 
184
182
  // Selection
@@ -251,17 +249,12 @@ export interface BuilderActions {
251
249
  unpublishPage: () => void;
252
250
 
253
251
  // Section operations
252
+ /** Add a Project Grid section as a V2 section with a full-width column (Session 164) */
254
253
  addSection: (blockType: "projectGridBlock", afterRowKey?: string | null) => void;
255
254
  reorderRows: (fromIndex: number, toIndex: number) => void;
256
- /** Update settings for a PageSection (background, spacing, animation, etc.) */
257
- updateSectionSettings: (sectionKey: string, settings: Partial<SectionSettings>) => void;
258
- /** BUG-013 fix: Update responsive overrides for a PageSection */
259
- updateSectionResponsive: (sectionKey: string, responsive: PageSection["responsive"]) => void;
260
- /** Update the block content inside a PageSection */
261
- updateSectionBlock: (sectionKey: string, updates: Partial<import("../../lib/sanity/types").ContentBlock>) => void;
262
- /** Delete a section (PageSection or PageSectionV2) by key */
255
+ /** Delete a section by key */
263
256
  deleteSection: (sectionKey: string) => void;
264
- /** Duplicate a section (PageSection or PageSectionV2), inserting copy after the original */
257
+ /** Duplicate a section, inserting copy after the original */
265
258
  duplicateSection: (sectionKey: string) => void;
266
259
 
267
260
  // Block operations
@@ -4,45 +4,22 @@ import { groq } from "next-sanity";
4
4
  // BLOCK EXPANSION (shared fragment)
5
5
  // ============================================
6
6
 
7
- // Deep expansion of row → column → block content.
8
- // Handles Row objects (columns→blocks), PageSection objects (_type: "pageSection"),
9
- // PageSectionV2 objects (_type: "pageSectionV2"), CustomSectionInstance, and
10
- // ParallaxGroup objects (_type: "parallaxGroup") with nested slides.
11
- // GROQ uses conditional projections to handle all types.
7
+ // Deep expansion of content_rows.
8
+ // Handles PageSectionV2, CustomSectionInstance, and ParallaxGroup.
9
+ // GROQ projections are additive fields that don't exist on an object are omitted.
12
10
  const blockExpansion = `
13
11
  content_rows[] {
14
12
  _key,
15
13
  _type,
16
- // ── PageSection fields (only present when _type == "pageSection") ──
17
14
  section_type,
18
- block[] {
19
- _type,
20
- _key,
21
- ...,
22
- "project_ref": project_ref-> {
23
- _id,
24
- title,
25
- slug,
26
- page_type
27
- }
28
- },
29
- // ── Row fields (columns only present on row objects) ──
30
- // Note: V2 sections also have "columns" but with different fields.
31
- // GROQ projections are additive — fields that don't exist on an object are omitted.
32
- // We include BOTH Row column fields (width, settings, responsive) and V2 column
33
- // fields (grid_column, grid_row, span) so both types are fully hydrated.
15
+ // ── V2 section columns ──
34
16
  columns[] {
35
17
  _key,
36
18
  _type,
37
- // Row column fields
38
- width,
39
- settings,
40
- responsive,
41
- // V2 column fields (grid positions)
42
19
  grid_column,
43
20
  grid_row,
44
21
  span,
45
- // Blocks (shared by both Row columns and V2 columns)
22
+ enter_animation,
46
23
  blocks[] {
47
24
  _type,
48
25
  _key,
@@ -96,7 +73,7 @@ const blockExpansion = `
96
73
  }
97
74
  }
98
75
  },
99
- // ── Settings (shared by Row, PageSection, and PageSectionV2) ──
76
+ // ── Settings (V2 section settings) ──
100
77
  settings,
101
78
  // ── Responsive overrides (V2 sections store responsive at section level) ──
102
79
  responsive
@@ -346,68 +346,6 @@ export interface ParallaxGroup {
346
346
  parallax_intensity: number; // 0.2–0.6, default 0.4 (hidden from UI, hardcoded)
347
347
  }
348
348
 
349
- // ============================================
350
- // Page Section — first-class section type (Session 76)
351
- // ============================================
352
- // Replaces the old "matryoshka" approach where section blocks were
353
- // wrapped in row → column → block. Sections are direct entries in
354
- // content_rows alongside regular rows.
355
-
356
- export type PageSectionType = "projectGrid";
357
-
358
- export interface SectionSettings {
359
- // Background
360
- background_color?: string;
361
- background_opacity?: number;
362
- background_image?: string;
363
- background_size?: "cover" | "contain" | "auto";
364
- background_position?: string;
365
- background_repeat?: "no-repeat" | "repeat" | "repeat-x" | "repeat-y";
366
- // Spacing (padding TRBL)
367
- spacing_top?: string;
368
- spacing_right?: string;
369
- spacing_bottom?: string;
370
- spacing_left?: string;
371
- // Offset (margin TRBL)
372
- offset_top?: string;
373
- offset_right?: string;
374
- offset_bottom?: string;
375
- offset_left?: string;
376
- // Border
377
- border_color?: string;
378
- border_width?: string;
379
- border_style?: "none" | "solid" | "dashed" | "dotted";
380
- border_sides?: "all" | "top" | "right" | "bottom" | "left" | "top-bottom" | "left-right";
381
- border_radius?: string;
382
- // Animation
383
- enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
384
- }
385
-
386
- /** Section block types that can appear inside a PageSection */
387
- export type SectionBlock = ProjectGridBlock;
388
-
389
- /** Section settings that can be overridden per-viewport */
390
- export type SectionSettingsOverridable = Pick<SectionSettings,
391
- "background_color" | "background_opacity" | "background_image" | "background_size" | "background_position" | "background_repeat" |
392
- "spacing_top" | "spacing_right" | "spacing_bottom" | "spacing_left" |
393
- "offset_top" | "offset_right" | "offset_bottom" | "offset_left" |
394
- "border_color" | "border_width" | "border_style" | "border_sides" | "border_radius"
395
- >;
396
-
397
- export interface PageSection {
398
- _type: "pageSection";
399
- _key: string;
400
- section_type: PageSectionType;
401
- /** The actual section content — single block */
402
- block: [SectionBlock];
403
- settings?: SectionSettings;
404
- /** BUG-013 fix: Per-viewport responsive overrides for section settings */
405
- responsive?: {
406
- tablet?: Partial<SectionSettingsOverridable>;
407
- phone?: Partial<SectionSettingsOverridable>;
408
- };
409
- }
410
-
411
349
  // ============================================
412
350
  // Page Section V2 — V2 Grid System (Session 83)
413
351
  // ============================================
@@ -540,16 +478,10 @@ export interface CustomSectionInstance {
540
478
  }
541
479
 
542
480
  // ============================================
543
- // Content Item — union of PageSection, PageSectionV2, and CustomSectionInstance
481
+ // Content Item — union of section types in content_rows
544
482
  // ============================================
545
- // Used in content_rows and the builder store.
546
483
 
547
- export type ContentItem = PageSection | PageSectionV2 | CustomSectionInstance | ParallaxGroup;
548
-
549
- /** Type guard: check if a content item is a PageSection (V1) */
550
- export function isPageSection(item: ContentItem): item is PageSection {
551
- return (item as PageSection)._type === "pageSection";
552
- }
484
+ export type ContentItem = PageSectionV2 | CustomSectionInstance | ParallaxGroup;
553
485
 
554
486
  /** Type guard: check if a content item is a PageSectionV2 */
555
487
  export function isPageSectionV2(item: ContentItem): item is PageSectionV2 {
@@ -704,6 +636,28 @@ export interface NavDesign {
704
636
  entrance_delay?: number; // ms, default 0
705
637
  entrance_stagger?: boolean; // stagger items, default false
706
638
  entrance_stagger_delay?: number; // ms between items, default 80
639
+
640
+ /** Per-viewport overrides for responsive nav settings.
641
+ * Only typography + spacing fields are overridable.
642
+ * Missing fields = inherit from desktop. */
643
+ responsive?: {
644
+ tablet?: NavDesignResponsiveOverride;
645
+ phone?: NavDesignResponsiveOverride;
646
+ };
647
+ }
648
+
649
+ /** Subset of NavDesign fields that can be overridden per viewport */
650
+ export interface NavDesignResponsiveOverride {
651
+ font_size?: number;
652
+ font_weight?: string;
653
+ text_align?: "left" | "center" | "right";
654
+ vertical_align?: "top" | "middle" | "bottom";
655
+ text_transform?: "none" | "uppercase" | "lowercase" | "capitalize";
656
+ padding_h?: number;
657
+ padding_v?: number;
658
+ margin_h?: number;
659
+ margin_v?: number;
660
+ items_gap?: number;
707
661
  }
708
662
 
709
663
  // ============================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morphika/andami",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
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",
@@ -17,6 +17,9 @@
17
17
  "page-builder",
18
18
  "morphika"
19
19
  ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
20
23
  "files": [
21
24
  "admin/",
22
25
  "app/",
@@ -1,5 +1,4 @@
1
1
  import page from "./page";
2
- import pageSection from "./pageSection";
3
2
  import pageSectionV2 from "./pageSectionV2";
4
3
  import customSection from "./customSection";
5
4
  import customSectionInstance from "./customSectionInstance";
@@ -35,9 +34,6 @@ export {
35
34
  export {
36
35
  default as assetRegistry,
37
36
  } from "./assetRegistry";
38
- export {
39
- default as pageSection,
40
- } from "./pageSection";
41
37
  export {
42
38
  default as pageSectionV2,
43
39
  } from "./pageSectionV2";
@@ -81,7 +77,6 @@ export const schemaTypes = [
81
77
  assetRegistry,
82
78
 
83
79
  // Structural objects
84
- pageSection,
85
80
  pageSectionV2,
86
81
  customSection,
87
82
  customSectionInstance,
@@ -3,8 +3,8 @@ import { defineField, defineType } from "sanity";
3
3
  /**
4
4
  * parallaxGroup — A group of full-screen parallax slides.
5
5
  *
6
- * Lives inline in a page's content_rows alongside PageSection,
7
- * PageSectionV2, and CustomSectionInstance. Each slide is a
6
+ * Lives inline in a page's content_rows alongside PageSectionV2
7
+ * and CustomSectionInstance. Each slide is a
8
8
  * full V2 section with background image/video and 100vh height.
9
9
  *
10
10
  * Session 123: Parallax V2 rewrite.
@@ -42,7 +42,7 @@ export default defineType({
42
42
  name: "content_rows",
43
43
  title: "Content Rows",
44
44
  type: "array",
45
- of: [{ type: "pageSection" }, { type: "pageSectionV2" }, { type: "parallaxGroup" }, { type: "customSectionInstance" }],
45
+ of: [{ type: "pageSectionV2" }, { type: "parallaxGroup" }, { type: "customSectionInstance" }],
46
46
  }),
47
47
  defineField({
48
48
  name: "metadata",
@@ -67,6 +67,7 @@ export default defineType({
67
67
  { type: "spacerBlock" },
68
68
  { type: "buttonBlock" },
69
69
  { type: "coverBlock" },
70
+ { type: "projectGridBlock" },
70
71
  ],
71
72
  }),
72
73
  // Column-level enter animation (Session 117)
@@ -210,6 +210,48 @@ export default defineType({
210
210
  defineField({ name: "entrance_delay", title: "Entrance Delay (ms)", type: "number", description: "Delay before animation starts. Default: 0" }),
211
211
  defineField({ name: "entrance_stagger", title: "Stagger Items", type: "boolean", description: "Animate items one by one", initialValue: false }),
212
212
  defineField({ name: "entrance_stagger_delay", title: "Stagger Delay (ms)", type: "number", description: "Delay between items. Default: 80" }),
213
+ // ── Responsive overrides (tablet / phone) ──
214
+ defineField({
215
+ name: "responsive",
216
+ title: "Responsive Overrides",
217
+ type: "object",
218
+ fields: [
219
+ defineField({
220
+ name: "tablet",
221
+ title: "Tablet Overrides",
222
+ type: "object",
223
+ fields: [
224
+ defineField({ name: "font_size", title: "Font Size", type: "number" }),
225
+ defineField({ name: "font_weight", title: "Font Weight", type: "string" }),
226
+ defineField({ name: "text_align", title: "Text Align", type: "string" }),
227
+ defineField({ name: "vertical_align", title: "Vertical Align", type: "string" }),
228
+ defineField({ name: "text_transform", title: "Text Transform", type: "string" }),
229
+ defineField({ name: "padding_h", title: "Padding H", type: "number" }),
230
+ defineField({ name: "padding_v", title: "Padding V", type: "number" }),
231
+ defineField({ name: "margin_h", title: "Margin H", type: "number" }),
232
+ defineField({ name: "margin_v", title: "Margin V", type: "number" }),
233
+ defineField({ name: "items_gap", title: "Items Gap", type: "number" }),
234
+ ],
235
+ }),
236
+ defineField({
237
+ name: "phone",
238
+ title: "Phone Overrides",
239
+ type: "object",
240
+ fields: [
241
+ defineField({ name: "font_size", title: "Font Size", type: "number" }),
242
+ defineField({ name: "font_weight", title: "Font Weight", type: "string" }),
243
+ defineField({ name: "text_align", title: "Text Align", type: "string" }),
244
+ defineField({ name: "vertical_align", title: "Vertical Align", type: "string" }),
245
+ defineField({ name: "text_transform", title: "Text Transform", type: "string" }),
246
+ defineField({ name: "padding_h", title: "Padding H", type: "number" }),
247
+ defineField({ name: "padding_v", title: "Padding V", type: "number" }),
248
+ defineField({ name: "margin_h", title: "Margin H", type: "number" }),
249
+ defineField({ name: "margin_v", title: "Margin V", type: "number" }),
250
+ defineField({ name: "items_gap", title: "Items Gap", type: "number" }),
251
+ ],
252
+ }),
253
+ ],
254
+ }),
213
255
  ],
214
256
  }),
215
257
 
package/styles/base.css CHANGED
@@ -57,7 +57,13 @@ body {
57
57
  overflow-x: clip;
58
58
  }
59
59
 
60
- /* Prevent text overflow in grid columns on narrow viewports */
60
+ /* Prevent text overflow in grid columns on narrow viewports.
61
+ * overflow-wrap: break-word — breaks mid-word ONLY when the entire word
62
+ * cannot fit on a fresh line. Does NOT affect min-content intrinsic sizing,
63
+ * so CSS Grid columns keep their proper width (unlike 'anywhere' which
64
+ * collapses min-content to a single character width).
65
+ * word-break: normal — wraps at natural word boundaries first, preventing
66
+ * ugly mid-word splits like "Collecti" + "on". */
61
67
  [data-site] p,
62
68
  [data-site] h1,
63
69
  [data-site] h2,
@@ -65,7 +71,7 @@ body {
65
71
  [data-site] h4,
66
72
  [data-site] span {
67
73
  overflow-wrap: break-word;
68
- word-break: break-word;
74
+ word-break: normal;
69
75
  }
70
76
 
71
77
  [data-custom-cursor] {
@@ -1,171 +0,0 @@
1
- "use client";
2
-
3
- /**
4
- * SectionRenderer — renders a first-class PageSection on the public site.
5
- *
6
- * Unlike V2 sections (which have columns → blocks), SectionRenderer
7
- * renders the section block directly — no row/column wrapper needed.
8
- *
9
- * Session 76: Created as part of the matryoshka → first-class section refactor.
10
- * Session 120: Updated for new enter animation system.
11
- */
12
-
13
- import type { PageSection, SectionBlock } from "../../lib/sanity/types";
14
- import type { EnterAnimationConfig } from "../../lib/animation/enter-types";
15
- import { resolveEnterAnimation } from "../../lib/animation/enter-resolve";
16
- import BlockRenderer from "./BlockRenderer";
17
- import EnterAnimationWrapper from "./EnterAnimationWrapper";
18
- import { getRowLayoutStyles, hexToRgba } from "../../lib/builder/layout-styles";
19
- import { colorToOverrideRule, borderColorToOverrideRule, parseColorField } from "../../lib/color-utils";
20
- import type { ColorField } from "../../lib/sanity/types";
21
- import { BREAKPOINTS } from "../../lib/builder/constants";
22
-
23
- /**
24
- * BUG-013 fix: Build responsive CSS overrides for section layout settings.
25
- * Same approach as RowRenderer's buildRowResponsiveCss.
26
- */
27
- function buildSectionResponsiveCss(section: PageSection): string | null {
28
- const responsive = section.responsive;
29
- if (!responsive) return null;
30
-
31
- const key = section._key;
32
- const cssRules: string[] = [];
33
-
34
- for (const [vp, breakpoint] of [["tablet", BREAKPOINTS.tablet], ["phone", BREAKPOINTS.phone]] as const) {
35
- const overrides = responsive[vp];
36
- if (!overrides) continue;
37
- const rules: string[] = [];
38
-
39
- // px-value properties
40
- const pxMap: Record<string, string> = {
41
- spacing_top: "padding-top", spacing_right: "padding-right",
42
- spacing_bottom: "padding-bottom", spacing_left: "padding-left",
43
- offset_top: "margin-top", offset_right: "margin-right",
44
- offset_bottom: "margin-bottom", offset_left: "margin-left",
45
- border_radius: "border-radius",
46
- };
47
-
48
- for (const [field, cssProp] of Object.entries(pxMap)) {
49
- const val = overrides[field as keyof typeof overrides];
50
- if (val !== undefined && val !== null && val !== "") {
51
- rules.push(`${cssProp}:${val}px!important`);
52
- }
53
- }
54
-
55
- // Border width — side-aware
56
- if (overrides.border_width !== undefined && overrides.border_width !== null && overrides.border_width !== "") {
57
- const bw = overrides.border_width;
58
- const sides = (overrides.border_sides as string) || "all";
59
- switch (sides) {
60
- case "top":
61
- rules.push(`border-top-width:${bw}px!important`);
62
- break;
63
- case "right":
64
- rules.push(`border-right-width:${bw}px!important`);
65
- break;
66
- case "bottom":
67
- rules.push(`border-bottom-width:${bw}px!important`);
68
- break;
69
- case "left":
70
- rules.push(`border-left-width:${bw}px!important`);
71
- break;
72
- case "top-bottom":
73
- rules.push(`border-top-width:${bw}px!important`);
74
- rules.push(`border-bottom-width:${bw}px!important`);
75
- break;
76
- case "left-right":
77
- rules.push(`border-left-width:${bw}px!important`);
78
- rules.push(`border-right-width:${bw}px!important`);
79
- break;
80
- default:
81
- rules.push(`border-width:${bw}px!important`);
82
- break;
83
- }
84
- }
85
-
86
- // Border color (supports solid + gradients via ColorField bridge)
87
- if (overrides.border_color) {
88
- rules.push(borderColorToOverrideRule(parseColorField(overrides.border_color)));
89
- }
90
-
91
- // Border style
92
- if (overrides.border_style && overrides.border_style !== "none") {
93
- rules.push(`border-style:${overrides.border_style}!important`);
94
- }
95
-
96
- // Background color + opacity (gradient-safe via ColorField bridge)
97
- if (overrides.background_color) {
98
- const opacity = overrides.background_opacity as number | undefined;
99
- rules.push(colorToOverrideRule(
100
- parseColorField(overrides.background_color),
101
- opacity
102
- ));
103
- }
104
-
105
- // Background image + sub-properties
106
- if (overrides.background_image) {
107
- const imgUrl = process.env.NEXT_PUBLIC_ASSET_BASE_URL
108
- ? `${(process.env.NEXT_PUBLIC_ASSET_BASE_URL as string).replace(/\/$/, "")}/${overrides.background_image}`
109
- : overrides.background_image;
110
- rules.push(`background-image:url(${imgUrl})!important`);
111
- rules.push(`background-size:${overrides.background_size || "cover"}!important`);
112
- rules.push(`background-position:${overrides.background_position || "center center"}!important`);
113
- rules.push(`background-repeat:${overrides.background_repeat || "no-repeat"}!important`);
114
- } else {
115
- if (overrides.background_size) rules.push(`background-size:${overrides.background_size}!important`);
116
- if (overrides.background_position) rules.push(`background-position:${overrides.background_position}!important`);
117
- if (overrides.background_repeat) rules.push(`background-repeat:${overrides.background_repeat}!important`);
118
- }
119
-
120
- if (rules.length > 0) {
121
- cssRules.push(`@media(max-width:${breakpoint}px){.section-${key}{${rules.join(";")}}}`);
122
- }
123
- }
124
-
125
- return cssRules.length > 0 ? cssRules.join("") : null;
126
- }
127
-
128
- interface SectionRendererProps {
129
- section: PageSection;
130
- /** Page-level enter animation config (from page_settings.enter_animation) */
131
- pageEnterAnimation?: EnterAnimationConfig;
132
- }
133
-
134
- export default function SectionRenderer({ section, pageEnterAnimation }: SectionRendererProps) {
135
- const s = section.settings ?? {};
136
-
137
- // Get the section block
138
- const block = Array.isArray(section.block) ? section.block[0] : undefined;
139
- if (!block) return null;
140
-
141
- // Resolve enter animation (section settings → page default → none)
142
- const sectionEnterConfig = s.enter_animation;
143
- const resolvedEnter = resolveEnterAnimation(undefined, undefined, sectionEnterConfig, pageEnterAnimation);
144
- const hasAnimation = resolvedEnter !== null && resolvedEnter.preset !== "none";
145
-
146
- // Section layout styles (background, spacing, border, etc.)
147
- const layoutStyles = getRowLayoutStyles(s as Record<string, unknown>);
148
-
149
- // BUG-013 fix: build responsive CSS overrides for section
150
- const responsiveCss = buildSectionResponsiveCss(section);
151
-
152
- // Render the section block directly — no columns, no grid
153
- // V1 PageSections contain section blocks (ProjectGrid, Parallax) which
154
- // don't use the block-level enter/hover cascade — they have their own systems.
155
- let content: React.ReactNode = (
156
- <section className={`section-${section._key}`} style={layoutStyles}>
157
- {responsiveCss && <style dangerouslySetInnerHTML={{ __html: responsiveCss }} />}
158
- <BlockRenderer block={block} pageEnterAnimation={pageEnterAnimation} sectionEnterAnimation={sectionEnterConfig} />
159
- </section>
160
- );
161
-
162
- if (hasAnimation && resolvedEnter) {
163
- content = (
164
- <EnterAnimationWrapper config={resolvedEnter}>
165
- {content}
166
- </EnterAnimationWrapper>
167
- );
168
- }
169
-
170
- return content;
171
- }