@morphika/webframe 0.1.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.
- package/LICENSE +21 -0
- package/README.md +46 -0
- package/admin/assets.ts +4 -0
- package/admin/database.ts +4 -0
- package/admin/index.ts +6 -0
- package/admin/login.ts +4 -0
- package/admin/navigation.ts +4 -0
- package/admin/pages-editor.ts +4 -0
- package/admin/pages.ts +4 -0
- package/admin/projects-editor.ts +4 -0
- package/admin/projects.ts +4 -0
- package/admin/settings.ts +4 -0
- package/admin/setup.ts +4 -0
- package/admin/storage.ts +4 -0
- package/admin/styles.ts +4 -0
- package/app/(site)/[slug]/loading.tsx +20 -0
- package/app/(site)/[slug]/page.tsx +83 -0
- package/app/(site)/error.tsx +32 -0
- package/app/(site)/layout.tsx +53 -0
- package/app/(site)/loading.tsx +20 -0
- package/app/(site)/not-found.tsx +41 -0
- package/app/(site)/page.tsx +43 -0
- package/app/(site)/preview/page.tsx +99 -0
- package/app/(site)/work/[slug]/loading.tsx +23 -0
- package/app/(site)/work/[slug]/page.tsx +84 -0
- package/app/admin/assets/page.tsx +573 -0
- package/app/admin/database/page.tsx +302 -0
- package/app/admin/error.tsx +53 -0
- package/app/admin/layout.tsx +273 -0
- package/app/admin/login/page.tsx +88 -0
- package/app/admin/navigation/page.tsx +157 -0
- package/app/admin/page.tsx +17 -0
- package/app/admin/pages/[slug]/page.tsx +849 -0
- package/app/admin/pages/page.tsx +588 -0
- package/app/admin/projects/[slug]/page.tsx +3 -0
- package/app/admin/projects/page.tsx +669 -0
- package/app/admin/settings/page.tsx +132 -0
- package/app/admin/setup/page.tsx +64 -0
- package/app/admin/storage/page.tsx +518 -0
- package/app/admin/styles/page.tsx +243 -0
- package/app/api/admin/assets/file/route.ts +81 -0
- package/app/api/admin/assets/health/route.ts +170 -0
- package/app/api/admin/assets/register/route.ts +163 -0
- package/app/api/admin/assets/registry/route.ts +98 -0
- package/app/api/admin/assets/relink/confirm/route.ts +242 -0
- package/app/api/admin/assets/relink/route.ts +202 -0
- package/app/api/admin/assets/scan/route.ts +271 -0
- package/app/api/admin/auth/route.ts +160 -0
- package/app/api/admin/custom-sections/[slug]/route.ts +159 -0
- package/app/api/admin/custom-sections/route.ts +127 -0
- package/app/api/admin/database/route.ts +53 -0
- package/app/api/admin/pages/[slug]/duplicate/route.ts +91 -0
- package/app/api/admin/pages/[slug]/route.ts +617 -0
- package/app/api/admin/pages/[slug]/set-home/route.ts +76 -0
- package/app/api/admin/pages/route.ts +129 -0
- package/app/api/admin/preview/route.ts +53 -0
- package/app/api/admin/r2/connect/route.ts +181 -0
- package/app/api/admin/r2/delete/route.ts +198 -0
- package/app/api/admin/r2/disconnect/route.ts +42 -0
- package/app/api/admin/r2/rename/route.ts +265 -0
- package/app/api/admin/r2/status/route.ts +106 -0
- package/app/api/admin/r2/upload-url/route.ts +148 -0
- package/app/api/admin/revalidate/route.ts +55 -0
- package/app/api/admin/settings/route.ts +279 -0
- package/app/api/admin/setup/complete/route.ts +51 -0
- package/app/api/admin/setup/route.ts +118 -0
- package/app/api/admin/storage/switch/route.ts +117 -0
- package/app/api/admin/styles/fonts/route.ts +97 -0
- package/app/api/admin/styles/route.ts +304 -0
- package/app/api/assets/[...path]/route.ts +98 -0
- package/app/api/custom-sections/[id]/route.ts +43 -0
- package/app/api/draft-mode/disable/route.ts +10 -0
- package/app/api/draft-mode/enable/route.ts +26 -0
- package/app/api/projects/route.ts +42 -0
- package/app/api/styles/route.ts +88 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +7 -0
- package/app/layout.tsx +53 -0
- package/app/robots.ts +17 -0
- package/app/sitemap.ts +48 -0
- package/app/studio/[[...index]]/page.tsx +8 -0
- package/components/admin/MetadataEditor.tsx +173 -0
- package/components/admin/PublishToggle.tsx +130 -0
- package/components/admin/icons.tsx +40 -0
- package/components/admin/nav-builder/NavBuilder.tsx +182 -0
- package/components/admin/nav-builder/NavBuilderGrid.tsx +326 -0
- package/components/admin/nav-builder/NavGeneralSettings.tsx +275 -0
- package/components/admin/nav-builder/NavGridCell.tsx +48 -0
- package/components/admin/nav-builder/NavGridItem.tsx +189 -0
- package/components/admin/nav-builder/NavItemSettings.tsx +288 -0
- package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -0
- package/components/admin/nav-builder/NavLivePreview.tsx +125 -0
- package/components/admin/nav-builder/NavSettingsFields.tsx +248 -0
- package/components/admin/nav-builder/NavSettingsPanel.tsx +127 -0
- package/components/admin/nav-builder/index.ts +10 -0
- package/components/admin/nav-builder/nav-builder-utils.ts +238 -0
- package/components/admin/setup-wizard/BrandingStep.tsx +218 -0
- package/components/admin/setup-wizard/DatabaseStep.tsx +331 -0
- package/components/admin/setup-wizard/DoneStep.tsx +187 -0
- package/components/admin/setup-wizard/SetupWizard.tsx +166 -0
- package/components/admin/setup-wizard/StorageStep.tsx +308 -0
- package/components/admin/setup-wizard/WelcomeStep.tsx +96 -0
- package/components/admin/setup-wizard/index.ts +9 -0
- package/components/admin/styles/ColorsEditor.tsx +214 -0
- package/components/admin/styles/FontsEditor.tsx +258 -0
- package/components/admin/styles/GridLayoutEditor.tsx +292 -0
- package/components/admin/styles/LinksButtonsEditor.tsx +120 -0
- package/components/admin/styles/TypographyEditor.tsx +266 -0
- package/components/admin/styles/index.ts +9 -0
- package/components/admin/styles/shared.tsx +68 -0
- package/components/blocks/BlockRenderer.tsx +404 -0
- package/components/blocks/ButtonBlockRenderer.tsx +52 -0
- package/components/blocks/CoverBlockRenderer.tsx +239 -0
- package/components/blocks/CustomSectionInstanceRenderer.tsx +82 -0
- package/components/blocks/EnterAnimationWrapper.tsx +140 -0
- package/components/blocks/HoverAnimationWrapper.tsx +308 -0
- package/components/blocks/ImageBlockRenderer.tsx +61 -0
- package/components/blocks/ImageGridBlockRenderer.tsx +545 -0
- package/components/blocks/PageBackground.tsx +28 -0
- package/components/blocks/PageNavAnimation.tsx +35 -0
- package/components/blocks/PageNavColor.tsx +24 -0
- package/components/blocks/PageRenderer.tsx +142 -0
- package/components/blocks/ParallaxGroupRenderer.tsx +448 -0
- package/components/blocks/ParallaxSlideRenderer.tsx +175 -0
- package/components/blocks/ProjectGridBlockRenderer.tsx +556 -0
- package/components/blocks/SectionRenderer.tsx +170 -0
- package/components/blocks/SectionV2Renderer.tsx +330 -0
- package/components/blocks/ShaderCanvas.tsx +392 -0
- package/components/blocks/SpacerBlockRenderer.tsx +17 -0
- package/components/blocks/TextBlockRenderer.tsx +87 -0
- package/components/blocks/TypewriterRichText.tsx +464 -0
- package/components/blocks/TypewriterWrapper.tsx +149 -0
- package/components/blocks/VideoBlockRenderer.tsx +304 -0
- package/components/blocks/index.ts +2 -0
- package/components/builder/AssetBrowser.tsx +2 -0
- package/components/builder/BlockLivePreview.tsx +101 -0
- package/components/builder/BlockTypePicker.tsx +178 -0
- package/components/builder/BuilderCanvas.tsx +354 -0
- package/components/builder/CanvasMinimap.tsx +200 -0
- package/components/builder/CanvasToolbar.tsx +202 -0
- package/components/builder/ColorPicker.tsx +243 -0
- package/components/builder/ColorSwatchPicker.tsx +274 -0
- package/components/builder/ColumnDragContext.tsx +51 -0
- package/components/builder/ColumnDragOverlay.tsx +110 -0
- package/components/builder/CustomSectionInstanceCard.tsx +97 -0
- package/components/builder/DeviceFrame.tsx +123 -0
- package/components/builder/DndWrapper.tsx +337 -0
- package/components/builder/InsertionLines.tsx +186 -0
- package/components/builder/ParallaxGroupCanvas.tsx +228 -0
- package/components/builder/ParallaxSlideHeader.tsx +113 -0
- package/components/builder/ReadOnlyFrame.tsx +417 -0
- package/components/builder/SectionEditorBar.tsx +288 -0
- package/components/builder/SectionTypePicker.tsx +422 -0
- package/components/builder/SectionV2Canvas.tsx +297 -0
- package/components/builder/SectionV2Column.tsx +488 -0
- package/components/builder/SettingsPanel.tsx +911 -0
- package/components/builder/SortableBlock.tsx +230 -0
- package/components/builder/SortableRow.tsx +362 -0
- package/components/builder/VirtualAssetGrid.tsx +397 -0
- package/components/builder/asset-browser/AssetBrowser.tsx +178 -0
- package/components/builder/asset-browser/FileLightbox.tsx +116 -0
- package/components/builder/asset-browser/FolderTreeItem.tsx +55 -0
- package/components/builder/asset-browser/R2BrowserContent.tsx +436 -0
- package/components/builder/asset-browser/R2ContextMenu.tsx +98 -0
- package/components/builder/asset-browser/VideoThumbnail.tsx +63 -0
- package/components/builder/asset-browser/helpers.ts +88 -0
- package/components/builder/asset-browser/index.ts +1 -0
- package/components/builder/asset-browser/types.ts +49 -0
- package/components/builder/asset-browser/useAssetBrowser.ts +344 -0
- package/components/builder/asset-browser/useR2DragDrop.ts +116 -0
- package/components/builder/asset-browser/useR2Operations.ts +189 -0
- package/components/builder/blockStyles.tsx +295 -0
- package/components/builder/editors/ButtonBlockEditor.tsx +184 -0
- package/components/builder/editors/CoverBlockEditor.tsx +488 -0
- package/components/builder/editors/EnterAnimationPicker.tsx +297 -0
- package/components/builder/editors/HoverEffectPicker.tsx +209 -0
- package/components/builder/editors/ImageBlockEditor.tsx +206 -0
- package/components/builder/editors/ImageGridBlockEditor.tsx +386 -0
- package/components/builder/editors/ProjectGridEditor.tsx +648 -0
- package/components/builder/editors/SpacerBlockEditor.tsx +167 -0
- package/components/builder/editors/StaggerSettings.tsx +108 -0
- package/components/builder/editors/TextAlignmentIcons.tsx +39 -0
- package/components/builder/editors/TextBlockEditor.tsx +462 -0
- package/components/builder/editors/TextStylePicker.tsx +183 -0
- package/components/builder/editors/VideoBlockEditor.tsx +278 -0
- package/components/builder/editors/index.ts +10 -0
- package/components/builder/editors/shared.tsx +345 -0
- package/components/builder/hooks/useColumnDrag.ts +472 -0
- package/components/builder/hooks/useColumnResize.ts +221 -0
- package/components/builder/index.ts +12 -0
- package/components/builder/live-preview/LiveButtonPreview.tsx +38 -0
- package/components/builder/live-preview/LiveCoverPreview.tsx +146 -0
- package/components/builder/live-preview/LiveImageGridPreview.tsx +123 -0
- package/components/builder/live-preview/LiveImagePreview.tsx +107 -0
- package/components/builder/live-preview/LiveProjectGridPreview.tsx +1010 -0
- package/components/builder/live-preview/LiveSpacerPreview.tsx +9 -0
- package/components/builder/live-preview/LiveTextEditor.tsx +198 -0
- package/components/builder/live-preview/LiveVideoPreview.tsx +98 -0
- package/components/builder/live-preview/index.ts +10 -0
- package/components/builder/live-preview/shared.tsx +153 -0
- package/components/builder/settings-panel/BlockLayoutTab.tsx +532 -0
- package/components/builder/settings-panel/BlockSettings.tsx +94 -0
- package/components/builder/settings-panel/ColumnV2Settings.tsx +160 -0
- package/components/builder/settings-panel/LayoutTab.tsx +310 -0
- package/components/builder/settings-panel/PageSettings.tsx +200 -0
- package/components/builder/settings-panel/ParallaxGroupSettings.tsx +118 -0
- package/components/builder/settings-panel/ParallaxSlideSettings.tsx +178 -0
- package/components/builder/settings-panel/SectionV2AnimationTab.tsx +103 -0
- package/components/builder/settings-panel/SectionV2LayoutTab.tsx +312 -0
- package/components/builder/settings-panel/SectionV2Settings.tsx +323 -0
- package/components/builder/settings-panel/TRBLInputs.tsx +51 -0
- package/components/builder/settings-panel/index.ts +19 -0
- package/components/builder/settings-panel/responsive-helpers.ts +524 -0
- package/components/ui/CustomCursor.tsx +118 -0
- package/components/ui/NavContentLightbox.tsx +152 -0
- package/components/ui/Navbar.tsx +582 -0
- package/components/ui/PortfolioTracker.tsx +87 -0
- package/components/ui/ScrollToTop.tsx +47 -0
- package/lib/animation/enter-presets.ts +147 -0
- package/lib/animation/enter-resolve.ts +90 -0
- package/lib/animation/enter-types.ts +128 -0
- package/lib/animation/hover-effect-presets.ts +210 -0
- package/lib/animation/hover-effect-types.ts +126 -0
- package/lib/asset-retry.ts +111 -0
- package/lib/assets.ts +92 -0
- package/lib/audit.ts +35 -0
- package/lib/auth-token.ts +94 -0
- package/lib/auth.ts +13 -0
- package/lib/builder/cascade-helpers.ts +51 -0
- package/lib/builder/cascade.ts +533 -0
- package/lib/builder/constants.ts +103 -0
- package/lib/builder/defaults.ts +182 -0
- package/lib/builder/history.ts +48 -0
- package/lib/builder/index.ts +21 -0
- package/lib/builder/layout-styles.ts +344 -0
- package/lib/builder/masonry.ts +166 -0
- package/lib/builder/responsive.ts +156 -0
- package/lib/builder/serializer.ts +845 -0
- package/lib/builder/store-blocks.ts +193 -0
- package/lib/builder/store-canvas.ts +319 -0
- package/lib/builder/store-helpers.ts +490 -0
- package/lib/builder/store-sections.ts +709 -0
- package/lib/builder/store.ts +333 -0
- package/lib/builder/templates.ts +297 -0
- package/lib/builder/types.ts +374 -0
- package/lib/builder/utils.ts +37 -0
- package/lib/color-utils.ts +116 -0
- package/lib/config/index.ts +57 -0
- package/lib/config/types.ts +122 -0
- package/lib/contexts/AssetContext.tsx +79 -0
- package/lib/contexts/NavAnimationContext.tsx +44 -0
- package/lib/contexts/NavColorContext.tsx +38 -0
- package/lib/contexts/PageExitContext.tsx +194 -0
- package/lib/contexts/ThumbStatusContext.tsx +83 -0
- package/lib/csrf-client.ts +34 -0
- package/lib/csrf.ts +68 -0
- package/lib/format-utils.ts +24 -0
- package/lib/hooks/useViewport.ts +42 -0
- package/lib/logger.ts +81 -0
- package/lib/revalidate.ts +23 -0
- package/lib/sanitize.ts +91 -0
- package/lib/sanity/client.ts +8 -0
- package/lib/sanity/queries.ts +486 -0
- package/lib/sanity/types.ts +869 -0
- package/lib/sanity/writeClient.ts +24 -0
- package/lib/security.ts +402 -0
- package/lib/setup/detect.ts +156 -0
- package/lib/shader/glsl/index.ts +27 -0
- package/lib/shader/glsl/pixelate.ts +51 -0
- package/lib/shader/glsl/rgb-shift.ts +45 -0
- package/lib/shader/glsl/ripple.ts +46 -0
- package/lib/shader/glsl/vertex.ts +14 -0
- package/lib/storage/index.ts +211 -0
- package/lib/storage/r2-adapter.ts +286 -0
- package/lib/storage/types.ts +125 -0
- package/lib/styles/provider.tsx +267 -0
- package/lib/thumbnails/generate.ts +151 -0
- package/lib/utils.ts +6 -0
- package/package.json +212 -0
- package/sanity/compose.ts +65 -0
- package/sanity/sanity.config.ts +126 -0
- package/sanity/schemas/assetRegistry.ts +301 -0
- package/sanity/schemas/blocks/blockLayout.ts +90 -0
- package/sanity/schemas/blocks/buttonBlock.ts +82 -0
- package/sanity/schemas/blocks/coverBlock.ts +229 -0
- package/sanity/schemas/blocks/imageBlock.ts +58 -0
- package/sanity/schemas/blocks/imageGridBlock.ts +112 -0
- package/sanity/schemas/blocks/index.ts +9 -0
- package/sanity/schemas/blocks/projectGridBlock.ts +251 -0
- package/sanity/schemas/blocks/spacerBlock.ts +41 -0
- package/sanity/schemas/blocks/textBlock.ts +139 -0
- package/sanity/schemas/blocks/videoBlock.ts +80 -0
- package/sanity/schemas/customSection.ts +69 -0
- package/sanity/schemas/customSectionInstance.ts +163 -0
- package/sanity/schemas/index.ts +111 -0
- package/sanity/schemas/objects/enterAnimationConfig.ts +72 -0
- package/sanity/schemas/objects/hoverEffectConfig.ts +90 -0
- package/sanity/schemas/objects/parallaxGroup.ts +66 -0
- package/sanity/schemas/objects/parallaxSlide.ts +217 -0
- package/sanity/schemas/objects/typewriterConfig.ts +38 -0
- package/sanity/schemas/page.ts +162 -0
- package/sanity/schemas/pageSection.ts +157 -0
- package/sanity/schemas/pageSectionV2.ts +269 -0
- package/sanity/schemas/siteSettings.ts +256 -0
- package/sanity/schemas/siteStyles.ts +210 -0
- package/site/error.ts +4 -0
- package/site/index.ts +8 -0
- package/site/not-found.ts +4 -0
- package/site/page.ts +4 -0
- package/site/preview.ts +4 -0
- package/site/robots.ts +4 -0
- package/site/sitemap.ts +4 -0
- package/site/work.ts +4 -0
- package/studio/index.ts +4 -0
- package/styles/admin.css +85 -0
- package/styles/animations.css +237 -0
- package/styles/base.css +148 -0
- package/styles/globals.css +10 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,170 @@
|
|
|
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 { BREAKPOINTS } from "../../lib/builder/constants";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* BUG-013 fix: Build responsive CSS overrides for section layout settings.
|
|
23
|
+
* Same approach as RowRenderer's buildRowResponsiveCss.
|
|
24
|
+
*/
|
|
25
|
+
function buildSectionResponsiveCss(section: PageSection): string | null {
|
|
26
|
+
const responsive = section.responsive;
|
|
27
|
+
if (!responsive) return null;
|
|
28
|
+
|
|
29
|
+
const key = section._key;
|
|
30
|
+
const cssRules: string[] = [];
|
|
31
|
+
|
|
32
|
+
for (const [vp, breakpoint] of [["tablet", BREAKPOINTS.tablet], ["phone", BREAKPOINTS.phone]] as const) {
|
|
33
|
+
const overrides = responsive[vp];
|
|
34
|
+
if (!overrides) continue;
|
|
35
|
+
const rules: string[] = [];
|
|
36
|
+
|
|
37
|
+
// px-value properties
|
|
38
|
+
const pxMap: Record<string, string> = {
|
|
39
|
+
spacing_top: "padding-top", spacing_right: "padding-right",
|
|
40
|
+
spacing_bottom: "padding-bottom", spacing_left: "padding-left",
|
|
41
|
+
offset_top: "margin-top", offset_right: "margin-right",
|
|
42
|
+
offset_bottom: "margin-bottom", offset_left: "margin-left",
|
|
43
|
+
border_radius: "border-radius",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
for (const [field, cssProp] of Object.entries(pxMap)) {
|
|
47
|
+
const val = overrides[field as keyof typeof overrides];
|
|
48
|
+
if (val !== undefined && val !== null && val !== "") {
|
|
49
|
+
rules.push(`${cssProp}:${val}px!important`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Border width — side-aware
|
|
54
|
+
if (overrides.border_width !== undefined && overrides.border_width !== null && overrides.border_width !== "") {
|
|
55
|
+
const bw = overrides.border_width;
|
|
56
|
+
const sides = (overrides.border_sides as string) || "all";
|
|
57
|
+
switch (sides) {
|
|
58
|
+
case "top":
|
|
59
|
+
rules.push(`border-top-width:${bw}px!important`);
|
|
60
|
+
break;
|
|
61
|
+
case "right":
|
|
62
|
+
rules.push(`border-right-width:${bw}px!important`);
|
|
63
|
+
break;
|
|
64
|
+
case "bottom":
|
|
65
|
+
rules.push(`border-bottom-width:${bw}px!important`);
|
|
66
|
+
break;
|
|
67
|
+
case "left":
|
|
68
|
+
rules.push(`border-left-width:${bw}px!important`);
|
|
69
|
+
break;
|
|
70
|
+
case "top-bottom":
|
|
71
|
+
rules.push(`border-top-width:${bw}px!important`);
|
|
72
|
+
rules.push(`border-bottom-width:${bw}px!important`);
|
|
73
|
+
break;
|
|
74
|
+
case "left-right":
|
|
75
|
+
rules.push(`border-left-width:${bw}px!important`);
|
|
76
|
+
rules.push(`border-right-width:${bw}px!important`);
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
rules.push(`border-width:${bw}px!important`);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Border color
|
|
85
|
+
if (overrides.border_color) {
|
|
86
|
+
rules.push(`border-color:${overrides.border_color}!important`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Border style
|
|
90
|
+
if (overrides.border_style && overrides.border_style !== "none") {
|
|
91
|
+
rules.push(`border-style:${overrides.border_style}!important`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Background color + opacity → rgba()
|
|
95
|
+
if (overrides.background_color) {
|
|
96
|
+
const opacity = overrides.background_opacity as number | undefined;
|
|
97
|
+
if (opacity !== undefined && opacity < 100) {
|
|
98
|
+
rules.push(`background-color:${hexToRgba(overrides.background_color as string, opacity / 100)}!important`);
|
|
99
|
+
} else {
|
|
100
|
+
rules.push(`background-color:${overrides.background_color}!important`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Background image + sub-properties
|
|
105
|
+
if (overrides.background_image) {
|
|
106
|
+
const imgUrl = process.env.NEXT_PUBLIC_ASSET_BASE_URL
|
|
107
|
+
? `${(process.env.NEXT_PUBLIC_ASSET_BASE_URL as string).replace(/\/$/, "")}/${overrides.background_image}`
|
|
108
|
+
: overrides.background_image;
|
|
109
|
+
rules.push(`background-image:url(${imgUrl})!important`);
|
|
110
|
+
rules.push(`background-size:${overrides.background_size || "cover"}!important`);
|
|
111
|
+
rules.push(`background-position:${overrides.background_position || "center center"}!important`);
|
|
112
|
+
rules.push(`background-repeat:${overrides.background_repeat || "no-repeat"}!important`);
|
|
113
|
+
} else {
|
|
114
|
+
if (overrides.background_size) rules.push(`background-size:${overrides.background_size}!important`);
|
|
115
|
+
if (overrides.background_position) rules.push(`background-position:${overrides.background_position}!important`);
|
|
116
|
+
if (overrides.background_repeat) rules.push(`background-repeat:${overrides.background_repeat}!important`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (rules.length > 0) {
|
|
120
|
+
cssRules.push(`@media(max-width:${breakpoint}px){.section-${key}{${rules.join(";")}}}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return cssRules.length > 0 ? cssRules.join("") : null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface SectionRendererProps {
|
|
128
|
+
section: PageSection;
|
|
129
|
+
/** Page-level enter animation config (from page_settings.enter_animation) */
|
|
130
|
+
pageEnterAnimation?: EnterAnimationConfig;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export default function SectionRenderer({ section, pageEnterAnimation }: SectionRendererProps) {
|
|
134
|
+
const s = section.settings ?? {};
|
|
135
|
+
|
|
136
|
+
// Get the section block
|
|
137
|
+
const block = Array.isArray(section.block) ? section.block[0] : undefined;
|
|
138
|
+
if (!block) return null;
|
|
139
|
+
|
|
140
|
+
// Resolve enter animation (section settings → page default → none)
|
|
141
|
+
const sectionEnterConfig = s.enter_animation;
|
|
142
|
+
const resolvedEnter = resolveEnterAnimation(undefined, undefined, sectionEnterConfig, pageEnterAnimation);
|
|
143
|
+
const hasAnimation = resolvedEnter !== null && resolvedEnter.preset !== "none";
|
|
144
|
+
|
|
145
|
+
// Section layout styles (background, spacing, border, etc.)
|
|
146
|
+
const layoutStyles = getRowLayoutStyles(s as Record<string, unknown>);
|
|
147
|
+
|
|
148
|
+
// BUG-013 fix: build responsive CSS overrides for section
|
|
149
|
+
const responsiveCss = buildSectionResponsiveCss(section);
|
|
150
|
+
|
|
151
|
+
// Render the section block directly — no columns, no grid
|
|
152
|
+
// V1 PageSections contain section blocks (ProjectGrid, Parallax) which
|
|
153
|
+
// don't use the block-level enter/hover cascade — they have their own systems.
|
|
154
|
+
let content: React.ReactNode = (
|
|
155
|
+
<section className={`section-${section._key}`} style={layoutStyles}>
|
|
156
|
+
{responsiveCss && <style dangerouslySetInnerHTML={{ __html: responsiveCss }} />}
|
|
157
|
+
<BlockRenderer block={block} pageEnterAnimation={pageEnterAnimation} sectionEnterAnimation={sectionEnterConfig} />
|
|
158
|
+
</section>
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
if (hasAnimation && resolvedEnter) {
|
|
162
|
+
content = (
|
|
163
|
+
<EnterAnimationWrapper config={resolvedEnter}>
|
|
164
|
+
{content}
|
|
165
|
+
</EnterAnimationWrapper>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return content;
|
|
170
|
+
}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SectionV2Renderer — renders a first-class PageSectionV2 on the public site.
|
|
5
|
+
*
|
|
6
|
+
* Pure CSS Grid renderer with explicit column positions stored in Sanity.
|
|
7
|
+
* No layout computation needed — grid_column, grid_row, and span map directly
|
|
8
|
+
* to CSS Grid properties.
|
|
9
|
+
*
|
|
10
|
+
* Session 83: Created as part of V2 Grid System Phase 6.
|
|
11
|
+
* Session 120: Updated for new enter animation system (4-level cascade).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
PageSectionV2,
|
|
16
|
+
SectionColumn,
|
|
17
|
+
ColumnOverride,
|
|
18
|
+
SectionV2Settings,
|
|
19
|
+
SectionV2ResponsiveOverride,
|
|
20
|
+
ContentBlock,
|
|
21
|
+
BlockLayout,
|
|
22
|
+
} from "../../lib/sanity/types";
|
|
23
|
+
import type { EnterAnimationConfig } from "../../lib/animation/enter-types";
|
|
24
|
+
import { resolveEnterAnimation } from "../../lib/animation/enter-resolve";
|
|
25
|
+
import BlockRenderer from "./BlockRenderer";
|
|
26
|
+
import EnterAnimationWrapper from "./EnterAnimationWrapper";
|
|
27
|
+
import { getRowLayoutStyles, getBlockAlignmentStyles, hasBlockAlignment, getColumnVerticalAlign, hexToRgba } from "../../lib/builder/layout-styles";
|
|
28
|
+
import { BREAKPOINTS } from "../../lib/builder/constants";
|
|
29
|
+
|
|
30
|
+
// ── Responsive CSS generation ──
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Build CSS override rules for section-level settings at a specific viewport.
|
|
34
|
+
* Covers: col_gap, row_gap, spacing (TRBL), offset (TRBL), border, background.
|
|
35
|
+
*/
|
|
36
|
+
function buildSettingsOverrideRules(
|
|
37
|
+
overrides?: Partial<SectionV2Settings>,
|
|
38
|
+
sectionKey?: string,
|
|
39
|
+
): string[] {
|
|
40
|
+
if (!overrides) return [];
|
|
41
|
+
const rules: string[] = [];
|
|
42
|
+
|
|
43
|
+
// Grid-specific: col_gap → column-gap, row_gap → row-gap
|
|
44
|
+
if (overrides.col_gap !== undefined) {
|
|
45
|
+
rules.push(`column-gap:${overrides.col_gap}px!important`);
|
|
46
|
+
}
|
|
47
|
+
if (overrides.row_gap !== undefined) {
|
|
48
|
+
rules.push(`row-gap:${overrides.row_gap}px!important`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// px-value properties (spacing, offset, border-radius)
|
|
52
|
+
const pxMap: Record<string, string> = {
|
|
53
|
+
spacing_top: "padding-top",
|
|
54
|
+
spacing_right: "padding-right",
|
|
55
|
+
spacing_bottom: "padding-bottom",
|
|
56
|
+
spacing_left: "padding-left",
|
|
57
|
+
offset_top: "margin-top",
|
|
58
|
+
offset_right: "margin-right",
|
|
59
|
+
offset_bottom: "margin-bottom",
|
|
60
|
+
offset_left: "margin-left",
|
|
61
|
+
border_radius: "border-radius",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
for (const [field, cssProp] of Object.entries(pxMap)) {
|
|
65
|
+
const val = (overrides as Record<string, unknown>)[field];
|
|
66
|
+
if (val !== undefined && val !== null && val !== "") {
|
|
67
|
+
rules.push(`${cssProp}:${val}px!important`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Border width — side-aware
|
|
72
|
+
if (overrides.border_width !== undefined && overrides.border_width !== null && overrides.border_width !== "") {
|
|
73
|
+
const bw = overrides.border_width;
|
|
74
|
+
const sides = overrides.border_sides || "all";
|
|
75
|
+
switch (sides) {
|
|
76
|
+
case "top":
|
|
77
|
+
rules.push(`border-top-width:${bw}px!important`);
|
|
78
|
+
break;
|
|
79
|
+
case "right":
|
|
80
|
+
rules.push(`border-right-width:${bw}px!important`);
|
|
81
|
+
break;
|
|
82
|
+
case "bottom":
|
|
83
|
+
rules.push(`border-bottom-width:${bw}px!important`);
|
|
84
|
+
break;
|
|
85
|
+
case "left":
|
|
86
|
+
rules.push(`border-left-width:${bw}px!important`);
|
|
87
|
+
break;
|
|
88
|
+
case "top-bottom":
|
|
89
|
+
rules.push(`border-top-width:${bw}px!important`);
|
|
90
|
+
rules.push(`border-bottom-width:${bw}px!important`);
|
|
91
|
+
break;
|
|
92
|
+
case "left-right":
|
|
93
|
+
rules.push(`border-left-width:${bw}px!important`);
|
|
94
|
+
rules.push(`border-right-width:${bw}px!important`);
|
|
95
|
+
break;
|
|
96
|
+
default:
|
|
97
|
+
rules.push(`border-width:${bw}px!important`);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Border color
|
|
103
|
+
if (overrides.border_color) {
|
|
104
|
+
rules.push(`border-color:${overrides.border_color}!important`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Border style
|
|
108
|
+
if (overrides.border_style && overrides.border_style !== "none") {
|
|
109
|
+
rules.push(`border-style:${overrides.border_style}!important`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Background color + opacity
|
|
113
|
+
if (overrides.background_color) {
|
|
114
|
+
const opacity = overrides.background_opacity;
|
|
115
|
+
if (opacity !== undefined && opacity < 100) {
|
|
116
|
+
rules.push(`background-color:${hexToRgba(overrides.background_color, opacity / 100)}!important`);
|
|
117
|
+
} else {
|
|
118
|
+
rules.push(`background-color:${overrides.background_color}!important`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return rules;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build responsive column override CSS for a specific viewport.
|
|
127
|
+
* Each column override replaces grid-column and/or grid-row and/or span.
|
|
128
|
+
*/
|
|
129
|
+
function buildColumnOverrideCss(
|
|
130
|
+
sectionKey: string,
|
|
131
|
+
columnOverrides: ColumnOverride[] | undefined,
|
|
132
|
+
breakpoint: number,
|
|
133
|
+
): string {
|
|
134
|
+
if (!columnOverrides || columnOverrides.length === 0) return "";
|
|
135
|
+
|
|
136
|
+
const rules: string[] = [];
|
|
137
|
+
for (const co of columnOverrides) {
|
|
138
|
+
const colRules: string[] = [];
|
|
139
|
+
if (co.grid_column !== undefined && co.span !== undefined) {
|
|
140
|
+
colRules.push(`grid-column:${co.grid_column}/span ${co.span}!important`);
|
|
141
|
+
} else if (co.grid_column !== undefined) {
|
|
142
|
+
colRules.push(`grid-column-start:${co.grid_column}!important`);
|
|
143
|
+
} else if (co.span !== undefined) {
|
|
144
|
+
// span-only: need to reconstruct grid-column with original start
|
|
145
|
+
colRules.push(`grid-column-end:span ${co.span}!important`);
|
|
146
|
+
}
|
|
147
|
+
if (co.grid_row !== undefined) {
|
|
148
|
+
colRules.push(`grid-row:${co.grid_row}!important`);
|
|
149
|
+
}
|
|
150
|
+
if (colRules.length > 0) {
|
|
151
|
+
rules.push(`.sv2-col-${sectionKey}-${co._key}{${colRules.join(";")}}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return rules.length > 0
|
|
156
|
+
? `@media(max-width:${breakpoint}px){${rules.join("")}}`
|
|
157
|
+
: "";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Generate all responsive CSS for a V2 section (section settings + column overrides).
|
|
162
|
+
*/
|
|
163
|
+
function buildSectionV2ResponsiveCss(section: PageSectionV2): string | null {
|
|
164
|
+
const responsive = section.responsive;
|
|
165
|
+
if (!responsive) return null;
|
|
166
|
+
|
|
167
|
+
const key = section._key;
|
|
168
|
+
const cssParts: string[] = [];
|
|
169
|
+
|
|
170
|
+
for (const [vp, breakpoint] of [["tablet", BREAKPOINTS.tablet], ["phone", BREAKPOINTS.phone]] as const) {
|
|
171
|
+
const override = responsive[vp];
|
|
172
|
+
if (!override) continue;
|
|
173
|
+
|
|
174
|
+
// Section-level settings overrides
|
|
175
|
+
const settingsRules = buildSettingsOverrideRules(override.settings as Partial<SectionV2Settings> | undefined);
|
|
176
|
+
if (settingsRules.length > 0) {
|
|
177
|
+
cssParts.push(`@media(max-width:${breakpoint}px){.sv2-${key}{${settingsRules.join(";")}}}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Column position/span overrides
|
|
181
|
+
const colCss = buildColumnOverrideCss(key, override.columns, breakpoint);
|
|
182
|
+
if (colCss) cssParts.push(colCss);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return cssParts.length > 0 ? cssParts.join("") : null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ── Main component ──
|
|
189
|
+
|
|
190
|
+
interface SectionV2RendererProps {
|
|
191
|
+
section: PageSectionV2;
|
|
192
|
+
/** Page-level enter animation config (from page_settings.enter_animation) */
|
|
193
|
+
pageEnterAnimation?: EnterAnimationConfig;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export default function SectionV2Renderer({ section, pageEnterAnimation }: SectionV2RendererProps) {
|
|
197
|
+
const s = section.settings;
|
|
198
|
+
|
|
199
|
+
const gridColumns = s.grid_columns || 12;
|
|
200
|
+
const colGap = s.col_gap ?? 20;
|
|
201
|
+
const rowGap = s.row_gap ?? 20;
|
|
202
|
+
|
|
203
|
+
// Resolve section-level enter animation (section → page → null)
|
|
204
|
+
const sectionEnterConfig = s.enter_animation;
|
|
205
|
+
|
|
206
|
+
// Stagger settings — stagger only applies when the section has an enter animation
|
|
207
|
+
const resolvedSectionEnter = resolveEnterAnimation(undefined, undefined, sectionEnterConfig, pageEnterAnimation);
|
|
208
|
+
const hasAnimation = resolvedSectionEnter !== null && resolvedSectionEnter.preset !== "none";
|
|
209
|
+
|
|
210
|
+
const stagger = s.stagger;
|
|
211
|
+
const staggerEnabled = stagger?.enabled && hasAnimation;
|
|
212
|
+
const staggerDelay = stagger?.delayPerChild ?? 100;
|
|
213
|
+
const staggerDirection = stagger?.direction ?? "left-to-right";
|
|
214
|
+
|
|
215
|
+
// Section-level layout styles (background, spacing, border, offset)
|
|
216
|
+
const layoutStyles = getRowLayoutStyles(s as unknown as Record<string, unknown>, process.env.NEXT_PUBLIC_ASSET_BASE_URL);
|
|
217
|
+
|
|
218
|
+
// Responsive CSS (section settings + column overrides)
|
|
219
|
+
const responsiveCss = buildSectionV2ResponsiveCss(section);
|
|
220
|
+
|
|
221
|
+
// Sort columns for stagger ordering
|
|
222
|
+
const sortedColumns = [...section.columns].sort((a, b) => {
|
|
223
|
+
if (a.grid_row !== b.grid_row) return a.grid_row - b.grid_row;
|
|
224
|
+
return a.grid_column - b.grid_column;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Stagger index computation
|
|
228
|
+
const getStaggerIndex = (colIndex: number): number => {
|
|
229
|
+
if (!staggerEnabled) return 0;
|
|
230
|
+
return staggerDirection === "right-to-left"
|
|
231
|
+
? sortedColumns.length - 1 - colIndex
|
|
232
|
+
: colIndex;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Build section content
|
|
236
|
+
const sectionContent = (
|
|
237
|
+
<section
|
|
238
|
+
className={`sv2-${section._key}`}
|
|
239
|
+
style={layoutStyles}
|
|
240
|
+
>
|
|
241
|
+
{responsiveCss && <style dangerouslySetInnerHTML={{ __html: responsiveCss }} />}
|
|
242
|
+
<div
|
|
243
|
+
style={{
|
|
244
|
+
maxWidth: "var(--grid-width, 1445px)",
|
|
245
|
+
marginLeft: "auto",
|
|
246
|
+
marginRight: "auto",
|
|
247
|
+
width: "100%",
|
|
248
|
+
}}
|
|
249
|
+
>
|
|
250
|
+
<div
|
|
251
|
+
style={{
|
|
252
|
+
display: "grid",
|
|
253
|
+
gridTemplateColumns: `repeat(${gridColumns}, 1fr)`,
|
|
254
|
+
columnGap: `${colGap}px`,
|
|
255
|
+
rowGap: `${rowGap}px`,
|
|
256
|
+
}}
|
|
257
|
+
>
|
|
258
|
+
{sortedColumns.map((col, colIndex) => {
|
|
259
|
+
const staggerIdx = staggerEnabled ? getStaggerIndex(colIndex) : undefined;
|
|
260
|
+
|
|
261
|
+
// Column-level vertical alignment from blocks' align_v settings
|
|
262
|
+
const colJustify = getColumnVerticalAlign(col.blocks || []);
|
|
263
|
+
|
|
264
|
+
const columnContent = (
|
|
265
|
+
<div
|
|
266
|
+
key={col._key}
|
|
267
|
+
className={`sv2-col-${section._key}-${col._key}`}
|
|
268
|
+
style={{
|
|
269
|
+
gridColumn: `${col.grid_column} / span ${col.span}`,
|
|
270
|
+
gridRow: col.grid_row,
|
|
271
|
+
display: "flex",
|
|
272
|
+
flexDirection: "column",
|
|
273
|
+
...(colJustify ? { justifyContent: colJustify } : {}),
|
|
274
|
+
height: "100%",
|
|
275
|
+
minWidth: 0,
|
|
276
|
+
overflow: "hidden",
|
|
277
|
+
}}
|
|
278
|
+
>
|
|
279
|
+
{(col.blocks || []).map((block) => {
|
|
280
|
+
const blockLayout = (block as unknown as Record<string, unknown>).layout as BlockLayout | undefined;
|
|
281
|
+
const alignStyles = hasBlockAlignment(blockLayout) ? getBlockAlignmentStyles(blockLayout) : undefined;
|
|
282
|
+
const hasHAlign = blockLayout?.align_h && blockLayout.align_h !== "left";
|
|
283
|
+
return (
|
|
284
|
+
<div key={block._key} className={`blk-wrap-${block._key}`} style={{ ...(!hasHAlign ? { width: "100%" } : {}), minWidth: 0, ...alignStyles }}>
|
|
285
|
+
<BlockRenderer
|
|
286
|
+
block={block}
|
|
287
|
+
columnEnterAnimation={col.enter_animation}
|
|
288
|
+
sectionEnterAnimation={sectionEnterConfig}
|
|
289
|
+
pageEnterAnimation={pageEnterAnimation}
|
|
290
|
+
/>
|
|
291
|
+
</div>
|
|
292
|
+
);
|
|
293
|
+
})}
|
|
294
|
+
</div>
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
// When stagger is enabled, wrap each column individually with enter animation
|
|
298
|
+
if (staggerEnabled && staggerIdx !== undefined && resolvedSectionEnter) {
|
|
299
|
+
return (
|
|
300
|
+
<EnterAnimationWrapper
|
|
301
|
+
key={col._key}
|
|
302
|
+
config={resolvedSectionEnter}
|
|
303
|
+
staggerIndex={staggerIdx}
|
|
304
|
+
staggerDelay={staggerDelay}
|
|
305
|
+
>
|
|
306
|
+
{columnContent}
|
|
307
|
+
</EnterAnimationWrapper>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return columnContent;
|
|
312
|
+
})}
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
</section>
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
// Section-level enter animation (without stagger — wraps entire section)
|
|
319
|
+
let result: React.ReactNode = sectionContent;
|
|
320
|
+
|
|
321
|
+
if (hasAnimation && !staggerEnabled && resolvedSectionEnter) {
|
|
322
|
+
result = (
|
|
323
|
+
<EnterAnimationWrapper config={resolvedSectionEnter}>
|
|
324
|
+
{result}
|
|
325
|
+
</EnterAnimationWrapper>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return result;
|
|
330
|
+
}
|