@morphika/andami 0.2.26 → 0.3.1
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/app/admin/pages/[slug]/page.tsx +39 -45
- package/app/api/admin/assets/scan/route.ts +40 -13
- package/app/api/admin/custom-sections/[slug]/route.ts +4 -1
- package/app/api/admin/custom-sections/route.ts +4 -1
- package/app/api/admin/pages/[slug]/route.ts +7 -1
- package/app/api/admin/pages/route.ts +4 -1
- package/app/api/admin/r2/connect/route.ts +19 -1
- package/app/api/admin/r2/disconnect/route.ts +3 -0
- package/app/api/admin/r2/rename/route.ts +52 -13
- package/app/api/admin/r2/upload-url/route.ts +8 -1
- package/app/api/admin/settings/route.ts +4 -1
- package/app/api/admin/styles/route.ts +4 -1
- package/components/admin/styles/GridLayoutEditor.tsx +46 -46
- package/components/blocks/BlockRenderer.tsx +11 -2
- package/components/blocks/CoverSectionRenderer.tsx +75 -3
- package/components/blocks/ImageGridBlockRenderer.tsx +17 -11
- package/components/blocks/ParallaxGroupRenderer.tsx +45 -10
- package/components/blocks/ShaderCanvas.tsx +10 -6
- package/components/builder/BlockCardIcons.tsx +227 -0
- package/components/builder/BlockTypePicker.tsx +36 -63
- package/components/builder/BuilderCanvas.tsx +6 -2
- package/components/builder/ColumnDragOverlay.tsx +3 -3
- package/components/builder/CoverRowResizeHandle.tsx +5 -2
- package/components/builder/CoverSectionCanvas.tsx +45 -52
- package/components/builder/DndWrapper.tsx +1 -1
- package/components/builder/InsertionLines.tsx +1 -1
- package/components/builder/ParallaxGroupCanvas.tsx +12 -71
- package/components/builder/SectionCardIcons.tsx +266 -0
- package/components/builder/SectionEditorBar.tsx +17 -12
- package/components/builder/SectionTypePicker.tsx +33 -137
- package/components/builder/SectionV2Canvas.tsx +1 -1
- package/components/builder/SectionV2Column.tsx +19 -30
- package/components/builder/SettingsPanel.tsx +8 -32
- package/components/builder/SortableBlock.tsx +42 -50
- package/components/builder/SortableRow.tsx +207 -19
- package/components/builder/blockStyles.tsx +53 -180
- package/components/builder/iconPrimitives.tsx +78 -0
- package/components/builder/live-preview/LiveImagePreview.tsx +16 -2
- package/components/builder/live-preview/LiveVideoPreview.tsx +15 -2
- package/components/builder/settings-panel/ColumnV2Settings.tsx +5 -5
- package/components/builder/settings-panel/CoverSectionSettings.tsx +28 -1
- package/components/builder/settings-panel/SectionV2Settings.tsx +14 -14
- package/lib/assets.ts +17 -2
- package/lib/builder/constants.ts +22 -15
- package/lib/builder/format.ts +25 -0
- package/lib/builder/history.ts +0 -3
- package/lib/builder/layout-styles.ts +1 -1
- package/lib/builder/section-visibility.ts +36 -0
- package/lib/builder/serializer/normalizers.ts +15 -6
- package/lib/builder/serializer/serializers.ts +3 -3
- package/lib/builder/store-blocks.ts +16 -9
- package/lib/builder/store-cover.ts +76 -8
- package/lib/builder/store.ts +0 -2
- package/lib/builder/types.ts +1 -2
- package/lib/csrf.ts +31 -0
- package/lib/sanity/types.ts +4 -1
- package/lib/security.ts +50 -0
- package/lib/version.ts +1 -1
- package/package.json +1 -1
- package/sanity/schemas/objects/coverSection.ts +35 -3
- package/components/builder/ParallaxSlideHeader.tsx +0 -113
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
import { useBuilderStore } from "../../lib/builder/store";
|
|
4
4
|
import type { ParallaxGroup, ParallaxSlideV2, PageSectionV2 } from "../../lib/sanity/types";
|
|
5
5
|
import SectionV2Canvas from "./SectionV2Canvas";
|
|
6
|
-
import ParallaxSlideHeader from "./ParallaxSlideHeader";
|
|
7
|
-
import { BUILDER_GREEN, BUILDER_VIOLET } from "../../lib/builder/constants";
|
|
8
6
|
import { DEVICE_HEIGHTS } from "../../lib/builder/types";
|
|
9
7
|
import { useAssetUrl } from "../../lib/contexts/AssetContext";
|
|
10
8
|
|
|
@@ -12,12 +10,13 @@ import { useAssetUrl } from "../../lib/contexts/AssetContext";
|
|
|
12
10
|
* ParallaxGroupCanvas — renders a ParallaxGroup in the builder canvas.
|
|
13
11
|
*
|
|
14
12
|
* Each slide is displayed as a stacked section with:
|
|
15
|
-
* - ParallaxSlideHeader (index, bg indicator, reorder, delete)
|
|
16
13
|
* - SectionV2Canvas (full V2 grid editor reuse)
|
|
17
14
|
* - Faint background preview when a bg image is set
|
|
18
15
|
* - Empty state message when slide has no background and no content blocks
|
|
19
16
|
*
|
|
20
|
-
*
|
|
17
|
+
* Slide-level chrome (select, reorder, delete) + "+ Slide" live in the side
|
|
18
|
+
* pill rendered by SortableRow. The old per-slide header component and the
|
|
19
|
+
* bottom "Add Slide" button were removed to reduce canvas clutter.
|
|
21
20
|
*
|
|
22
21
|
* Session 123: Parallax V2 Phase 2
|
|
23
22
|
* Session 127: Phase 5 — empty state, slide counter badge, smooth reorder
|
|
@@ -41,9 +40,7 @@ export default function ParallaxGroupCanvas({
|
|
|
41
40
|
group,
|
|
42
41
|
onAddBlockTarget,
|
|
43
42
|
}: ParallaxGroupCanvasProps) {
|
|
44
|
-
const
|
|
45
|
-
const selectedRowKey = store.selectedRowKey;
|
|
46
|
-
const activeViewport = store.activeViewport || "desktop";
|
|
43
|
+
const activeViewport = useBuilderStore((s) => s.activeViewport) || "desktop";
|
|
47
44
|
const slidePreviewHeight = DEVICE_HEIGHTS[activeViewport];
|
|
48
45
|
const assetUrl = useAssetUrl();
|
|
49
46
|
|
|
@@ -56,40 +53,8 @@ export default function ParallaxGroupCanvas({
|
|
|
56
53
|
overflow: "visible",
|
|
57
54
|
}}
|
|
58
55
|
>
|
|
59
|
-
{/*
|
|
60
|
-
<div
|
|
61
|
-
className="flex items-center gap-2 px-3 py-2 cursor-pointer"
|
|
62
|
-
style={{
|
|
63
|
-
background: selectedRowKey === group._key
|
|
64
|
-
? "linear-gradient(135deg, #e8deff 0%, #ddd0ff 100%)"
|
|
65
|
-
: "linear-gradient(135deg, #f3f0ff 0%, #ede5ff 100%)",
|
|
66
|
-
borderBottom: "1px solid rgba(139, 92, 246, 0.15)",
|
|
67
|
-
borderRadius: "12px 12px 0 0",
|
|
68
|
-
}}
|
|
69
|
-
onClick={(e) => {
|
|
70
|
-
e.stopPropagation();
|
|
71
|
-
store.selectRow(group._key);
|
|
72
|
-
}}
|
|
73
|
-
>
|
|
74
|
-
<span className="text-[11px] font-semibold text-[#8b5cf6]">
|
|
75
|
-
▽ Parallax Showcase
|
|
76
|
-
</span>
|
|
77
|
-
{/* Slide counter badge */}
|
|
78
|
-
<span
|
|
79
|
-
className="inline-flex items-center justify-center rounded-full text-[9px] font-bold text-white min-w-[18px] h-[18px] px-1"
|
|
80
|
-
style={{ background: BUILDER_VIOLET }}
|
|
81
|
-
>
|
|
82
|
-
{group.slides.length}
|
|
83
|
-
</span>
|
|
84
|
-
<div className="flex-1" />
|
|
85
|
-
<span className="text-[9px] text-neutral-400 uppercase tracking-wider">
|
|
86
|
-
{group.transition_effect}
|
|
87
|
-
</span>
|
|
88
|
-
</div>
|
|
89
|
-
|
|
90
|
-
{/* Slides — CSS transition for smooth reorder */}
|
|
56
|
+
{/* Slides — group header removed; slide management lives in the section pill (SortableRow). */}
|
|
91
57
|
{group.slides.map((slide, slideIndex) => {
|
|
92
|
-
const isSlideSelected = selectedRowKey === slide._key;
|
|
93
58
|
const bgImagePath = slide.background_type === "image" && slide.background_image
|
|
94
59
|
? assetUrl(slide.background_image)
|
|
95
60
|
: null;
|
|
@@ -118,15 +83,8 @@ export default function ParallaxGroupCanvas({
|
|
|
118
83
|
transition: "opacity 0.2s ease",
|
|
119
84
|
}}
|
|
120
85
|
>
|
|
121
|
-
{/* Slide header
|
|
122
|
-
|
|
123
|
-
slide={slide}
|
|
124
|
-
slideIndex={slideIndex}
|
|
125
|
-
totalSlides={group.slides.length}
|
|
126
|
-
groupKey={group._key}
|
|
127
|
-
isSelected={isSlideSelected}
|
|
128
|
-
onSelect={() => store.selectRow(slide._key)}
|
|
129
|
-
/>
|
|
86
|
+
{/* Slide header removed — slide selection / delete / reorder
|
|
87
|
+
moved to the side pill (SortableRow). */}
|
|
130
88
|
|
|
131
89
|
{/* Slide content with optional background preview — 100vh equivalent */}
|
|
132
90
|
<div className="relative" style={{ minHeight: slidePreviewHeight }}>
|
|
@@ -166,10 +124,11 @@ export default function ParallaxGroupCanvas({
|
|
|
166
124
|
/>
|
|
167
125
|
)}
|
|
168
126
|
|
|
169
|
-
{/* Empty state message
|
|
127
|
+
{/* Empty state message — anchored to the top so it doesn't
|
|
128
|
+
collide with the centered "+ Add Block" pill on hover. */}
|
|
170
129
|
{slideEmpty && (
|
|
171
130
|
<div
|
|
172
|
-
className="absolute inset-0 flex items-
|
|
131
|
+
className="absolute inset-0 flex items-start justify-center pt-3 pointer-events-none"
|
|
173
132
|
style={{ zIndex: 5 }}
|
|
174
133
|
>
|
|
175
134
|
<div className="text-center px-6 py-4 rounded-lg" style={{ background: "rgba(139, 92, 246, 0.06)" }}>
|
|
@@ -203,26 +162,8 @@ export default function ParallaxGroupCanvas({
|
|
|
203
162
|
);
|
|
204
163
|
})}
|
|
205
164
|
|
|
206
|
-
{/* Add Slide button
|
|
207
|
-
|
|
208
|
-
className="flex justify-center py-3"
|
|
209
|
-
style={{
|
|
210
|
-
background: "linear-gradient(135deg, #f9f7ff 0%, #f3f0ff 100%)",
|
|
211
|
-
borderTop: "1px solid rgba(139, 92, 246, 0.1)",
|
|
212
|
-
borderRadius: "0 0 12px 12px",
|
|
213
|
-
}}
|
|
214
|
-
>
|
|
215
|
-
<button
|
|
216
|
-
className="flex items-center gap-1.5 rounded-lg px-4 py-1.5 text-[11px] font-medium text-white transition-colors hover:opacity-90"
|
|
217
|
-
style={{ background: BUILDER_GREEN }}
|
|
218
|
-
onClick={(e) => {
|
|
219
|
-
e.stopPropagation();
|
|
220
|
-
store.addParallaxSlide(group._key);
|
|
221
|
-
}}
|
|
222
|
-
>
|
|
223
|
-
<span className="text-sm leading-none">+</span> Add Slide
|
|
224
|
-
</button>
|
|
225
|
-
</div>
|
|
165
|
+
{/* Add Slide button removed — adding slides is now done from the
|
|
166
|
+
section pill (SortableRow). */}
|
|
226
167
|
</div>
|
|
227
168
|
);
|
|
228
169
|
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Full-size section card icons (220×120) used in the Add Section modal.
|
|
5
|
+
*
|
|
6
|
+
* Same visual language as `BlockCardIcons.tsx` but violet-tinted to
|
|
7
|
+
* differentiate sections from blocks:
|
|
8
|
+
* - accent: #7500D5 (block equivalent: #4794E2)
|
|
9
|
+
* - ghost fill: #E8DFF2 (block equivalent: #DDE6F5)
|
|
10
|
+
* - neutral stroke:#D6C9E2 (block equivalent: #C9D3E4)
|
|
11
|
+
* - bevel end: #EBEAEF (block equivalent: #E6ECF6)
|
|
12
|
+
*
|
|
13
|
+
* IDs are namespaced per icon (`seSec`, `scSec`, `spgSec`, `spSec`, `sccSec`,
|
|
14
|
+
* `ssSec`) so multiple cards can render side by side without collisions.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Bg, BgDefs, ShadowFilter, VertBevel } from "./iconPrimitives";
|
|
18
|
+
|
|
19
|
+
// Section-specific colour tokens
|
|
20
|
+
const ACCENT = "#7500D5";
|
|
21
|
+
const GHOST = "#E8DFF2";
|
|
22
|
+
const NEUTRAL_STROKE = "#D6C9E2";
|
|
23
|
+
const BEVEL_END = "#EBEAEF";
|
|
24
|
+
|
|
25
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
26
|
+
// Empty Section — frame + 4 ghost columns
|
|
27
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
28
|
+
export function EmptySectionV2CardIcon() {
|
|
29
|
+
return (
|
|
30
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 120" width="100%" height="100%" preserveAspectRatio="xMidYMid meet">
|
|
31
|
+
<defs>
|
|
32
|
+
<BgDefs prefix="seSec" />
|
|
33
|
+
<ShadowFilter id="shadSeSec" />
|
|
34
|
+
<VertBevel id="bevelSeSec" endColor={BEVEL_END} />
|
|
35
|
+
</defs>
|
|
36
|
+
<Bg prefix="seSec" />
|
|
37
|
+
|
|
38
|
+
<g filter="url(#shadSeSec)">
|
|
39
|
+
<path d="M26.8,19h112.7c1.3,0,2.3,1.2,2.3,2.7v75c0,1.5-1.1,2.7-2.3,2.7H26.8c-1.3-0.1-2.3-1.3-2.3-2.7V21.6C24.5,20.2,25.5,19,26.8,19z"
|
|
40
|
+
fill="url(#bevelSeSec)" stroke={NEUTRAL_STROKE} strokeWidth="0.7" />
|
|
41
|
+
</g>
|
|
42
|
+
<g fill={GHOST}>
|
|
43
|
+
<path d="M51.3,24.9H33.4c-1.4,0-2.5,1.1-2.5,2.5v63.3c0,1.4,1.1,2.5,2.5,2.5h17.9c1.4,0,2.5-1.1,2.5-2.5V27.4C53.8,26.1,52.6,24.9,51.3,24.9z" />
|
|
44
|
+
<path d="M78.3,24.9H60.4c-1.4,0-2.5,1.1-2.5,2.5v63.3c0,1.4,1.1,2.5,2.5,2.5h17.9c1.4,0,2.5-1.1,2.5-2.5V27.4C80.8,26.1,79.7,24.9,78.3,24.9z" />
|
|
45
|
+
<path d="M105.1,24.9H87.2c-1.4,0-2.5,1.1-2.5,2.5v63.3c0,1.4,1.1,2.5,2.5,2.5h17.9c1.4,0,2.5-1.1,2.5-2.5V27.4C107.6,26.1,106.5,24.9,105.1,24.9z" />
|
|
46
|
+
<path d="M132.2,24.9h-17.9c-1.4,0-2.5,1.1-2.5,2.5v63.3c0,1.4,1.1,2.5,2.5,2.5h17.9c1.4,0,2.5-1.1,2.5-2.5V27.4C134.7,26.1,133.6,24.9,132.2,24.9z" />
|
|
47
|
+
</g>
|
|
48
|
+
<path d="M26.8,19h112.7c1.3,0,2.3,1.2,2.3,2.7v75c0,1.5-1.1,2.7-2.3,2.7H26.8c-1.3-0.1-2.3-1.3-2.3-2.7V21.6C24.5,20.2,25.5,19,26.8,19z"
|
|
49
|
+
fill="none" stroke={ACCENT} strokeWidth="2" strokeDasharray="3,3" />
|
|
50
|
+
</svg>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
55
|
+
// Cover Section — frame + vertical + horizontal dimension arrows
|
|
56
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
57
|
+
export function CoverSectionCardIcon() {
|
|
58
|
+
return (
|
|
59
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 120" width="100%" height="100%" preserveAspectRatio="xMidYMid meet">
|
|
60
|
+
<defs>
|
|
61
|
+
<BgDefs prefix="scSec" />
|
|
62
|
+
<ShadowFilter id="shadScSec" />
|
|
63
|
+
<VertBevel id="bevelScSec" endColor={BEVEL_END} />
|
|
64
|
+
</defs>
|
|
65
|
+
<Bg prefix="scSec" />
|
|
66
|
+
|
|
67
|
+
<g filter="url(#shadScSec)">
|
|
68
|
+
<path d="M26.8,19h112.7c1.3,0,2.3,1.2,2.3,2.7v75c0,1.5-1.1,2.7-2.3,2.7H26.8c-1.3-0.1-2.3-1.3-2.3-2.7V21.6C24.5,20.2,25.5,19,26.8,19z"
|
|
69
|
+
fill="url(#bevelScSec)" stroke={NEUTRAL_STROKE} strokeWidth="0.7" />
|
|
70
|
+
</g>
|
|
71
|
+
<path d="M26.8,19h112.7c1.3,0,2.3,1.2,2.3,2.7v75c0,1.5-1.1,2.7-2.3,2.7H26.8c-1.3-0.1-2.3-1.3-2.3-2.7V21.6C24.5,20.2,25.5,19,26.8,19z"
|
|
72
|
+
fill="none" stroke={ACCENT} strokeWidth="2" strokeDasharray="3,3" />
|
|
73
|
+
|
|
74
|
+
{/* Vertical height arrow */}
|
|
75
|
+
<g fill={ACCENT} stroke={ACCENT} strokeMiterlimit="10">
|
|
76
|
+
<line x1="82.4" y1="30.1" x2="82.4" y2="88.8" />
|
|
77
|
+
<polygon points="82.4,23 78.4,33 82.4,30.6 86.5,33" />
|
|
78
|
+
<polygon points="82.4,95.9 78.4,85.9 82.4,88.3 86.5,85.9" />
|
|
79
|
+
</g>
|
|
80
|
+
|
|
81
|
+
{/* Horizontal width arrow */}
|
|
82
|
+
<g fill={ACCENT} stroke={ACCENT} strokeMiterlimit="10">
|
|
83
|
+
<line x1="127.9" y1="59.4" x2="34.5" y2="59.4" />
|
|
84
|
+
<polygon points="135.4,59.4 125.4,55.4 127.8,59.4 125.4,63.5" />
|
|
85
|
+
<polygon points="29.4,59.4 39.4,55.4 37,59.4 39.4,63.5" />
|
|
86
|
+
</g>
|
|
87
|
+
</svg>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
92
|
+
// Project Grid — 5 masonry tiles
|
|
93
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
94
|
+
export function ProjectGridCardIcon() {
|
|
95
|
+
return (
|
|
96
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 120" width="100%" height="100%" preserveAspectRatio="xMidYMid meet">
|
|
97
|
+
<defs>
|
|
98
|
+
<BgDefs prefix="spgSec" />
|
|
99
|
+
<ShadowFilter id="shadSpgSec" />
|
|
100
|
+
<VertBevel id="tileBevelSpgSec" endColor={BEVEL_END} />
|
|
101
|
+
</defs>
|
|
102
|
+
<Bg prefix="spgSec" />
|
|
103
|
+
|
|
104
|
+
{/* 5 masonry tiles with shadow */}
|
|
105
|
+
<g filter="url(#shadSpgSec)">
|
|
106
|
+
<path d="M25.8,19h30.8c1,0,1.8,0.8,1.8,1.8v76.9c0,1-0.8,1.8-1.8,1.8H25.8c-1,0-1.8-0.8-1.8-1.8V20.8C24,19.8,24.8,19,25.8,19z"
|
|
107
|
+
fill="url(#tileBevelSpgSec)" stroke={NEUTRAL_STROKE} strokeWidth="0.6" />
|
|
108
|
+
<path d="M67.5,19h30.8c1,0,1.8,0.8,1.8,1.8v38.4c0,1-0.8,1.8-1.8,1.8H67.5c-1,0-1.8-0.8-1.8-1.8V20.8C65.6,19.8,66.5,19,67.5,19z"
|
|
109
|
+
fill="url(#tileBevelSpgSec)" stroke={NEUTRAL_STROKE} strokeWidth="0.6" />
|
|
110
|
+
<path d="M109.1,19h30.8c1,0,1.8,0.8,1.8,1.8v53.1c0,1-0.8,1.8-1.8,1.8h-30.8c-1,0-1.8-0.8-1.8-1.8V20.8C107.3,19.8,108.1,19,109.1,19z"
|
|
111
|
+
fill="url(#tileBevelSpgSec)" stroke={NEUTRAL_STROKE} strokeWidth="0.6" />
|
|
112
|
+
<path d="M66.8,67h30.8c1,0,1.8,0.8,1.8,1.8v29.3c0,1-0.8,1.8-1.8,1.8H66.8c-1,0-1.8-0.8-1.8-1.8V68.8C65,67.8,65.8,67,66.8,67z"
|
|
113
|
+
fill="url(#tileBevelSpgSec)" stroke={NEUTRAL_STROKE} strokeWidth="0.6" />
|
|
114
|
+
<path d="M139.6,99.7h-30.3c-1.2,0-2.1-0.9-2.1-2.1V81.4c0-1.2,0.9-2.1,2.1-2.1h30.3c1.2,0,2.1,0.9,2.1,2.1v16.2C141.6,98.8,140.7,99.7,139.6,99.7z"
|
|
115
|
+
fill="url(#tileBevelSpgSec)" stroke={NEUTRAL_STROKE} strokeWidth="0.6" />
|
|
116
|
+
</g>
|
|
117
|
+
|
|
118
|
+
{/* Inner ghost content */}
|
|
119
|
+
<g fill={GHOST}>
|
|
120
|
+
<path d="M28.7,22.6h25c0.6,0,1.1,0.5,1.1,1.1v54.5c0,0.6-0.5,1.1-1.1,1.1h-25c-0.6,0-1.1-0.5-1.1-1.1V23.7C27.7,23.1,28.1,22.6,28.7,22.6z" />
|
|
121
|
+
<path d="M70.4,22.6h25c0.6,0,1.1,0.5,1.1,1.1v23.4c0,0.6-0.5,1.1-1.1,1.1h-25c-0.6,0-1.1-0.5-1.1-1.1V23.7C69.3,23.1,69.7,22.6,70.4,22.6z" />
|
|
122
|
+
<path d="M112,22.6h25c0.6,0,1.1,0.5,1.1,1.1v38.1c0,0.6-0.5,1.1-1.1,1.1h-25c-0.6,0-1.1-0.5-1.1-1.1V23.7C110.9,23.1,111.3,22.6,112,22.6z" />
|
|
123
|
+
<path d="M69.7,70.7h25c0.6,0,1.1,0.5,1.1,1.1v17.9c0,0.6-0.5,1.1-1.1,1.1h-25c-0.6,0-1.1-0.5-1.1-1.1V71.8C68.6,71.2,69.2,70.7,69.7,70.7z" />
|
|
124
|
+
<path d="M137.5,96.2h-25.7c-0.7,0-1.4-0.6-1.4-1.4V84c0-0.7,0.6-1.4,1.4-1.4h25.7c0.7,0,1.4,0.6,1.4,1.4v10.8C138.7,95.6,138.2,96.2,137.5,96.2z" />
|
|
125
|
+
<path d="M28.6,83h18.1c0.5,0,0.9,0.4,0.9,0.9v0.9c0,0.5-0.4,0.9-0.9,0.9H28.6c-0.5,0-0.9-0.4-0.9-0.9v-0.9C27.7,83.4,28,83,28.6,83z" />
|
|
126
|
+
<path d="M28.6,88.5h10.9c0.5,0,0.9,0.4,0.9,0.9v0.9c0,0.5-0.4,0.9-0.9,0.9H28.6c-0.5,0-0.9-0.4-0.9-0.9v-0.9C27.7,88.9,28,88.5,28.6,88.5z" />
|
|
127
|
+
</g>
|
|
128
|
+
|
|
129
|
+
{/* Dashed violet outlines on all tiles */}
|
|
130
|
+
<g fill="none" stroke={ACCENT} strokeWidth="2" strokeMiterlimit="10" strokeDasharray="3,3">
|
|
131
|
+
<path d="M25.8,19h30.8c1,0,1.8,0.8,1.8,1.8v76.9c0,1-0.8,1.8-1.8,1.8H25.8c-1,0-1.8-0.8-1.8-1.8V20.8C24,19.8,24.8,19,25.8,19z" />
|
|
132
|
+
<path d="M67.5,19h30.8c1,0,1.8,0.8,1.8,1.8v38.4c0,1-0.8,1.8-1.8,1.8H67.5c-1,0-1.8-0.8-1.8-1.8V20.8C65.6,19.8,66.5,19,67.5,19z" />
|
|
133
|
+
<path d="M109.1,19h30.8c1,0,1.8,0.8,1.8,1.8v53.1c0,1-0.8,1.8-1.8,1.8h-30.8c-1,0-1.8-0.8-1.8-1.8V20.8C107.3,19.8,108.1,19,109.1,19z" />
|
|
134
|
+
<path d="M66.8,67h30.8c1,0,1.8,0.8,1.8,1.8v29.3c0,1-0.8,1.8-1.8,1.8H66.8c-1,0-1.8-0.8-1.8-1.8V68.8C65,67.8,65.8,67,66.8,67z" />
|
|
135
|
+
<path d="M139.6,99.7h-30.3c-1.2,0-2.1-0.9-2.1-2.1V81.4c0-1.2,0.9-2.1,2.1-2.1h30.3c1.2,0,2.1,0.9,2.1,2.1v16.2C141.6,98.8,140.7,99.7,139.6,99.7z" />
|
|
136
|
+
</g>
|
|
137
|
+
</svg>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
142
|
+
// Parallax Group — 3 stacked slides with image content
|
|
143
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
144
|
+
export function ParallaxGroupCardIcon() {
|
|
145
|
+
return (
|
|
146
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 120" width="100%" height="100%" preserveAspectRatio="xMidYMid meet">
|
|
147
|
+
<defs>
|
|
148
|
+
<BgDefs prefix="spSec" />
|
|
149
|
+
<ShadowFilter id="shadSpSec" />
|
|
150
|
+
<VertBevel id="slideBevelSpSec" endColor={BEVEL_END} />
|
|
151
|
+
</defs>
|
|
152
|
+
<Bg prefix="spSec" />
|
|
153
|
+
|
|
154
|
+
{/* Slide 1 (back/smallest) */}
|
|
155
|
+
<g filter="url(#shadSpSec)">
|
|
156
|
+
<path d="M62.1,18.7H105c0.5,0,0.9,0.3,0.9,0.7v19.5c0,0.4-0.4,0.7-0.9,0.7H62.1c-0.5,0-0.9-0.3-0.9-0.7V19.4C61.2,19,61.6,18.7,62.1,18.7z"
|
|
157
|
+
fill="url(#slideBevelSpSec)" stroke={NEUTRAL_STROKE} strokeWidth="0.7" />
|
|
158
|
+
</g>
|
|
159
|
+
<path d="M66.8,22.6h33.5c0.2,0,0.5,0.1,0.5,0.3v12.4c0,0.2-0.2,0.3-0.5,0.3H66.8c-0.2,0-0.5-0.1-0.5-0.3V22.9C66.3,22.7,66.5,22.6,66.8,22.6z"
|
|
160
|
+
fill={GHOST} />
|
|
161
|
+
<ellipse cx="95.2" cy="26.5" rx="1.5" ry="1.5" fill="#FFFFFF" stroke={ACCENT} strokeWidth="2" strokeMiterlimit="10" />
|
|
162
|
+
<path d="M69.1,32.7l3.9-4.2c0.3-0.3,0.7-0.3,1,0l2.2,2.1c0.3,0.3,0.7,0.3,1,0l2-1.9c0.2-0.2,0.6-0.3,0.9-0.1l6,3.9c0.6,0.3,0.3,1.2-0.4,1.2H69.6C69.1,33.8,68.7,33.1,69.1,32.7z"
|
|
163
|
+
fill="#FFFFFF" stroke={ACCENT} strokeWidth="2" strokeMiterlimit="10" />
|
|
164
|
+
<path d="M62.6,18.7h41.8c0.5,0,0.9,0.3,0.9,0.7v19.5c0,0.4-0.4,0.7-0.9,0.7H62.6c-0.5,0-0.9-0.3-0.9-0.7V19.4C61.8,19,62.1,18.7,62.6,18.7z"
|
|
165
|
+
fill="none" stroke={ACCENT} strokeWidth="2" strokeMiterlimit="10" strokeDasharray="3,3" />
|
|
166
|
+
|
|
167
|
+
{/* Slide 2 (middle) */}
|
|
168
|
+
<g filter="url(#shadSpSec)">
|
|
169
|
+
<path d="M49.6,34h67.9c0.8,0,1.4,0.5,1.4,1.1v30.8c0,0.6-0.7,1.1-1.4,1.1H49.6c-0.8,0-1.4-0.5-1.4-1.1V35.1C48.2,34.5,48.8,34,49.6,34z"
|
|
170
|
+
fill="url(#slideBevelSpSec)" stroke={NEUTRAL_STROKE} strokeWidth="0.7" />
|
|
171
|
+
</g>
|
|
172
|
+
<path d="M55.7,39.4h55.7c0.4,0,0.8,0.2,0.8,0.5v21.7c0,0.3-0.3,0.5-0.8,0.5H55.7c-0.4,0-0.8-0.2-0.8-0.5V39.9C55,39.6,55.3,39.4,55.7,39.4z"
|
|
173
|
+
fill={GHOST} />
|
|
174
|
+
<ellipse cx="102.2" cy="44.3" rx="2.4" ry="2.4" fill="#FFFFFF" stroke={ACCENT} strokeWidth="2" strokeMiterlimit="10" />
|
|
175
|
+
<path d="M60.3,56.6l8.7-9.8c0.6-0.6,1.6-0.7,2.2,0l5,5c0.6,0.6,1.5,0.6,2.1,0l4.4-4.4c0.5-0.5,1.3-0.6,1.9-0.2l13.3,9.2c1.2,0.8,0.6,2.8-0.9,2.8H61.4C60.1,59.1,59.4,57.5,60.3,56.6z"
|
|
176
|
+
fill="#FFFFFF" stroke={ACCENT} strokeWidth="2" strokeMiterlimit="10" />
|
|
177
|
+
<path d="M50.4,34h66.3c0.8,0,1.4,0.5,1.4,1.1v30.8c0,0.6-0.6,1.1-1.4,1.1H50.4c-0.8,0-1.4-0.5-1.4-1.1V35.1C49.1,34.5,49.6,34,50.4,34z"
|
|
178
|
+
fill="none" stroke={ACCENT} strokeWidth="2" strokeMiterlimit="10" strokeDasharray="3,3" />
|
|
179
|
+
|
|
180
|
+
{/* Slide 3 (front/largest) */}
|
|
181
|
+
<g filter="url(#shadSpSec)">
|
|
182
|
+
<path d="M34,51.6h99c1.1,0,2,0.7,2,1.6v45c0,0.9-1,1.6-2,1.6H34c-1.1-0.1-2-0.8-2-1.6v-45C32,52.3,32.9,51.6,34,51.6z"
|
|
183
|
+
fill="url(#slideBevelSpSec)" stroke={NEUTRAL_STROKE} strokeWidth="0.7" />
|
|
184
|
+
</g>
|
|
185
|
+
<path d="M41.3,58.1h84.5c0.6,0,1.1,0.4,1.1,0.8v33.5c0,0.5-0.5,0.8-1.1,0.8H41.3c-0.6,0-1.1-0.4-1.1-0.8V58.9C40.2,58.4,40.7,58.1,41.3,58.1z"
|
|
186
|
+
fill={GHOST} />
|
|
187
|
+
<ellipse cx="110.8" cy="66.7" rx="3.5" ry="3.5" fill="#FFFFFF" stroke={ACCENT} strokeWidth="2" strokeMiterlimit="10" />
|
|
188
|
+
<path d="M47.6,84.9l12.7-14.4c0.9-0.9,2.3-1,3.2-0.1l7.2,7.3c0.9,0.9,2.2,0.9,3.1,0l6.4-6.4c0.7-0.7,1.9-0.9,2.8-0.2l19.3,13.4c1.8,1.2,0.9,4.1-1.2,4.1h-52C47.4,88.6,46.4,86.3,47.6,84.9z"
|
|
189
|
+
fill="#FFFFFF" stroke={ACCENT} strokeWidth="2" strokeMiterlimit="10" />
|
|
190
|
+
<path d="M35.2,51.6h96.6c1.1,0,2,0.7,2,1.6v45c0,0.9-0.9,1.6-2,1.6H35.2c-1.1-0.1-2-0.8-2-1.6v-45C33.2,52.3,34.1,51.6,35.2,51.6z"
|
|
191
|
+
fill="none" stroke={ACCENT} strokeWidth="2" strokeMiterlimit="10" strokeDasharray="3,3" />
|
|
192
|
+
|
|
193
|
+
{/* Scroll arrow on the right */}
|
|
194
|
+
<g fill={ACCENT} stroke={ACCENT} strokeMiterlimit="10">
|
|
195
|
+
<line x1="150.1" y1="17.6" x2="150.1" y2="88.3" />
|
|
196
|
+
<polygon points="150.1,96.8 145.3,84.8 150.1,87.7 155,84.8" />
|
|
197
|
+
</g>
|
|
198
|
+
</svg>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
203
|
+
// Create Custom Section — frame + circle + plus
|
|
204
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
205
|
+
export function CreateCustomSectionCardIcon() {
|
|
206
|
+
return (
|
|
207
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 120" width="100%" height="100%" preserveAspectRatio="xMidYMid meet">
|
|
208
|
+
<defs>
|
|
209
|
+
<BgDefs prefix="sccSec" />
|
|
210
|
+
<ShadowFilter id="shadSccSec" />
|
|
211
|
+
<VertBevel id="bevelSccSec" endColor={BEVEL_END} />
|
|
212
|
+
</defs>
|
|
213
|
+
<Bg prefix="sccSec" />
|
|
214
|
+
|
|
215
|
+
<g filter="url(#shadSccSec)">
|
|
216
|
+
<path d="M26.8,19h112.7c1.3,0,2.3,1.2,2.3,2.7v75c0,1.5-1.1,2.7-2.3,2.7H26.8c-1.3-0.1-2.3-1.3-2.3-2.7V21.6C24.5,20.2,25.5,19,26.8,19z"
|
|
217
|
+
fill="url(#bevelSccSec)" stroke={NEUTRAL_STROKE} strokeWidth="0.7" />
|
|
218
|
+
</g>
|
|
219
|
+
<path d="M26.8,19h112.7c1.3,0,2.3,1.2,2.3,2.7v75c0,1.5-1.1,2.7-2.3,2.7H26.8c-1.3-0.1-2.3-1.3-2.3-2.7V21.6C24.5,20.2,25.5,19,26.8,19z"
|
|
220
|
+
fill="none" stroke={ACCENT} strokeWidth="2" strokeMiterlimit="10" strokeDasharray="3,3" />
|
|
221
|
+
|
|
222
|
+
<circle cx="82.8" cy="59.2" r="16.9" fill="none" stroke={ACCENT} strokeWidth="2" strokeMiterlimit="10" />
|
|
223
|
+
<line x1="82.8" y1="50.2" x2="82.8" y2="67.7" fill="none" stroke={ACCENT} strokeWidth="3" strokeLinecap="round" strokeMiterlimit="10" />
|
|
224
|
+
<line x1="74" y1="58.9" x2="91.5" y2="58.9" fill="none" stroke={ACCENT} strokeWidth="3" strokeLinecap="round" strokeMiterlimit="10" />
|
|
225
|
+
</svg>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
230
|
+
// Saved Custom Section — frame + save/download icon
|
|
231
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
232
|
+
export function SavedSectionCardIcon() {
|
|
233
|
+
return (
|
|
234
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 120" width="100%" height="100%" preserveAspectRatio="xMidYMid meet">
|
|
235
|
+
<defs>
|
|
236
|
+
<BgDefs prefix="ssSec" />
|
|
237
|
+
<ShadowFilter id="shadSsSec" />
|
|
238
|
+
<VertBevel id="bevelSsSec" endColor={BEVEL_END} />
|
|
239
|
+
</defs>
|
|
240
|
+
<Bg prefix="ssSec" />
|
|
241
|
+
|
|
242
|
+
<g filter="url(#shadSsSec)">
|
|
243
|
+
<path d="M26.8,19h112.7c1.3,0,2.3,1.2,2.3,2.7v75c0,1.5-1.1,2.7-2.3,2.7H26.8c-1.3-0.1-2.3-1.3-2.3-2.7V21.6C24.5,20.2,25.5,19,26.8,19z"
|
|
244
|
+
fill="url(#bevelSsSec)" stroke={NEUTRAL_STROKE} strokeWidth="0.7" />
|
|
245
|
+
</g>
|
|
246
|
+
<path d="M26.8,19h112.7c1.3,0,2.3,1.2,2.3,2.7v75c0,1.5-1.1,2.7-2.3,2.7H26.8c-1.3-0.1-2.3-1.3-2.3-2.7V21.6C24.5,20.2,25.5,19,26.8,19z"
|
|
247
|
+
fill="none" stroke={ACCENT} strokeWidth="2" strokeMiterlimit="10" strokeDasharray="3,3" />
|
|
248
|
+
|
|
249
|
+
{/* Save/download icon */}
|
|
250
|
+
<path fill={ACCENT}
|
|
251
|
+
d="M69.4,39.5c-0.7,0-1.2,0.5-1.2,1.2v34.2c0,0.7,0.5,1.2,1.2,1.2h28.5c0.7,0,1.2-0.5,1.2-1.2V49.7c0-0.3-0.1-0.6-0.4-0.8l-9.1-9.1h0c-0.2-0.2-0.5-0.3-0.8-0.3L69.4,39.5z M70.6,41.9h17.8l8.3,8.4v23.5H70.6V41.9z M83.7,47.6c-0.7,0-1.2,0.5-1.2,1.2v11.5l-2-2h0C80.3,58.1,80,58,79.6,58c-0.3,0-0.6,0.1-0.9,0.4c-0.5,0.5-0.5,1.2,0,1.7l4,4c0.2,0.2,0.4,0.3,0.8,0.3c0.3,0,0.6-0.1,0.8-0.3l4-4c0.5-0.5,0.5-1.2,0-1.7c-0.5-0.5-1.2-0.5-1.7,0l-2,2V48.8C84.9,48.2,84.3,47.6,83.7,47.6L83.7,47.6z M78.1,65.5c-0.7,0-1.2,0.5-1.2,1.2c0,0.7,0.5,1.2,1.2,1.2h11.2c0.7,0,1.2-0.5,1.2-1.2c0-0.7-0.5-1.2-1.2-1.2H78.1z" />
|
|
252
|
+
</svg>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
257
|
+
// Lookup map for the Add Section modal
|
|
258
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
259
|
+
export const SECTION_CARD_ICONS: Record<string, React.FC> = {
|
|
260
|
+
"empty-v2": EmptySectionV2CardIcon,
|
|
261
|
+
coverSection: CoverSectionCardIcon,
|
|
262
|
+
projectGridBlock: ProjectGridCardIcon,
|
|
263
|
+
parallaxGroup: ParallaxGroupCardIcon,
|
|
264
|
+
createCustom: CreateCustomSectionCardIcon,
|
|
265
|
+
savedCustom: SavedSectionCardIcon,
|
|
266
|
+
};
|
|
@@ -119,13 +119,12 @@ export default function SectionEditorBar({ onSaveComplete }: SectionEditorBarPro
|
|
|
119
119
|
return (
|
|
120
120
|
<>
|
|
121
121
|
<div
|
|
122
|
-
className="
|
|
122
|
+
className="grid grid-cols-[1fr_auto_1fr] items-center px-4 py-2 border-b border-[#2a2a2a]"
|
|
123
123
|
style={{ backgroundColor: "#1a1a1a" }}
|
|
124
124
|
>
|
|
125
|
-
{/*
|
|
126
|
-
<div className="flex items-center gap-3">
|
|
127
|
-
|
|
128
|
-
<div className="flex items-center gap-1.5 text-xs mr-1">
|
|
125
|
+
{/* LEFT — breadcrumbs */}
|
|
126
|
+
<div className="flex items-center gap-3 justify-self-start">
|
|
127
|
+
<div className="flex items-center gap-1.5 text-xs">
|
|
129
128
|
<a
|
|
130
129
|
href={parentListUrl}
|
|
131
130
|
className="text-[#666] hover:text-[#aaa] transition-colors cursor-pointer"
|
|
@@ -145,24 +144,30 @@ export default function SectionEditorBar({ onSaveComplete }: SectionEditorBarPro
|
|
|
145
144
|
{isEditing ? "Edit Section" : "New Section"}
|
|
146
145
|
</span>
|
|
147
146
|
</div>
|
|
148
|
-
|
|
149
147
|
<div className="w-px h-4 bg-[#3a3a3a]" />
|
|
148
|
+
</div>
|
|
150
149
|
|
|
150
|
+
{/* CENTER — section name input (identity of the thing being edited) */}
|
|
151
|
+
<div className="flex items-center justify-self-center">
|
|
151
152
|
<input
|
|
152
153
|
ref={inputRef}
|
|
153
154
|
type="text"
|
|
154
155
|
value={name}
|
|
155
156
|
onChange={(e) => setName(e.target.value)}
|
|
156
157
|
placeholder="Section name..."
|
|
157
|
-
className="bg-[#2a2a2a] text-white text-sm px-3 py-1.5 rounded border border-[#3a3a3a] focus:border-[#076bff] focus:outline-none w-64 transition-colors"
|
|
158
|
+
className="bg-[#2a2a2a] text-white text-sm px-3 py-1.5 rounded border border-[#3a3a3a] focus:border-[#076bff] focus:outline-none w-64 transition-colors text-center"
|
|
158
159
|
/>
|
|
159
160
|
</div>
|
|
160
161
|
|
|
161
|
-
{/*
|
|
162
|
-
<div className="flex items-center gap-2">
|
|
163
|
-
{
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
{/* RIGHT — notification slot + actions */}
|
|
163
|
+
<div className="flex items-center gap-2 justify-self-end">
|
|
164
|
+
{/* Notification slot: save error takes precedence over unsaved-changes */}
|
|
165
|
+
{saveError ? (
|
|
166
|
+
<span className="text-xs text-red-400 mr-1">{saveError}</span>
|
|
167
|
+
) : isDirty ? (
|
|
168
|
+
<span className="text-xs text-amber-400 animate-pulse mr-1">Unsaved changes</span>
|
|
169
|
+
) : null}
|
|
170
|
+
|
|
166
171
|
{/* Delete button — only for existing sections */}
|
|
167
172
|
{isEditing && (
|
|
168
173
|
<button
|