pagiera 0.2.0-alpha.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +528 -0
- package/dist/adapters.d.ts +89 -0
- package/dist/adapters.d.ts.map +1 -0
- package/dist/adapters.js +2 -0
- package/dist/adapters.js.map +1 -0
- package/dist/client.d.ts +22 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +33 -0
- package/dist/client.js.map +1 -0
- package/dist/data.js +210 -0
- package/dist/data.js.map +7 -0
- package/dist/document.d.ts +63 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +25 -0
- package/dist/document.js.map +1 -0
- package/dist/editor.d.ts +14 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +59 -0
- package/dist/editor.js.map +1 -0
- package/dist/full-editor.d.ts +53 -0
- package/dist/full-editor.js +11117 -0
- package/dist/full-editor.js.map +7 -0
- package/dist/full.css +3498 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/app/api/ai-design/route.d.ts +3 -0
- package/dist/internal/app/api/ai-design/route.d.ts.map +1 -0
- package/dist/internal/app/api/ai-design/route.js +165 -0
- package/dist/internal/app/api/ai-design/route.js.map +1 -0
- package/dist/internal/app/p/editor/ai-panel.d.ts +27 -0
- package/dist/internal/app/p/editor/ai-panel.d.ts.map +1 -0
- package/dist/internal/app/p/editor/ai-panel.js +77 -0
- package/dist/internal/app/p/editor/ai-panel.js.map +1 -0
- package/dist/internal/app/p/editor/data-modal.d.ts +26 -0
- package/dist/internal/app/p/editor/data-modal.d.ts.map +1 -0
- package/dist/internal/app/p/editor/data-modal.js +94 -0
- package/dist/internal/app/p/editor/data-modal.js.map +1 -0
- package/dist/internal/app/p/editor/data-panel.d.ts +18 -0
- package/dist/internal/app/p/editor/data-panel.d.ts.map +1 -0
- package/dist/internal/app/p/editor/data-panel.js +45 -0
- package/dist/internal/app/p/editor/data-panel.js.map +1 -0
- package/dist/internal/app/p/editor/editor.d.ts +51 -0
- package/dist/internal/app/p/editor/editor.d.ts.map +1 -0
- package/dist/internal/app/p/editor/editor.js +1551 -0
- package/dist/internal/app/p/editor/editor.js.map +1 -0
- package/dist/internal/app/p/editor/fields.d.ts +111 -0
- package/dist/internal/app/p/editor/fields.d.ts.map +1 -0
- package/dist/internal/app/p/editor/fields.js +141 -0
- package/dist/internal/app/p/editor/fields.js.map +1 -0
- package/dist/internal/app/p/editor/inspector.d.ts +38 -0
- package/dist/internal/app/p/editor/inspector.d.ts.map +1 -0
- package/dist/internal/app/p/editor/inspector.js +336 -0
- package/dist/internal/app/p/editor/inspector.js.map +1 -0
- package/dist/internal/app/p/editor/library.d.ts +18 -0
- package/dist/internal/app/p/editor/library.d.ts.map +1 -0
- package/dist/internal/app/p/editor/library.js +61 -0
- package/dist/internal/app/p/editor/library.js.map +1 -0
- package/dist/internal/app/p/editor/parts.d.ts +75 -0
- package/dist/internal/app/p/editor/parts.d.ts.map +1 -0
- package/dist/internal/app/p/editor/parts.js +261 -0
- package/dist/internal/app/p/editor/parts.js.map +1 -0
- package/dist/internal/app/p/editor/templates-panel.d.ts +6 -0
- package/dist/internal/app/p/editor/templates-panel.d.ts.map +1 -0
- package/dist/internal/app/p/editor/templates-panel.js +12 -0
- package/dist/internal/app/p/editor/templates-panel.js.map +1 -0
- package/dist/internal/app/p/editor/use-canvas-view.d.ts +24 -0
- package/dist/internal/app/p/editor/use-canvas-view.d.ts.map +1 -0
- package/dist/internal/app/p/editor/use-canvas-view.js +242 -0
- package/dist/internal/app/p/editor/use-canvas-view.js.map +1 -0
- package/dist/internal/app/p/editor/use-editor.d.ts +48 -0
- package/dist/internal/app/p/editor/use-editor.d.ts.map +1 -0
- package/dist/internal/app/p/editor/use-editor.js +224 -0
- package/dist/internal/app/p/editor/use-editor.js.map +1 -0
- package/dist/internal/app/p/editor/variables-panel.d.ts +8 -0
- package/dist/internal/app/p/editor/variables-panel.d.ts.map +1 -0
- package/dist/internal/app/p/editor/variables-panel.js +52 -0
- package/dist/internal/app/p/editor/variables-panel.js.map +1 -0
- package/dist/internal/components/ui/select.d.ts +12 -0
- package/dist/internal/components/ui/select.d.ts.map +1 -0
- package/dist/internal/components/ui/select.js +32 -0
- package/dist/internal/components/ui/select.js.map +1 -0
- package/dist/internal/drizzle/index.d.ts +6 -0
- package/dist/internal/drizzle/index.d.ts.map +1 -0
- package/dist/internal/drizzle/index.js +20 -0
- package/dist/internal/drizzle/index.js.map +1 -0
- package/dist/internal/drizzle/schema.d.ts +405 -0
- package/dist/internal/drizzle/schema.d.ts.map +1 -0
- package/dist/internal/drizzle/schema.js +70 -0
- package/dist/internal/drizzle/schema.js.map +1 -0
- package/dist/internal/lib/data/context.d.ts +8 -0
- package/dist/internal/lib/data/context.d.ts.map +1 -0
- package/dist/internal/lib/data/context.js +17 -0
- package/dist/internal/lib/data/context.js.map +1 -0
- package/dist/internal/lib/data/source.d.ts +54 -0
- package/dist/internal/lib/data/source.d.ts.map +1 -0
- package/dist/internal/lib/data/source.js +229 -0
- package/dist/internal/lib/data/source.js.map +1 -0
- package/dist/internal/lib/editor/ai-types.d.ts +44 -0
- package/dist/internal/lib/editor/ai-types.d.ts.map +1 -0
- package/dist/internal/lib/editor/ai-types.js +2 -0
- package/dist/internal/lib/editor/ai-types.js.map +1 -0
- package/dist/internal/lib/editor/arrange.d.ts +12 -0
- package/dist/internal/lib/editor/arrange.d.ts.map +1 -0
- package/dist/internal/lib/editor/arrange.js +72 -0
- package/dist/internal/lib/editor/arrange.js.map +1 -0
- package/dist/internal/lib/editor/cascade.d.ts +39 -0
- package/dist/internal/lib/editor/cascade.d.ts.map +1 -0
- package/dist/internal/lib/editor/cascade.js +86 -0
- package/dist/internal/lib/editor/cascade.js.map +1 -0
- package/dist/internal/lib/editor/icon.d.ts +6 -0
- package/dist/internal/lib/editor/icon.d.ts.map +1 -0
- package/dist/internal/lib/editor/icon.js +13 -0
- package/dist/internal/lib/editor/icon.js.map +1 -0
- package/dist/internal/lib/editor/presets.d.ts +10 -0
- package/dist/internal/lib/editor/presets.d.ts.map +1 -0
- package/dist/internal/lib/editor/presets.js +83 -0
- package/dist/internal/lib/editor/presets.js.map +1 -0
- package/dist/internal/lib/editor/showcase.d.ts +14 -0
- package/dist/internal/lib/editor/showcase.d.ts.map +1 -0
- package/dist/internal/lib/editor/showcase.js +219 -0
- package/dist/internal/lib/editor/showcase.js.map +1 -0
- package/dist/internal/lib/editor/site-templates.d.ts +4 -0
- package/dist/internal/lib/editor/site-templates.d.ts.map +1 -0
- package/dist/internal/lib/editor/site-templates.js +156 -0
- package/dist/internal/lib/editor/site-templates.js.map +1 -0
- package/dist/internal/lib/editor/snap.d.ts +34 -0
- package/dist/internal/lib/editor/snap.d.ts.map +1 -0
- package/dist/internal/lib/editor/snap.js +70 -0
- package/dist/internal/lib/editor/snap.js.map +1 -0
- package/dist/internal/lib/editor/style.d.ts +53 -0
- package/dist/internal/lib/editor/style.d.ts.map +1 -0
- package/dist/internal/lib/editor/style.js +313 -0
- package/dist/internal/lib/editor/style.js.map +1 -0
- package/dist/internal/lib/editor/tree.d.ts +72 -0
- package/dist/internal/lib/editor/tree.d.ts.map +1 -0
- package/dist/internal/lib/editor/tree.js +301 -0
- package/dist/internal/lib/editor/tree.js.map +1 -0
- package/dist/internal/lib/editor/types.d.ts +273 -0
- package/dist/internal/lib/editor/types.d.ts.map +1 -0
- package/dist/internal/lib/editor/types.js +411 -0
- package/dist/internal/lib/editor/types.js.map +1 -0
- package/dist/internal/lib/editor/validate.d.ts +20 -0
- package/dist/internal/lib/editor/validate.d.ts.map +1 -0
- package/dist/internal/lib/editor/validate.js +467 -0
- package/dist/internal/lib/editor/validate.js.map +1 -0
- package/dist/internal/lib/pages.d.ts +83 -0
- package/dist/internal/lib/pages.d.ts.map +1 -0
- package/dist/internal/lib/pages.js +294 -0
- package/dist/internal/lib/pages.js.map +1 -0
- package/dist/internal/lib/render/bind.d.ts +18 -0
- package/dist/internal/lib/render/bind.d.ts.map +1 -0
- package/dist/internal/lib/render/bind.js +28 -0
- package/dist/internal/lib/render/bind.js.map +1 -0
- package/dist/internal/lib/render/css.d.ts +31 -0
- package/dist/internal/lib/render/css.d.ts.map +1 -0
- package/dist/internal/lib/render/css.js +239 -0
- package/dist/internal/lib/render/css.js.map +1 -0
- package/dist/internal/lib/render/load-data.d.ts +15 -0
- package/dist/internal/lib/render/load-data.d.ts.map +1 -0
- package/dist/internal/lib/render/load-data.js +39 -0
- package/dist/internal/lib/render/load-data.js.map +1 -0
- package/dist/internal/lib/render/page-render.d.ts +17 -0
- package/dist/internal/lib/render/page-render.d.ts.map +1 -0
- package/dist/internal/lib/render/page-render.js +117 -0
- package/dist/internal/lib/render/page-render.js.map +1 -0
- package/dist/internal/lib/utils.d.ts +3 -0
- package/dist/internal/lib/utils.d.ts.map +1 -0
- package/dist/internal/lib/utils.js +6 -0
- package/dist/internal/lib/utils.js.map +1 -0
- package/dist/manrope-data.d.ts +3 -0
- package/dist/manrope-data.d.ts.map +1 -0
- package/dist/manrope-data.js +3 -0
- package/dist/manrope-data.js.map +1 -0
- package/dist/page.d.ts +9 -0
- package/dist/page.d.ts.map +1 -0
- package/dist/page.js +148 -0
- package/dist/page.js.map +1 -0
- package/dist/provider.d.ts +14 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +45 -0
- package/dist/provider.js.map +1 -0
- package/dist/runtime.d.ts +8 -0
- package/dist/runtime.js +717 -0
- package/dist/runtime.js.map +7 -0
- package/dist/server.d.ts +31 -0
- package/dist/server.js +2267 -0
- package/dist/server.js.map +7 -0
- package/dist/styles.css +300 -0
- package/package.json +94 -0
- package/thumbnail.png +0 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,2267 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/internal/lib/editor/types.ts
|
|
12
|
+
function sendsBody(method) {
|
|
13
|
+
return method === "POST" || method === "PUT" || method === "PATCH";
|
|
14
|
+
}
|
|
15
|
+
function makeStyle(overrides) {
|
|
16
|
+
return { ...BASE_STYLE, ...overrides };
|
|
17
|
+
}
|
|
18
|
+
var ELEMENT_TYPES, HTTP_METHODS, DATA_SOURCE_NOT_FOUND_BEHAVIORS, DEFAULT_BREAKPOINTS, STYLE_KEYS, BASE_STYLE, ELEMENT_DEFAULTS, DEFAULT_ROOT_STYLE;
|
|
19
|
+
var init_types = __esm({
|
|
20
|
+
"src/internal/lib/editor/types.ts"() {
|
|
21
|
+
"use strict";
|
|
22
|
+
ELEMENT_TYPES = [
|
|
23
|
+
"Frame",
|
|
24
|
+
"Stack",
|
|
25
|
+
"Section",
|
|
26
|
+
"Container",
|
|
27
|
+
"Grid",
|
|
28
|
+
"Heading",
|
|
29
|
+
"Text",
|
|
30
|
+
"Image",
|
|
31
|
+
"Button",
|
|
32
|
+
"Video",
|
|
33
|
+
"Icon",
|
|
34
|
+
"Request",
|
|
35
|
+
"Repeat"
|
|
36
|
+
];
|
|
37
|
+
HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
38
|
+
DATA_SOURCE_NOT_FOUND_BEHAVIORS = ["empty", "page-404"];
|
|
39
|
+
DEFAULT_BREAKPOINTS = [
|
|
40
|
+
{ id: "desktop", name: "Desktop", width: 1280 },
|
|
41
|
+
{ id: "tablet", name: "Tablet", width: 768 },
|
|
42
|
+
{ id: "mobile", name: "Mobile", width: 375 }
|
|
43
|
+
];
|
|
44
|
+
STYLE_KEYS = [
|
|
45
|
+
"x",
|
|
46
|
+
"y",
|
|
47
|
+
"constraintX",
|
|
48
|
+
"constraintY",
|
|
49
|
+
"w",
|
|
50
|
+
"h",
|
|
51
|
+
"widthMode",
|
|
52
|
+
"heightMode",
|
|
53
|
+
"layout",
|
|
54
|
+
"direction",
|
|
55
|
+
"gap",
|
|
56
|
+
"padT",
|
|
57
|
+
"padR",
|
|
58
|
+
"padB",
|
|
59
|
+
"padL",
|
|
60
|
+
"justify",
|
|
61
|
+
"align",
|
|
62
|
+
"wrap",
|
|
63
|
+
"columns",
|
|
64
|
+
"bg",
|
|
65
|
+
"gradient",
|
|
66
|
+
"color",
|
|
67
|
+
"radius",
|
|
68
|
+
"opacity",
|
|
69
|
+
"borderW",
|
|
70
|
+
"borderC",
|
|
71
|
+
"borderStyle",
|
|
72
|
+
"shadow",
|
|
73
|
+
"rotate",
|
|
74
|
+
"fontFamily",
|
|
75
|
+
"fontSize",
|
|
76
|
+
"fontWeight",
|
|
77
|
+
"lineHeight",
|
|
78
|
+
"letterSpacing",
|
|
79
|
+
"textAlign",
|
|
80
|
+
"textTransform",
|
|
81
|
+
"overflow",
|
|
82
|
+
"position",
|
|
83
|
+
"stickyOffset",
|
|
84
|
+
"bgImage",
|
|
85
|
+
"bgSize",
|
|
86
|
+
"bgPosition",
|
|
87
|
+
"blur",
|
|
88
|
+
"backdropBlur",
|
|
89
|
+
"blendMode",
|
|
90
|
+
"scale",
|
|
91
|
+
"aspectRatio",
|
|
92
|
+
"entrance",
|
|
93
|
+
"entranceDuration",
|
|
94
|
+
"entranceDelay",
|
|
95
|
+
"entranceCurve",
|
|
96
|
+
"entranceBezier",
|
|
97
|
+
"springStiffness",
|
|
98
|
+
"springDamping",
|
|
99
|
+
"cursor",
|
|
100
|
+
"hidden"
|
|
101
|
+
];
|
|
102
|
+
BASE_STYLE = {
|
|
103
|
+
x: 0,
|
|
104
|
+
y: 0,
|
|
105
|
+
constraintX: "start",
|
|
106
|
+
constraintY: "start",
|
|
107
|
+
w: 200,
|
|
108
|
+
h: 100,
|
|
109
|
+
widthMode: "fixed",
|
|
110
|
+
heightMode: "fixed",
|
|
111
|
+
layout: "absolute",
|
|
112
|
+
direction: "column",
|
|
113
|
+
gap: 0,
|
|
114
|
+
padT: 0,
|
|
115
|
+
padR: 0,
|
|
116
|
+
padB: 0,
|
|
117
|
+
padL: 0,
|
|
118
|
+
justify: "start",
|
|
119
|
+
align: "start",
|
|
120
|
+
wrap: false,
|
|
121
|
+
columns: 3,
|
|
122
|
+
bg: "transparent",
|
|
123
|
+
gradient: "",
|
|
124
|
+
color: "#27272a",
|
|
125
|
+
radius: 0,
|
|
126
|
+
opacity: 100,
|
|
127
|
+
borderW: 0,
|
|
128
|
+
borderC: "transparent",
|
|
129
|
+
borderStyle: "solid",
|
|
130
|
+
shadow: "",
|
|
131
|
+
rotate: 0,
|
|
132
|
+
fontFamily: "inherit",
|
|
133
|
+
fontSize: 16,
|
|
134
|
+
fontWeight: "normal",
|
|
135
|
+
lineHeight: 1.5,
|
|
136
|
+
letterSpacing: 0,
|
|
137
|
+
textAlign: "left",
|
|
138
|
+
textTransform: "none",
|
|
139
|
+
overflow: "visible",
|
|
140
|
+
position: "static",
|
|
141
|
+
stickyOffset: 0,
|
|
142
|
+
bgImage: "",
|
|
143
|
+
bgSize: "cover",
|
|
144
|
+
bgPosition: "center",
|
|
145
|
+
blur: 0,
|
|
146
|
+
backdropBlur: 0,
|
|
147
|
+
blendMode: "normal",
|
|
148
|
+
scale: 100,
|
|
149
|
+
aspectRatio: "",
|
|
150
|
+
entrance: "none",
|
|
151
|
+
entranceDuration: 600,
|
|
152
|
+
entranceDelay: 0,
|
|
153
|
+
entranceCurve: "ease",
|
|
154
|
+
entranceBezier: "0.44, 0, 0.56, 1",
|
|
155
|
+
springStiffness: 300,
|
|
156
|
+
springDamping: 30,
|
|
157
|
+
cursor: "auto",
|
|
158
|
+
hidden: false
|
|
159
|
+
};
|
|
160
|
+
ELEMENT_DEFAULTS = {
|
|
161
|
+
Frame: {
|
|
162
|
+
style: {
|
|
163
|
+
w: 640,
|
|
164
|
+
h: 420,
|
|
165
|
+
widthMode: "fixed",
|
|
166
|
+
heightMode: "fixed",
|
|
167
|
+
layout: "absolute",
|
|
168
|
+
direction: "column",
|
|
169
|
+
overflow: "hidden",
|
|
170
|
+
bg: "#ffffff",
|
|
171
|
+
borderW: 1,
|
|
172
|
+
borderC: "#e4e4e7",
|
|
173
|
+
radius: 12
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
Stack: {
|
|
177
|
+
style: {
|
|
178
|
+
w: 600,
|
|
179
|
+
h: 160,
|
|
180
|
+
widthMode: "fill",
|
|
181
|
+
heightMode: "auto",
|
|
182
|
+
layout: "stack",
|
|
183
|
+
direction: "column",
|
|
184
|
+
gap: 16,
|
|
185
|
+
padT: 0,
|
|
186
|
+
padR: 0,
|
|
187
|
+
padB: 0,
|
|
188
|
+
padL: 0,
|
|
189
|
+
justify: "start",
|
|
190
|
+
align: "stretch",
|
|
191
|
+
bg: "transparent"
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
Section: {
|
|
195
|
+
style: {
|
|
196
|
+
w: 1280,
|
|
197
|
+
h: 480,
|
|
198
|
+
widthMode: "fill",
|
|
199
|
+
heightMode: "auto",
|
|
200
|
+
layout: "stack",
|
|
201
|
+
direction: "column",
|
|
202
|
+
gap: 24,
|
|
203
|
+
padT: 64,
|
|
204
|
+
padR: 48,
|
|
205
|
+
padB: 64,
|
|
206
|
+
padL: 48,
|
|
207
|
+
justify: "start",
|
|
208
|
+
align: "stretch",
|
|
209
|
+
bg: "#ffffff"
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
Container: {
|
|
213
|
+
style: {
|
|
214
|
+
w: 600,
|
|
215
|
+
h: 240,
|
|
216
|
+
widthMode: "fill",
|
|
217
|
+
heightMode: "auto",
|
|
218
|
+
layout: "stack",
|
|
219
|
+
direction: "column",
|
|
220
|
+
gap: 16,
|
|
221
|
+
padT: 24,
|
|
222
|
+
padR: 24,
|
|
223
|
+
padB: 24,
|
|
224
|
+
padL: 24,
|
|
225
|
+
align: "stretch",
|
|
226
|
+
bg: "transparent",
|
|
227
|
+
borderW: 1,
|
|
228
|
+
borderC: "#e4e4e7",
|
|
229
|
+
radius: 12
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
Grid: {
|
|
233
|
+
style: {
|
|
234
|
+
w: 900,
|
|
235
|
+
h: 300,
|
|
236
|
+
widthMode: "fill",
|
|
237
|
+
heightMode: "auto",
|
|
238
|
+
layout: "stack",
|
|
239
|
+
direction: "row",
|
|
240
|
+
columns: 3,
|
|
241
|
+
gap: 24,
|
|
242
|
+
align: "stretch"
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
Heading: {
|
|
246
|
+
style: {
|
|
247
|
+
w: 480,
|
|
248
|
+
h: 48,
|
|
249
|
+
widthMode: "fill",
|
|
250
|
+
heightMode: "auto",
|
|
251
|
+
fontSize: 40,
|
|
252
|
+
fontWeight: "bold",
|
|
253
|
+
lineHeight: 1.2,
|
|
254
|
+
letterSpacing: -0.5,
|
|
255
|
+
color: "#18181b"
|
|
256
|
+
},
|
|
257
|
+
props: { content: "Heading" }
|
|
258
|
+
},
|
|
259
|
+
Text: {
|
|
260
|
+
style: {
|
|
261
|
+
w: 480,
|
|
262
|
+
h: 24,
|
|
263
|
+
widthMode: "fill",
|
|
264
|
+
heightMode: "auto",
|
|
265
|
+
fontSize: 16,
|
|
266
|
+
lineHeight: 1.6,
|
|
267
|
+
color: "#52525b"
|
|
268
|
+
},
|
|
269
|
+
props: { content: "Text block content" }
|
|
270
|
+
},
|
|
271
|
+
Image: {
|
|
272
|
+
style: { w: 400, h: 260, widthMode: "fill", bg: "#e4e4e7", radius: 12 },
|
|
273
|
+
props: { src: "", alt: "", objectFit: "cover" }
|
|
274
|
+
},
|
|
275
|
+
Button: {
|
|
276
|
+
style: {
|
|
277
|
+
w: 140,
|
|
278
|
+
h: 44,
|
|
279
|
+
widthMode: "auto",
|
|
280
|
+
heightMode: "auto",
|
|
281
|
+
layout: "stack",
|
|
282
|
+
direction: "row",
|
|
283
|
+
justify: "center",
|
|
284
|
+
align: "center",
|
|
285
|
+
padT: 12,
|
|
286
|
+
padR: 22,
|
|
287
|
+
padB: 12,
|
|
288
|
+
padL: 22,
|
|
289
|
+
bg: "#2563eb",
|
|
290
|
+
color: "#ffffff",
|
|
291
|
+
radius: 8,
|
|
292
|
+
fontSize: 15,
|
|
293
|
+
fontWeight: "500",
|
|
294
|
+
textAlign: "center"
|
|
295
|
+
},
|
|
296
|
+
props: { content: "Button", href: "", target: "_self" }
|
|
297
|
+
},
|
|
298
|
+
Video: {
|
|
299
|
+
style: { w: 640, h: 360, widthMode: "fill", bg: "#18181b", radius: 12 },
|
|
300
|
+
props: { src: "" }
|
|
301
|
+
},
|
|
302
|
+
Icon: {
|
|
303
|
+
style: { w: 24, h: 24, widthMode: "fixed", heightMode: "fixed", color: "#5402E6" },
|
|
304
|
+
props: { iconName: "star" }
|
|
305
|
+
},
|
|
306
|
+
Request: {
|
|
307
|
+
style: {
|
|
308
|
+
w: 900,
|
|
309
|
+
h: 200,
|
|
310
|
+
widthMode: "fill",
|
|
311
|
+
heightMode: "auto",
|
|
312
|
+
layout: "stack",
|
|
313
|
+
direction: "column",
|
|
314
|
+
gap: 16,
|
|
315
|
+
align: "stretch"
|
|
316
|
+
},
|
|
317
|
+
props: { sourceId: "" }
|
|
318
|
+
},
|
|
319
|
+
Repeat: {
|
|
320
|
+
style: {
|
|
321
|
+
w: 900,
|
|
322
|
+
h: 200,
|
|
323
|
+
widthMode: "fill",
|
|
324
|
+
heightMode: "auto",
|
|
325
|
+
layout: "stack",
|
|
326
|
+
direction: "row",
|
|
327
|
+
gap: 24,
|
|
328
|
+
align: "stretch",
|
|
329
|
+
wrap: true
|
|
330
|
+
},
|
|
331
|
+
props: { sourceId: "" }
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
DEFAULT_ROOT_STYLE = {
|
|
335
|
+
documentMode: "page",
|
|
336
|
+
maxWidth: 1280,
|
|
337
|
+
canvasHeight: 800,
|
|
338
|
+
fullWidth: false,
|
|
339
|
+
bg: "#ffffff",
|
|
340
|
+
layout: "stack",
|
|
341
|
+
direction: "column",
|
|
342
|
+
gap: 0,
|
|
343
|
+
padT: 0,
|
|
344
|
+
padR: 0,
|
|
345
|
+
padB: 0,
|
|
346
|
+
padL: 0,
|
|
347
|
+
align: "stretch",
|
|
348
|
+
fontFamily: "inherit"
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// src/internal/drizzle/schema.ts
|
|
354
|
+
import {
|
|
355
|
+
index,
|
|
356
|
+
integer,
|
|
357
|
+
jsonb,
|
|
358
|
+
pgTable,
|
|
359
|
+
text,
|
|
360
|
+
timestamp,
|
|
361
|
+
uniqueIndex,
|
|
362
|
+
uuid
|
|
363
|
+
} from "drizzle-orm/pg-core";
|
|
364
|
+
var sites, pages, pageRevisions;
|
|
365
|
+
var init_schema = __esm({
|
|
366
|
+
"src/internal/drizzle/schema.ts"() {
|
|
367
|
+
"use strict";
|
|
368
|
+
init_types();
|
|
369
|
+
sites = pgTable(
|
|
370
|
+
"sites",
|
|
371
|
+
{
|
|
372
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
373
|
+
name: text("name").notNull(),
|
|
374
|
+
slug: text("slug").notNull(),
|
|
375
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
376
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
377
|
+
},
|
|
378
|
+
(t) => [uniqueIndex("sites_slug_key").on(t.slug)]
|
|
379
|
+
);
|
|
380
|
+
pages = pgTable(
|
|
381
|
+
"pages",
|
|
382
|
+
{
|
|
383
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
384
|
+
siteId: uuid("site_id").notNull().references(() => sites.id, { onDelete: "cascade" }),
|
|
385
|
+
name: text("name").notNull(),
|
|
386
|
+
slug: text("slug").notNull(),
|
|
387
|
+
/**
|
|
388
|
+
* Flat list of canvas elements; nesting is expressed through
|
|
389
|
+
* `parentId`, mirroring the editor's in-memory state exactly so a save
|
|
390
|
+
* is a single atomic write instead of a per-element diff.
|
|
391
|
+
*/
|
|
392
|
+
elements: jsonb("elements").$type().notNull().default([]),
|
|
393
|
+
/** Page-level container settings: max width, background, root layout. */
|
|
394
|
+
rootStyle: jsonb("root_style").$type().notNull().default(DEFAULT_ROOT_STYLE),
|
|
395
|
+
/** JSON endpoints this page pulls content from. */
|
|
396
|
+
dataSources: jsonb("data_sources").$type().notNull().default([]),
|
|
397
|
+
/** Bumped on every write; used for optimistic concurrency on save. */
|
|
398
|
+
version: integer("version").notNull().default(1),
|
|
399
|
+
/**
|
|
400
|
+
* Snapshot taken by Publish. The public site reads these columns, so
|
|
401
|
+
* drafts never leak before the author is ready.
|
|
402
|
+
*/
|
|
403
|
+
publishedElements: jsonb("published_elements").$type(),
|
|
404
|
+
publishedRootStyle: jsonb("published_root_style").$type(),
|
|
405
|
+
publishedDataSources: jsonb("published_data_sources").$type(),
|
|
406
|
+
publishedAt: timestamp("published_at", { withTimezone: true }),
|
|
407
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
408
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
409
|
+
},
|
|
410
|
+
(t) => [
|
|
411
|
+
uniqueIndex("pages_site_id_slug_key").on(t.siteId, t.slug),
|
|
412
|
+
index("pages_site_id_idx").on(t.siteId)
|
|
413
|
+
]
|
|
414
|
+
);
|
|
415
|
+
pageRevisions = pgTable(
|
|
416
|
+
"page_revisions",
|
|
417
|
+
{
|
|
418
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
419
|
+
pageId: uuid("page_id").notNull().references(() => pages.id, { onDelete: "cascade" }),
|
|
420
|
+
version: integer("version").notNull(),
|
|
421
|
+
elements: jsonb("elements").$type().notNull(),
|
|
422
|
+
rootStyle: jsonb("root_style").$type(),
|
|
423
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
424
|
+
},
|
|
425
|
+
(t) => [index("page_revisions_page_id_created_at_idx").on(t.pageId, t.createdAt)]
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// src/internal/drizzle/index.ts
|
|
431
|
+
import { drizzle } from "drizzle-orm/node-postgres";
|
|
432
|
+
import { Pool } from "pg";
|
|
433
|
+
var connectionString, globalForDb, pool, db;
|
|
434
|
+
var init_drizzle = __esm({
|
|
435
|
+
"src/internal/drizzle/index.ts"() {
|
|
436
|
+
"use strict";
|
|
437
|
+
init_schema();
|
|
438
|
+
connectionString = process.env.DATABASE_URL;
|
|
439
|
+
if (!connectionString) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
"DATABASE_URL is not set. Copy .env.example to .env.local and start Postgres with `docker compose up -d`."
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
globalForDb = globalThis;
|
|
445
|
+
pool = globalForDb.pagieraPool ?? new Pool({ connectionString, max: 10, idleTimeoutMillis: 3e4 });
|
|
446
|
+
if (process.env.NODE_ENV !== "production") {
|
|
447
|
+
globalForDb.pagieraPool = pool;
|
|
448
|
+
}
|
|
449
|
+
db = drizzle({ client: pool });
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// src/internal/lib/pages.ts
|
|
454
|
+
var pages_exports = {};
|
|
455
|
+
__export(pages_exports, {
|
|
456
|
+
DEFAULT_PAGE_SLUG: () => DEFAULT_PAGE_SLUG,
|
|
457
|
+
DEFAULT_SITE_SLUG: () => DEFAULT_SITE_SLUG,
|
|
458
|
+
createPage: () => createPage,
|
|
459
|
+
deletePage: () => deletePage,
|
|
460
|
+
duplicatePage: () => duplicatePage,
|
|
461
|
+
getOrCreateDefaultPage: () => getOrCreateDefaultPage,
|
|
462
|
+
getPage: () => getPage,
|
|
463
|
+
getPublishedPage: () => getPublishedPage,
|
|
464
|
+
installTemplatePages: () => installTemplatePages,
|
|
465
|
+
listPageDocuments: () => listPageDocuments,
|
|
466
|
+
listPages: () => listPages,
|
|
467
|
+
listPublishedSlugs: () => listPublishedSlugs,
|
|
468
|
+
publishPage: () => publishPage,
|
|
469
|
+
renamePage: () => renamePage,
|
|
470
|
+
savePageDocument: () => savePageDocument,
|
|
471
|
+
unpublishPage: () => unpublishPage
|
|
472
|
+
});
|
|
473
|
+
import { and, asc, desc, eq, isNotNull, ne, notInArray, sql } from "drizzle-orm";
|
|
474
|
+
async function getDefaultSiteId() {
|
|
475
|
+
const [existing] = await db.select({ id: sites.id }).from(sites).where(eq(sites.slug, DEFAULT_SITE_SLUG)).limit(1);
|
|
476
|
+
if (existing) return existing.id;
|
|
477
|
+
await db.insert(sites).values({ name: "Default site", slug: DEFAULT_SITE_SLUG }).onConflictDoNothing({ target: sites.slug });
|
|
478
|
+
const [created] = await db.select({ id: sites.id }).from(sites).where(eq(sites.slug, DEFAULT_SITE_SLUG)).limit(1);
|
|
479
|
+
if (!created) throw new Error("Failed to create the default site");
|
|
480
|
+
return created.id;
|
|
481
|
+
}
|
|
482
|
+
async function listPages() {
|
|
483
|
+
const siteId = await getDefaultSiteId();
|
|
484
|
+
return db.select({
|
|
485
|
+
id: pages.id,
|
|
486
|
+
name: pages.name,
|
|
487
|
+
slug: pages.slug,
|
|
488
|
+
publishedAt: pages.publishedAt,
|
|
489
|
+
updatedAt: pages.updatedAt
|
|
490
|
+
}).from(pages).where(eq(pages.siteId, siteId)).orderBy(asc(pages.createdAt));
|
|
491
|
+
}
|
|
492
|
+
async function listPageDocuments() {
|
|
493
|
+
const siteId = await getDefaultSiteId();
|
|
494
|
+
return db.select({ id: pages.id, name: pages.name, elements: pages.elements }).from(pages).where(eq(pages.siteId, siteId)).orderBy(asc(pages.createdAt));
|
|
495
|
+
}
|
|
496
|
+
async function getPage(pageId) {
|
|
497
|
+
const [page] = await db.select().from(pages).where(eq(pages.id, pageId)).limit(1);
|
|
498
|
+
return page;
|
|
499
|
+
}
|
|
500
|
+
async function getPublishedPage(slug) {
|
|
501
|
+
const siteId = await getDefaultSiteId();
|
|
502
|
+
const [page] = await db.select({
|
|
503
|
+
name: pages.name,
|
|
504
|
+
elements: pages.publishedElements,
|
|
505
|
+
rootStyle: pages.publishedRootStyle,
|
|
506
|
+
dataSources: pages.publishedDataSources,
|
|
507
|
+
publishedAt: pages.publishedAt
|
|
508
|
+
}).from(pages).where(
|
|
509
|
+
and(
|
|
510
|
+
eq(pages.siteId, siteId),
|
|
511
|
+
eq(pages.slug, slug),
|
|
512
|
+
isNotNull(pages.publishedAt)
|
|
513
|
+
)
|
|
514
|
+
).limit(1);
|
|
515
|
+
if (!page?.elements) return void 0;
|
|
516
|
+
return {
|
|
517
|
+
name: page.name,
|
|
518
|
+
elements: page.elements,
|
|
519
|
+
rootStyle: page.rootStyle ?? DEFAULT_ROOT_STYLE,
|
|
520
|
+
dataSources: page.dataSources ?? [],
|
|
521
|
+
publishedAt: page.publishedAt
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
async function listPublishedSlugs() {
|
|
525
|
+
const siteId = await getDefaultSiteId();
|
|
526
|
+
return db.select({ slug: pages.slug }).from(pages).where(and(eq(pages.siteId, siteId), isNotNull(pages.publishedAt)));
|
|
527
|
+
}
|
|
528
|
+
async function getOrCreateDefaultPage() {
|
|
529
|
+
const siteId = await getDefaultSiteId();
|
|
530
|
+
const [existing] = await db.select().from(pages).where(and(eq(pages.siteId, siteId), eq(pages.slug, DEFAULT_PAGE_SLUG))).limit(1);
|
|
531
|
+
if (existing) return existing;
|
|
532
|
+
await db.insert(pages).values({
|
|
533
|
+
siteId,
|
|
534
|
+
name: "Home",
|
|
535
|
+
slug: DEFAULT_PAGE_SLUG,
|
|
536
|
+
elements: [],
|
|
537
|
+
rootStyle: DEFAULT_ROOT_STYLE
|
|
538
|
+
}).onConflictDoNothing({ target: [pages.siteId, pages.slug] });
|
|
539
|
+
const [page] = await db.select().from(pages).where(and(eq(pages.siteId, siteId), eq(pages.slug, DEFAULT_PAGE_SLUG))).limit(1);
|
|
540
|
+
if (!page) throw new Error("Failed to create the default page");
|
|
541
|
+
return page;
|
|
542
|
+
}
|
|
543
|
+
async function savePageDocument(pageId, elements, rootStyle, dataSources, expectedVersion) {
|
|
544
|
+
const [updated] = await db.update(pages).set({
|
|
545
|
+
elements,
|
|
546
|
+
rootStyle,
|
|
547
|
+
dataSources,
|
|
548
|
+
version: sql`${pages.version} + 1`,
|
|
549
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
550
|
+
}).where(and(eq(pages.id, pageId), eq(pages.version, expectedVersion))).returning({ version: pages.version });
|
|
551
|
+
if (!updated) {
|
|
552
|
+
const [current] = await db.select({
|
|
553
|
+
version: pages.version,
|
|
554
|
+
elements: pages.elements,
|
|
555
|
+
rootStyle: pages.rootStyle,
|
|
556
|
+
dataSources: pages.dataSources
|
|
557
|
+
}).from(pages).where(eq(pages.id, pageId)).limit(1);
|
|
558
|
+
if (!current) throw new Error(`Page ${pageId} no longer exists`);
|
|
559
|
+
return {
|
|
560
|
+
status: "conflict",
|
|
561
|
+
version: current.version,
|
|
562
|
+
elements: current.elements,
|
|
563
|
+
rootStyle: current.rootStyle ?? DEFAULT_ROOT_STYLE,
|
|
564
|
+
dataSources: current.dataSources ?? []
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
await db.insert(pageRevisions).values({ pageId, version: updated.version, elements, rootStyle });
|
|
568
|
+
await pruneRevisions(pageId);
|
|
569
|
+
return { status: "saved", version: updated.version };
|
|
570
|
+
}
|
|
571
|
+
async function publishPage(pageId) {
|
|
572
|
+
const [updated] = await db.update(pages).set({
|
|
573
|
+
publishedElements: sql`${pages.elements}`,
|
|
574
|
+
publishedRootStyle: sql`${pages.rootStyle}`,
|
|
575
|
+
publishedDataSources: sql`${pages.dataSources}`,
|
|
576
|
+
publishedAt: /* @__PURE__ */ new Date()
|
|
577
|
+
}).where(eq(pages.id, pageId)).returning({ slug: pages.slug, publishedAt: pages.publishedAt });
|
|
578
|
+
return updated;
|
|
579
|
+
}
|
|
580
|
+
async function unpublishPage(pageId) {
|
|
581
|
+
await db.update(pages).set({
|
|
582
|
+
publishedAt: null,
|
|
583
|
+
publishedElements: null,
|
|
584
|
+
publishedRootStyle: null,
|
|
585
|
+
publishedDataSources: null
|
|
586
|
+
}).where(eq(pages.id, pageId));
|
|
587
|
+
}
|
|
588
|
+
async function createPage(name, slug) {
|
|
589
|
+
const siteId = await getDefaultSiteId();
|
|
590
|
+
const [created] = await db.insert(pages).values({
|
|
591
|
+
siteId,
|
|
592
|
+
name,
|
|
593
|
+
slug,
|
|
594
|
+
elements: [],
|
|
595
|
+
rootStyle: DEFAULT_ROOT_STYLE
|
|
596
|
+
}).onConflictDoNothing({ target: [pages.siteId, pages.slug] }).returning({ id: pages.id });
|
|
597
|
+
return created?.id;
|
|
598
|
+
}
|
|
599
|
+
async function installTemplatePages(templatePages) {
|
|
600
|
+
const siteId = await getDefaultSiteId();
|
|
601
|
+
if (templatePages.length === 0) return void 0;
|
|
602
|
+
if (!templatePages.some((template) => template.slug === DEFAULT_PAGE_SLUG)) {
|
|
603
|
+
throw new Error("Every site template must include a home page.");
|
|
604
|
+
}
|
|
605
|
+
return db.transaction(async (tx) => {
|
|
606
|
+
await tx.delete(pages).where(eq(pages.siteId, siteId));
|
|
607
|
+
const created = await tx.insert(pages).values(templatePages.map((template) => ({
|
|
608
|
+
siteId,
|
|
609
|
+
name: template.name,
|
|
610
|
+
slug: template.slug,
|
|
611
|
+
elements: template.elements,
|
|
612
|
+
rootStyle: template.rootStyle,
|
|
613
|
+
dataSources: template.dataSources ?? []
|
|
614
|
+
}))).returning({ id: pages.id, slug: pages.slug });
|
|
615
|
+
return created.find((page) => page.slug === templatePages[0].slug)?.id;
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
async function duplicatePage(pageId, name, slug) {
|
|
619
|
+
const source = await getPage(pageId);
|
|
620
|
+
if (!source) return void 0;
|
|
621
|
+
const [created] = await db.insert(pages).values({
|
|
622
|
+
siteId: source.siteId,
|
|
623
|
+
name,
|
|
624
|
+
slug,
|
|
625
|
+
elements: source.elements,
|
|
626
|
+
rootStyle: source.rootStyle ?? DEFAULT_ROOT_STYLE
|
|
627
|
+
}).onConflictDoNothing({ target: [pages.siteId, pages.slug] }).returning({ id: pages.id });
|
|
628
|
+
return created?.id;
|
|
629
|
+
}
|
|
630
|
+
async function renamePage(pageId, name, slug) {
|
|
631
|
+
const current = await getPage(pageId);
|
|
632
|
+
if (!current || current.slug === DEFAULT_PAGE_SLUG && slug !== DEFAULT_PAGE_SLUG) return void 0;
|
|
633
|
+
const [updated] = await db.update(pages).set({ name, slug, updatedAt: /* @__PURE__ */ new Date() }).where(eq(pages.id, pageId)).returning({ id: pages.id });
|
|
634
|
+
return updated?.id;
|
|
635
|
+
}
|
|
636
|
+
async function deletePage(pageId) {
|
|
637
|
+
const siteId = await getDefaultSiteId();
|
|
638
|
+
await getOrCreateDefaultPage();
|
|
639
|
+
const [target] = await db.select({ slug: pages.slug }).from(pages).where(and(eq(pages.siteId, siteId), eq(pages.id, pageId))).limit(1);
|
|
640
|
+
if (!target) return { deleted: false, reason: "not-found" };
|
|
641
|
+
if (target.slug === DEFAULT_PAGE_SLUG) {
|
|
642
|
+
return { deleted: false, reason: "home-required" };
|
|
643
|
+
}
|
|
644
|
+
const remaining = await db.select({ id: pages.id }).from(pages).where(and(eq(pages.siteId, siteId), ne(pages.id, pageId))).limit(1);
|
|
645
|
+
if (remaining.length === 0) return { deleted: false, reason: "last-page" };
|
|
646
|
+
await db.delete(pages).where(eq(pages.id, pageId));
|
|
647
|
+
return { deleted: true, fallbackId: remaining[0].id };
|
|
648
|
+
}
|
|
649
|
+
async function pruneRevisions(pageId) {
|
|
650
|
+
const keep = await db.select({ id: pageRevisions.id }).from(pageRevisions).where(eq(pageRevisions.pageId, pageId)).orderBy(desc(pageRevisions.createdAt)).limit(REVISION_LIMIT);
|
|
651
|
+
if (keep.length < REVISION_LIMIT) return;
|
|
652
|
+
await db.delete(pageRevisions).where(
|
|
653
|
+
and(
|
|
654
|
+
eq(pageRevisions.pageId, pageId),
|
|
655
|
+
notInArray(
|
|
656
|
+
pageRevisions.id,
|
|
657
|
+
keep.map((r) => r.id)
|
|
658
|
+
)
|
|
659
|
+
)
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
var DEFAULT_SITE_SLUG, DEFAULT_PAGE_SLUG, REVISION_LIMIT;
|
|
663
|
+
var init_pages = __esm({
|
|
664
|
+
"src/internal/lib/pages.ts"() {
|
|
665
|
+
"use strict";
|
|
666
|
+
init_drizzle();
|
|
667
|
+
init_schema();
|
|
668
|
+
init_types();
|
|
669
|
+
DEFAULT_SITE_SLUG = "default";
|
|
670
|
+
DEFAULT_PAGE_SLUG = "home";
|
|
671
|
+
REVISION_LIMIT = 50;
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
// src/internal/lib/editor/validate.ts
|
|
676
|
+
var validate_exports = {};
|
|
677
|
+
__export(validate_exports, {
|
|
678
|
+
InvalidElementsError: () => InvalidElementsError,
|
|
679
|
+
MAX_ELEMENTS: () => MAX_ELEMENTS,
|
|
680
|
+
parseDataSources: () => parseDataSources,
|
|
681
|
+
parseElements: () => parseElements,
|
|
682
|
+
parseName: () => parseName,
|
|
683
|
+
parseRootStyle: () => parseRootStyle,
|
|
684
|
+
parseSlug: () => parseSlug
|
|
685
|
+
});
|
|
686
|
+
function num(value, fallback, min, max) {
|
|
687
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
688
|
+
if (!Number.isFinite(n)) return fallback;
|
|
689
|
+
return Math.min(max, Math.max(min, n));
|
|
690
|
+
}
|
|
691
|
+
function scalePercent(value, fallback) {
|
|
692
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
693
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
694
|
+
const normalized = parsed > 0 && parsed <= 5 ? parsed * 100 : parsed;
|
|
695
|
+
return Math.min(500, Math.max(1, normalized));
|
|
696
|
+
}
|
|
697
|
+
function str(value, max) {
|
|
698
|
+
if (typeof value !== "string") return void 0;
|
|
699
|
+
return value.length > max ? value.slice(0, max) : value;
|
|
700
|
+
}
|
|
701
|
+
function oneOf(value, allowed, fallback) {
|
|
702
|
+
return typeof value === "string" && allowed.includes(value) ? value : fallback;
|
|
703
|
+
}
|
|
704
|
+
function cssValue(value, fallback) {
|
|
705
|
+
const raw = str(value, MAX_SHORT_STRING);
|
|
706
|
+
if (raw === void 0) return fallback;
|
|
707
|
+
const cleaned = raw.replace(/[;{}<>\\]/g, "").replace(/@import/gi, "");
|
|
708
|
+
return cleaned.trim();
|
|
709
|
+
}
|
|
710
|
+
function safeUrl(value) {
|
|
711
|
+
const raw = str(value, MAX_URL);
|
|
712
|
+
if (!raw) return void 0;
|
|
713
|
+
const trimmed = raw.trim();
|
|
714
|
+
if (trimmed === "") return "";
|
|
715
|
+
if (trimmed.startsWith("/") || trimmed.startsWith("#")) return trimmed;
|
|
716
|
+
if (/^https?:\/\//i.test(trimmed)) return trimmed;
|
|
717
|
+
if (/^data:image\//i.test(trimmed)) return trimmed;
|
|
718
|
+
return void 0;
|
|
719
|
+
}
|
|
720
|
+
function imageUrl(value) {
|
|
721
|
+
const raw = safeUrl(value);
|
|
722
|
+
if (!raw) return "";
|
|
723
|
+
return /["'()\\]/.test(raw) ? "" : raw;
|
|
724
|
+
}
|
|
725
|
+
function bindingPath(value) {
|
|
726
|
+
const raw = str(value, MAX_SHORT_STRING)?.trim();
|
|
727
|
+
if (!raw) return void 0;
|
|
728
|
+
return /^[A-Za-z0-9_$][A-Za-z0-9_$.-]*$/.test(raw) ? raw : void 0;
|
|
729
|
+
}
|
|
730
|
+
function parseStyle(input, base) {
|
|
731
|
+
const raw = input && typeof input === "object" ? input : {};
|
|
732
|
+
return {
|
|
733
|
+
x: num(raw.x, base.x, -COORD_LIMIT, COORD_LIMIT),
|
|
734
|
+
y: num(raw.y, base.y, -COORD_LIMIT, COORD_LIMIT),
|
|
735
|
+
constraintX: oneOf(
|
|
736
|
+
raw.constraintX,
|
|
737
|
+
["start", "center", "end", "stretch"],
|
|
738
|
+
base.constraintX
|
|
739
|
+
),
|
|
740
|
+
constraintY: oneOf(
|
|
741
|
+
raw.constraintY,
|
|
742
|
+
["start", "center", "end", "stretch"],
|
|
743
|
+
base.constraintY
|
|
744
|
+
),
|
|
745
|
+
w: num(raw.w, base.w, 1, COORD_LIMIT),
|
|
746
|
+
h: num(raw.h, base.h, 1, COORD_LIMIT),
|
|
747
|
+
widthMode: oneOf(raw.widthMode, ["fixed", "fill", "auto"], base.widthMode),
|
|
748
|
+
heightMode: oneOf(raw.heightMode, ["fixed", "fill", "auto"], base.heightMode),
|
|
749
|
+
layout: oneOf(raw.layout, ["absolute", "stack"], base.layout),
|
|
750
|
+
direction: oneOf(raw.direction, ["row", "column"], base.direction),
|
|
751
|
+
gap: num(raw.gap, base.gap, 0, 999),
|
|
752
|
+
padT: num(raw.padT, base.padT, 0, 9999),
|
|
753
|
+
padR: num(raw.padR, base.padR, 0, 9999),
|
|
754
|
+
padB: num(raw.padB, base.padB, 0, 9999),
|
|
755
|
+
padL: num(raw.padL, base.padL, 0, 9999),
|
|
756
|
+
justify: oneOf(raw.justify, ["start", "center", "end", "between"], base.justify),
|
|
757
|
+
align: oneOf(raw.align, ["start", "center", "end", "stretch"], base.align),
|
|
758
|
+
wrap: raw.wrap === true,
|
|
759
|
+
columns: num(raw.columns, base.columns, 1, 12),
|
|
760
|
+
bg: cssValue(raw.bg, base.bg),
|
|
761
|
+
gradient: cssValue(raw.gradient, base.gradient),
|
|
762
|
+
color: cssValue(raw.color, base.color),
|
|
763
|
+
radius: num(raw.radius, base.radius, 0, 9999),
|
|
764
|
+
opacity: num(raw.opacity, base.opacity, 0, 100),
|
|
765
|
+
borderW: num(raw.borderW, base.borderW, 0, 999),
|
|
766
|
+
borderC: cssValue(raw.borderC, base.borderC),
|
|
767
|
+
borderStyle: oneOf(raw.borderStyle, ["solid", "dashed", "dotted"], base.borderStyle),
|
|
768
|
+
shadow: cssValue(raw.shadow, base.shadow),
|
|
769
|
+
rotate: num(raw.rotate, base.rotate, -360, 360),
|
|
770
|
+
fontFamily: cssValue(raw.fontFamily, base.fontFamily),
|
|
771
|
+
fontSize: num(raw.fontSize, base.fontSize, 1, 999),
|
|
772
|
+
fontWeight: cssValue(raw.fontWeight, base.fontWeight),
|
|
773
|
+
lineHeight: num(raw.lineHeight, base.lineHeight, 0.5, 10),
|
|
774
|
+
letterSpacing: num(raw.letterSpacing, base.letterSpacing, -50, 50),
|
|
775
|
+
textAlign: oneOf(raw.textAlign, ["left", "center", "right", "justify"], base.textAlign),
|
|
776
|
+
textTransform: oneOf(
|
|
777
|
+
raw.textTransform,
|
|
778
|
+
["none", "uppercase", "lowercase", "capitalize"],
|
|
779
|
+
base.textTransform
|
|
780
|
+
),
|
|
781
|
+
overflow: oneOf(raw.overflow, ["visible", "hidden", "auto", "scroll"], base.overflow),
|
|
782
|
+
position: oneOf(raw.position, ["static", "sticky"], base.position),
|
|
783
|
+
stickyOffset: num(raw.stickyOffset, base.stickyOffset, -9999, 9999),
|
|
784
|
+
bgImage: raw.bgImage === void 0 ? base.bgImage : imageUrl(raw.bgImage),
|
|
785
|
+
bgSize: oneOf(raw.bgSize, ["cover", "contain", "auto"], base.bgSize),
|
|
786
|
+
bgPosition: cssValue(raw.bgPosition, base.bgPosition),
|
|
787
|
+
blur: num(raw.blur, base.blur, 0, 200),
|
|
788
|
+
backdropBlur: num(raw.backdropBlur, base.backdropBlur, 0, 200),
|
|
789
|
+
blendMode: oneOf(
|
|
790
|
+
raw.blendMode,
|
|
791
|
+
["normal", "multiply", "screen", "overlay", "darken", "lighten", "difference", "luminosity"],
|
|
792
|
+
base.blendMode
|
|
793
|
+
),
|
|
794
|
+
scale: scalePercent(raw.scale, base.scale),
|
|
795
|
+
aspectRatio: /^\d{1,3}\/\d{1,3}$/.test(String(raw.aspectRatio ?? "")) ? String(raw.aspectRatio) : raw.aspectRatio === void 0 ? base.aspectRatio : "",
|
|
796
|
+
entrance: oneOf(
|
|
797
|
+
raw.entrance,
|
|
798
|
+
["none", "fade", "up", "down", "left", "right", "zoom"],
|
|
799
|
+
base.entrance
|
|
800
|
+
),
|
|
801
|
+
entranceDuration: num(raw.entranceDuration, base.entranceDuration, 50, 5e3),
|
|
802
|
+
entranceDelay: num(raw.entranceDelay, base.entranceDelay, 0, 5e3),
|
|
803
|
+
entranceCurve: oneOf(raw.entranceCurve, ["ease", "spring"], base.entranceCurve),
|
|
804
|
+
entranceBezier: cssValue(raw.entranceBezier, base.entranceBezier),
|
|
805
|
+
springStiffness: num(raw.springStiffness, base.springStiffness, 1, 1e3),
|
|
806
|
+
springDamping: num(raw.springDamping, base.springDamping, 1, 100),
|
|
807
|
+
cursor: oneOf(raw.cursor, ["auto", "default", "pointer", "text", "grab", "zoom-in", "none"], base.cursor),
|
|
808
|
+
hidden: raw.hidden === true
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
function parsePartialStyle(input, base) {
|
|
812
|
+
if (!input || typeof input !== "object") return {};
|
|
813
|
+
const raw = input;
|
|
814
|
+
const full = parseStyle(raw, base);
|
|
815
|
+
const partial = {};
|
|
816
|
+
for (const key of STYLE_KEYS) {
|
|
817
|
+
if (raw[key] !== void 0) {
|
|
818
|
+
partial[key] = full[key];
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
return partial;
|
|
822
|
+
}
|
|
823
|
+
function parseElements(input, baseBreakpointId = "desktop") {
|
|
824
|
+
if (!Array.isArray(input)) {
|
|
825
|
+
throw new InvalidElementsError("elements must be an array");
|
|
826
|
+
}
|
|
827
|
+
if (input.length > MAX_ELEMENTS) {
|
|
828
|
+
throw new InvalidElementsError(
|
|
829
|
+
`a page cannot hold more than ${MAX_ELEMENTS} elements`
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
const seen = /* @__PURE__ */ new Set();
|
|
833
|
+
const parsed = [];
|
|
834
|
+
for (const raw of input) {
|
|
835
|
+
if (!raw || typeof raw !== "object") continue;
|
|
836
|
+
const el2 = upgradeLegacy(raw);
|
|
837
|
+
const id = str(el2.id, MAX_SHORT_STRING);
|
|
838
|
+
if (!id || seen.has(id)) continue;
|
|
839
|
+
if (typeof el2.type !== "string" || !ELEMENT_TYPE_SET.has(el2.type)) continue;
|
|
840
|
+
seen.add(id);
|
|
841
|
+
const base = parseStyle(el2.base, BASE_STYLE);
|
|
842
|
+
const overrides = {};
|
|
843
|
+
if (el2.overrides && typeof el2.overrides === "object") {
|
|
844
|
+
for (const [key, value] of Object.entries(
|
|
845
|
+
el2.overrides
|
|
846
|
+
)) {
|
|
847
|
+
if (!/^[a-zA-Z0-9_-]{1,60}$/.test(key)) continue;
|
|
848
|
+
if (key === baseBreakpointId) continue;
|
|
849
|
+
const partial = parsePartialStyle(value, base);
|
|
850
|
+
if (Object.keys(partial).length > 0) {
|
|
851
|
+
overrides[key] = partial;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
const hover = parsePartialStyle(el2.hover, base);
|
|
856
|
+
const press = parsePartialStyle(el2.press, base);
|
|
857
|
+
parsed.push({
|
|
858
|
+
id,
|
|
859
|
+
type: el2.type,
|
|
860
|
+
name: str(el2.name, MAX_SHORT_STRING),
|
|
861
|
+
parentId: str(el2.parentId, MAX_SHORT_STRING),
|
|
862
|
+
z: num(el2.z, 0, 0, MAX_ELEMENTS),
|
|
863
|
+
locked: el2.locked === true ? true : void 0,
|
|
864
|
+
componentRole: el2.componentRole === "master" || el2.componentRole === "instance" ? el2.componentRole : void 0,
|
|
865
|
+
componentId: str(el2.componentId, MAX_SHORT_STRING),
|
|
866
|
+
componentSourceId: str(el2.componentSourceId, MAX_SHORT_STRING),
|
|
867
|
+
variant: str(el2.variant, MAX_SHORT_STRING),
|
|
868
|
+
styleBindings: el2.styleBindings && typeof el2.styleBindings === "object" ? Object.fromEntries(
|
|
869
|
+
Object.entries(
|
|
870
|
+
el2.styleBindings
|
|
871
|
+
).flatMap(
|
|
872
|
+
([key, value]) => STYLE_KEYS.includes(key) && typeof value === "string" ? [[key, value.slice(0, MAX_SHORT_STRING)]] : []
|
|
873
|
+
)
|
|
874
|
+
) : void 0,
|
|
875
|
+
content: str(el2.content, MAX_CONTENT),
|
|
876
|
+
code: str(el2.code, MAX_CONTENT),
|
|
877
|
+
src: safeUrl(el2.src),
|
|
878
|
+
alt: str(el2.alt, MAX_SHORT_STRING),
|
|
879
|
+
objectFit: oneOf(el2.objectFit, ["cover", "contain", "fill", "none"], "cover"),
|
|
880
|
+
iconName: oneOf(el2.iconName, ["star", "heart", "arrow-right", "check", "menu", "search"], "star"),
|
|
881
|
+
href: safeUrl(el2.href),
|
|
882
|
+
sourceId: str(el2.sourceId, MAX_SHORT_STRING),
|
|
883
|
+
binding: bindingPath(el2.binding),
|
|
884
|
+
target: oneOf(el2.target, ["_self", "_blank"], "_self"),
|
|
885
|
+
interaction: el2.interaction && typeof el2.interaction === "object" ? (() => {
|
|
886
|
+
const interaction = el2.interaction;
|
|
887
|
+
const action = oneOf(
|
|
888
|
+
interaction.action,
|
|
889
|
+
["navigate", "scroll-to", "toggle-layer", "show-layer", "hide-layer"],
|
|
890
|
+
"navigate"
|
|
891
|
+
);
|
|
892
|
+
const value = action !== "navigate" ? str(interaction.value, MAX_SHORT_STRING)?.replace(/[^a-zA-Z0-9_-]/g, "") : safeUrl(interaction.value);
|
|
893
|
+
return value ? {
|
|
894
|
+
trigger: "click",
|
|
895
|
+
action,
|
|
896
|
+
value,
|
|
897
|
+
target: oneOf(
|
|
898
|
+
interaction.target,
|
|
899
|
+
["_self", "_blank"],
|
|
900
|
+
"_self"
|
|
901
|
+
)
|
|
902
|
+
} : void 0;
|
|
903
|
+
})() : void 0,
|
|
904
|
+
base,
|
|
905
|
+
overrides: Object.keys(overrides).length > 0 ? overrides : void 0,
|
|
906
|
+
hover: Object.keys(hover).length > 0 ? hover : void 0,
|
|
907
|
+
press: Object.keys(press).length > 0 ? press : void 0,
|
|
908
|
+
loop: el2.loop && typeof el2.loop === "object" ? {
|
|
909
|
+
type: oneOf(el2.loop.type, ["pulse", "float", "spin"], "pulse"),
|
|
910
|
+
duration: num(el2.loop.duration, 1600, 100, 2e4)
|
|
911
|
+
} : void 0,
|
|
912
|
+
draggable: el2.draggable === true ? true : void 0
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
return severBrokenParents(parsed);
|
|
916
|
+
}
|
|
917
|
+
function upgradeLegacy(raw) {
|
|
918
|
+
if (raw.base !== void 0) return raw;
|
|
919
|
+
const legacy = raw;
|
|
920
|
+
return {
|
|
921
|
+
...legacy,
|
|
922
|
+
base: {
|
|
923
|
+
x: legacy.x,
|
|
924
|
+
y: legacy.y,
|
|
925
|
+
w: legacy.w,
|
|
926
|
+
h: legacy.h,
|
|
927
|
+
heightMode: legacy.autoHeight === true ? "auto" : "fixed",
|
|
928
|
+
bg: legacy.bg,
|
|
929
|
+
color: legacy.color,
|
|
930
|
+
radius: legacy.radius,
|
|
931
|
+
opacity: legacy.opacity,
|
|
932
|
+
borderW: legacy.borderW,
|
|
933
|
+
borderC: legacy.borderC,
|
|
934
|
+
fontSize: legacy.fontSize,
|
|
935
|
+
fontWeight: legacy.fontWeight,
|
|
936
|
+
hidden: legacy.hidden
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function severBrokenParents(elements) {
|
|
941
|
+
const byId = new Map(elements.map((el2) => [el2.id, el2]));
|
|
942
|
+
return elements.map((el2) => {
|
|
943
|
+
if (!el2.parentId) return el2;
|
|
944
|
+
if (el2.parentId === el2.id || !byId.has(el2.parentId)) {
|
|
945
|
+
return { ...el2, parentId: void 0 };
|
|
946
|
+
}
|
|
947
|
+
const visited = /* @__PURE__ */ new Set([el2.id]);
|
|
948
|
+
let cursor = byId.get(el2.parentId);
|
|
949
|
+
while (cursor) {
|
|
950
|
+
if (visited.has(cursor.id)) return { ...el2, parentId: void 0 };
|
|
951
|
+
visited.add(cursor.id);
|
|
952
|
+
cursor = cursor.parentId ? byId.get(cursor.parentId) : void 0;
|
|
953
|
+
}
|
|
954
|
+
return el2;
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
function parseRootStyle(input) {
|
|
958
|
+
const raw = input && typeof input === "object" ? input : {};
|
|
959
|
+
const base = DEFAULT_ROOT_STYLE;
|
|
960
|
+
const breakpoints = Array.isArray(raw.breakpoints) ? raw.breakpoints.slice(0, 12).flatMap((item) => {
|
|
961
|
+
if (!item || typeof item !== "object") return [];
|
|
962
|
+
const value = item;
|
|
963
|
+
const id = str(value.id, 60)?.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
964
|
+
const name = str(value.name, 40)?.trim();
|
|
965
|
+
if (!id || !name) return [];
|
|
966
|
+
return [{ id, name, width: num(value.width, 1280, 240, 4e3) }];
|
|
967
|
+
}) : void 0;
|
|
968
|
+
const variables = Array.isArray(raw.variables) ? raw.variables.slice(0, 100).reduce((result, item) => {
|
|
969
|
+
if (!item || typeof item !== "object") return result;
|
|
970
|
+
const variable = item;
|
|
971
|
+
const id = str(variable.id, 60);
|
|
972
|
+
const name = str(variable.name, 60)?.trim();
|
|
973
|
+
if (!id || !name) return result;
|
|
974
|
+
if (variable.type === "color" && typeof variable.value === "string")
|
|
975
|
+
result.push({ id, name, type: "color", value: cssValue(variable.value, "#000000") });
|
|
976
|
+
if (variable.type === "number" && Number.isFinite(Number(variable.value)))
|
|
977
|
+
result.push({ id, name, type: "number", value: Number(variable.value) });
|
|
978
|
+
return result;
|
|
979
|
+
}, []) : void 0;
|
|
980
|
+
const customFonts = Array.isArray(raw.customFonts) ? raw.customFonts.slice(0, 20).flatMap((item) => {
|
|
981
|
+
if (!item || typeof item !== "object") return [];
|
|
982
|
+
const font = item;
|
|
983
|
+
const id = str(font.id, 60);
|
|
984
|
+
const name = str(font.name, 80)?.trim();
|
|
985
|
+
const url = safeUrl(font.url);
|
|
986
|
+
if (!id || !name || !url || !/^https?:\/\//i.test(url) && !url.startsWith("/")) return [];
|
|
987
|
+
return [{ id, name, url, weight: num(font.weight, 400, 100, 900), style: oneOf(font.style, ["normal", "italic"], "normal") }];
|
|
988
|
+
}) : void 0;
|
|
989
|
+
return {
|
|
990
|
+
documentMode: oneOf(raw.documentMode, ["page", "component"], base.documentMode),
|
|
991
|
+
maxWidth: num(raw.maxWidth, base.maxWidth, 1, 4e3),
|
|
992
|
+
canvasHeight: num(raw.canvasHeight, base.canvasHeight, 1, 12e3),
|
|
993
|
+
fullWidth: raw.fullWidth === true,
|
|
994
|
+
bg: cssValue(raw.bg, base.bg),
|
|
995
|
+
layout: oneOf(raw.layout, ["absolute", "stack"], base.layout),
|
|
996
|
+
direction: oneOf(raw.direction, ["row", "column"], base.direction),
|
|
997
|
+
gap: num(raw.gap, base.gap, 0, 999),
|
|
998
|
+
padT: num(raw.padT, base.padT, 0, 9999),
|
|
999
|
+
padR: num(raw.padR, base.padR, 0, 9999),
|
|
1000
|
+
padB: num(raw.padB, base.padB, 0, 9999),
|
|
1001
|
+
padL: num(raw.padL, base.padL, 0, 9999),
|
|
1002
|
+
align: oneOf(raw.align, ["start", "center", "end", "stretch"], base.align),
|
|
1003
|
+
fontFamily: cssValue(raw.fontFamily, base.fontFamily),
|
|
1004
|
+
// A page may define any set of artboards, so the stored list is kept as
|
|
1005
|
+
// it comes; only an empty one falls back to the defaults.
|
|
1006
|
+
breakpoints: breakpoints?.length ? breakpoints : void 0,
|
|
1007
|
+
// The main breakpoint is whichever one holds the shared values. It has
|
|
1008
|
+
// to name a breakpoint that exists, or resolution would find nothing.
|
|
1009
|
+
baseBreakpointId: (breakpoints ?? DEFAULT_BREAKPOINTS).some(
|
|
1010
|
+
(item) => item.id === raw.baseBreakpointId
|
|
1011
|
+
) ? raw.baseBreakpointId : void 0,
|
|
1012
|
+
variables,
|
|
1013
|
+
customFonts
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
function parsePairs(input) {
|
|
1017
|
+
if (!Array.isArray(input)) return [];
|
|
1018
|
+
const out = [];
|
|
1019
|
+
for (const raw of input.slice(0, 25)) {
|
|
1020
|
+
if (!raw || typeof raw !== "object") continue;
|
|
1021
|
+
const row = raw;
|
|
1022
|
+
const key = str(row.key, 120)?.trim();
|
|
1023
|
+
if (!key) continue;
|
|
1024
|
+
out.push({ key, value: str(row.value, 500)?.trim() ?? "" });
|
|
1025
|
+
}
|
|
1026
|
+
return out;
|
|
1027
|
+
}
|
|
1028
|
+
function parseDataSources(input) {
|
|
1029
|
+
if (!Array.isArray(input)) return [];
|
|
1030
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1031
|
+
const out = [];
|
|
1032
|
+
for (const raw of input.slice(0, 20)) {
|
|
1033
|
+
if (!raw || typeof raw !== "object") continue;
|
|
1034
|
+
const row = raw;
|
|
1035
|
+
const id = str(row.id, MAX_SHORT_STRING);
|
|
1036
|
+
if (!id || seen.has(id)) continue;
|
|
1037
|
+
seen.add(id);
|
|
1038
|
+
out.push({
|
|
1039
|
+
id,
|
|
1040
|
+
name: str(row.name, 120)?.trim() || "Untitled source",
|
|
1041
|
+
url: str(row.url, MAX_URL)?.trim() ?? "",
|
|
1042
|
+
path: bindingPath(row.path) ?? "",
|
|
1043
|
+
method: oneOf(row.method, HTTP_METHODS, "GET"),
|
|
1044
|
+
body: str(row.body, 2e4)?.trim() ?? "",
|
|
1045
|
+
params: parsePairs(row.params),
|
|
1046
|
+
headers: parsePairs(row.headers),
|
|
1047
|
+
onNotFound: oneOf(row.onNotFound, DATA_SOURCE_NOT_FOUND_BEHAVIORS, "empty")
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
return out;
|
|
1051
|
+
}
|
|
1052
|
+
function parseSlug(input) {
|
|
1053
|
+
const raw = str(input, 200)?.trim().toLowerCase().replace(/^\/+|\/+$/g, "");
|
|
1054
|
+
if (!raw) return null;
|
|
1055
|
+
const segments = raw.split("/").map(
|
|
1056
|
+
(segment) => segment.startsWith(":") ? segment : segment.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")
|
|
1057
|
+
);
|
|
1058
|
+
if (segments.some((segment) => !STATIC_PATH_SEGMENT_RE.test(segment) && !PARAM_PATH_SEGMENT_RE.test(segment))) return null;
|
|
1059
|
+
const names = segments.filter((segment) => segment.startsWith(":")).map((segment) => segment.slice(1));
|
|
1060
|
+
if (new Set(names).size !== names.length) return null;
|
|
1061
|
+
return segments.join("/");
|
|
1062
|
+
}
|
|
1063
|
+
function parseName(input) {
|
|
1064
|
+
const raw = str(input, 120)?.trim();
|
|
1065
|
+
return raw && raw.length > 0 ? raw : null;
|
|
1066
|
+
}
|
|
1067
|
+
var MAX_ELEMENTS, MAX_CONTENT, MAX_SHORT_STRING, MAX_URL, COORD_LIMIT, ELEMENT_TYPE_SET, InvalidElementsError, STATIC_PATH_SEGMENT_RE, PARAM_PATH_SEGMENT_RE;
|
|
1068
|
+
var init_validate = __esm({
|
|
1069
|
+
"src/internal/lib/editor/validate.ts"() {
|
|
1070
|
+
"use strict";
|
|
1071
|
+
init_types();
|
|
1072
|
+
MAX_ELEMENTS = 2e3;
|
|
1073
|
+
MAX_CONTENT = 1e4;
|
|
1074
|
+
MAX_SHORT_STRING = 200;
|
|
1075
|
+
MAX_URL = 2048;
|
|
1076
|
+
COORD_LIMIT = 1e5;
|
|
1077
|
+
ELEMENT_TYPE_SET = new Set(ELEMENT_TYPES);
|
|
1078
|
+
InvalidElementsError = class extends Error {
|
|
1079
|
+
};
|
|
1080
|
+
STATIC_PATH_SEGMENT_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
1081
|
+
PARAM_PATH_SEGMENT_RE = /^:[a-z][a-z0-9_]*$/;
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
// src/internal/lib/data/source.ts
|
|
1086
|
+
var source_exports = {};
|
|
1087
|
+
__export(source_exports, {
|
|
1088
|
+
DEFAULT_REVALIDATE: () => DEFAULT_REVALIDATE,
|
|
1089
|
+
DataSourceError: () => DataSourceError,
|
|
1090
|
+
EMPTY_CONTEXT: () => EMPTY_CONTEXT,
|
|
1091
|
+
MAX_ROWS: () => MAX_ROWS,
|
|
1092
|
+
assertFetchableUrl: () => assertFetchableUrl,
|
|
1093
|
+
buildRequest: () => buildRequest,
|
|
1094
|
+
loadSource: () => loadSource,
|
|
1095
|
+
readBinding: () => readBinding,
|
|
1096
|
+
resolveTokens: () => resolveTokens,
|
|
1097
|
+
selectPath: () => selectPath
|
|
1098
|
+
});
|
|
1099
|
+
function assertFetchableUrl(raw) {
|
|
1100
|
+
let url;
|
|
1101
|
+
try {
|
|
1102
|
+
url = new URL(raw);
|
|
1103
|
+
} catch {
|
|
1104
|
+
throw new DataSourceError("That is not a valid URL.");
|
|
1105
|
+
}
|
|
1106
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
1107
|
+
throw new DataSourceError("Only http and https URLs can be requested.");
|
|
1108
|
+
}
|
|
1109
|
+
const host = url.hostname.toLowerCase();
|
|
1110
|
+
if (BLOCKED_HOSTS.has(host) || PRIVATE_IPV4.test(host)) {
|
|
1111
|
+
throw new DataSourceError(`Requests to ${host} are not allowed.`);
|
|
1112
|
+
}
|
|
1113
|
+
if (PRIVATE_SUFFIXES.some((suffix) => host.endsWith(suffix))) {
|
|
1114
|
+
throw new DataSourceError(`Requests to ${host} are not allowed.`);
|
|
1115
|
+
}
|
|
1116
|
+
if (host.startsWith("[") && /^\[(fe80|fc|fd)/i.test(host)) {
|
|
1117
|
+
throw new DataSourceError("Requests to private addresses are not allowed.");
|
|
1118
|
+
}
|
|
1119
|
+
return url;
|
|
1120
|
+
}
|
|
1121
|
+
function selectPath(payload, path) {
|
|
1122
|
+
const steps = path.split(".").map((s) => s.trim()).filter(Boolean);
|
|
1123
|
+
let cursor = payload;
|
|
1124
|
+
for (const step of steps) {
|
|
1125
|
+
if (cursor === null || typeof cursor !== "object") return void 0;
|
|
1126
|
+
cursor = cursor[step];
|
|
1127
|
+
}
|
|
1128
|
+
return cursor;
|
|
1129
|
+
}
|
|
1130
|
+
async function readCapped(response) {
|
|
1131
|
+
const reader = response.body?.getReader();
|
|
1132
|
+
if (!reader) return "";
|
|
1133
|
+
const chunks = [];
|
|
1134
|
+
let total = 0;
|
|
1135
|
+
while (true) {
|
|
1136
|
+
const { done, value } = await reader.read();
|
|
1137
|
+
if (done) break;
|
|
1138
|
+
total += value.byteLength;
|
|
1139
|
+
if (total > MAX_BYTES) {
|
|
1140
|
+
await reader.cancel();
|
|
1141
|
+
throw new DataSourceError("That response is too large (over 2 MB).");
|
|
1142
|
+
}
|
|
1143
|
+
chunks.push(value);
|
|
1144
|
+
}
|
|
1145
|
+
return new TextDecoder().decode(
|
|
1146
|
+
chunks.reduce((acc, chunk) => {
|
|
1147
|
+
const merged = new Uint8Array(acc.length + chunk.length);
|
|
1148
|
+
merged.set(acc);
|
|
1149
|
+
merged.set(chunk, acc.length);
|
|
1150
|
+
return merged;
|
|
1151
|
+
}, new Uint8Array())
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
async function loadSource(source, { revalidate = DEFAULT_REVALIDATE, context = EMPTY_CONTEXT } = {}) {
|
|
1155
|
+
const request = buildRequest(source, context);
|
|
1156
|
+
let target = request.url;
|
|
1157
|
+
let response;
|
|
1158
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
1159
|
+
response = await fetch(target, {
|
|
1160
|
+
// Redirects are followed by hand so each hop is validated too.
|
|
1161
|
+
redirect: "manual",
|
|
1162
|
+
method: request.method,
|
|
1163
|
+
headers: request.headers,
|
|
1164
|
+
body: request.body,
|
|
1165
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
1166
|
+
// Only GET responses are cacheable; anything with side effects is
|
|
1167
|
+
// fetched fresh every time.
|
|
1168
|
+
...revalidate === false || request.method !== "GET" ? { cache: "no-store" } : { next: { revalidate } }
|
|
1169
|
+
});
|
|
1170
|
+
if (response.status < 300 || response.status >= 400) break;
|
|
1171
|
+
const location = response.headers.get("location");
|
|
1172
|
+
if (!location) break;
|
|
1173
|
+
target = assertFetchableUrl(new URL(location, target).toString());
|
|
1174
|
+
response = void 0;
|
|
1175
|
+
}
|
|
1176
|
+
if (!response) throw new DataSourceError("Too many redirects.");
|
|
1177
|
+
if (!response.ok) {
|
|
1178
|
+
throw new DataSourceError(`The API replied ${response.status}.`, response.status);
|
|
1179
|
+
}
|
|
1180
|
+
const text2 = await readCapped(response);
|
|
1181
|
+
let payload;
|
|
1182
|
+
try {
|
|
1183
|
+
payload = JSON.parse(text2);
|
|
1184
|
+
} catch {
|
|
1185
|
+
throw new DataSourceError("The response was not JSON.");
|
|
1186
|
+
}
|
|
1187
|
+
const selected = selectPath(payload, source.path);
|
|
1188
|
+
const selectedRows = Array.isArray(selected) ? selected : selected !== null && typeof selected === "object" ? [selected] : void 0;
|
|
1189
|
+
if (!selectedRows) {
|
|
1190
|
+
throw new DataSourceError(
|
|
1191
|
+
source.path ? `"${source.path}" is not a list in that response.` : "That response is neither an object nor a list."
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
const rows = selectedRows.slice(0, MAX_ROWS).map(
|
|
1195
|
+
(row) => row !== null && typeof row === "object" && !Array.isArray(row) ? row : { value: row }
|
|
1196
|
+
);
|
|
1197
|
+
const keys = [...new Set(rows.flatMap((row) => Object.keys(row)))].sort();
|
|
1198
|
+
return { rows, keys };
|
|
1199
|
+
}
|
|
1200
|
+
function readBinding(row, path) {
|
|
1201
|
+
const value = selectPath(row, path);
|
|
1202
|
+
if (value === null || value === void 0) return "";
|
|
1203
|
+
if (typeof value === "object") return "";
|
|
1204
|
+
return String(value);
|
|
1205
|
+
}
|
|
1206
|
+
function resolveTokens(value, context) {
|
|
1207
|
+
return value.replace(/\{\{\s*([a-zA-Z0-9_.]+)\s*\}\}/g, (_, token) => {
|
|
1208
|
+
const [namespace, ...rest] = token.split(".");
|
|
1209
|
+
const key = rest.join(".");
|
|
1210
|
+
if (namespace === "query") return context.query[key] ?? "";
|
|
1211
|
+
if (namespace === "params") return context.params[key] ?? "";
|
|
1212
|
+
if (namespace === "page" && key === "slug") return context.page.slug;
|
|
1213
|
+
return "";
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
function safeHeader(pair, context) {
|
|
1217
|
+
const name = pair.key.trim();
|
|
1218
|
+
if (!/^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/.test(name)) return null;
|
|
1219
|
+
const value = resolveTokens(pair.value, context).replace(/[\r\n\0]/g, "").trim();
|
|
1220
|
+
return value ? [name, value] : null;
|
|
1221
|
+
}
|
|
1222
|
+
function buildRequest(source, context) {
|
|
1223
|
+
const url = assertFetchableUrl(resolveTokens(source.url, context));
|
|
1224
|
+
for (const pair of source.params ?? []) {
|
|
1225
|
+
const key = pair.key.trim();
|
|
1226
|
+
if (!key) continue;
|
|
1227
|
+
const value = resolveTokens(pair.value, context);
|
|
1228
|
+
if (value === "") continue;
|
|
1229
|
+
url.searchParams.set(key, value);
|
|
1230
|
+
}
|
|
1231
|
+
const method = source.method ?? "GET";
|
|
1232
|
+
const headers = {
|
|
1233
|
+
accept: "application/json,text/plain;q=0.9,*/*;q=0.8"
|
|
1234
|
+
};
|
|
1235
|
+
let body;
|
|
1236
|
+
if (sendsBody(method) && source.body?.trim()) {
|
|
1237
|
+
body = resolveTokens(source.body, context);
|
|
1238
|
+
headers["content-type"] = "application/json";
|
|
1239
|
+
}
|
|
1240
|
+
for (const pair of source.headers ?? []) {
|
|
1241
|
+
const safe = safeHeader(pair, context);
|
|
1242
|
+
if (safe) headers[safe[0]] = safe[1];
|
|
1243
|
+
}
|
|
1244
|
+
return { url: assertFetchableUrl(url.toString()), headers, method, body };
|
|
1245
|
+
}
|
|
1246
|
+
var BLOCKED_HOSTS, PRIVATE_IPV4, PRIVATE_SUFFIXES, MAX_ROWS, MAX_BYTES, TIMEOUT_MS, MAX_REDIRECTS, DataSourceError, DEFAULT_REVALIDATE, EMPTY_CONTEXT;
|
|
1247
|
+
var init_source = __esm({
|
|
1248
|
+
"src/internal/lib/data/source.ts"() {
|
|
1249
|
+
"use strict";
|
|
1250
|
+
init_types();
|
|
1251
|
+
BLOCKED_HOSTS = /* @__PURE__ */ new Set([
|
|
1252
|
+
"localhost",
|
|
1253
|
+
"127.0.0.1",
|
|
1254
|
+
"0.0.0.0",
|
|
1255
|
+
"::1",
|
|
1256
|
+
"[::1]",
|
|
1257
|
+
"metadata.google.internal",
|
|
1258
|
+
"metadata.goog"
|
|
1259
|
+
]);
|
|
1260
|
+
PRIVATE_IPV4 = /^(10\.|127\.|0\.|169\.254\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/;
|
|
1261
|
+
PRIVATE_SUFFIXES = [".local", ".internal", ".localdomain", ".home.arpa"];
|
|
1262
|
+
MAX_ROWS = 200;
|
|
1263
|
+
MAX_BYTES = 2e6;
|
|
1264
|
+
TIMEOUT_MS = 8e3;
|
|
1265
|
+
MAX_REDIRECTS = 3;
|
|
1266
|
+
DataSourceError = class extends Error {
|
|
1267
|
+
constructor(message, status) {
|
|
1268
|
+
super(message);
|
|
1269
|
+
this.status = status;
|
|
1270
|
+
this.name = "DataSourceError";
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
DEFAULT_REVALIDATE = 60;
|
|
1274
|
+
EMPTY_CONTEXT = { query: {}, params: {}, page: { slug: "" } };
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
// src/internal/lib/editor/tree.ts
|
|
1279
|
+
function newId() {
|
|
1280
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
1281
|
+
return crypto.randomUUID();
|
|
1282
|
+
}
|
|
1283
|
+
return `el-${Date.now().toString(36)}-${Math.floor(Math.random() * 1e9).toString(36)}`;
|
|
1284
|
+
}
|
|
1285
|
+
function createElement(type, position) {
|
|
1286
|
+
const defaults = ELEMENT_DEFAULTS[type];
|
|
1287
|
+
return {
|
|
1288
|
+
id: newId(),
|
|
1289
|
+
type,
|
|
1290
|
+
z: position.z,
|
|
1291
|
+
parentId: position.parentId,
|
|
1292
|
+
...defaults.props,
|
|
1293
|
+
base: makeStyle({ ...defaults.style, x: position.x, y: position.y })
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
var init_tree = __esm({
|
|
1297
|
+
"src/internal/lib/editor/tree.ts"() {
|
|
1298
|
+
"use strict";
|
|
1299
|
+
init_types();
|
|
1300
|
+
}
|
|
1301
|
+
});
|
|
1302
|
+
|
|
1303
|
+
// src/internal/lib/editor/showcase.ts
|
|
1304
|
+
function node(type, name, style, content, parentId) {
|
|
1305
|
+
const element = createElement(type, { x: 0, y: 0, z: 0, parentId });
|
|
1306
|
+
element.name = name;
|
|
1307
|
+
element.base = { ...element.base, ...style };
|
|
1308
|
+
if (content !== void 0) element.content = content;
|
|
1309
|
+
return element;
|
|
1310
|
+
}
|
|
1311
|
+
function responsive(element, tablet, mobile) {
|
|
1312
|
+
element.overrides = { tablet, mobile };
|
|
1313
|
+
return element;
|
|
1314
|
+
}
|
|
1315
|
+
function enter(element, delay = 0, entrance = "up") {
|
|
1316
|
+
element.base = { ...element.base, entrance, entranceDelay: delay, entranceDuration: 760, entranceCurve: "ease", entranceBezier: "0.16, 1, 0.3, 1" };
|
|
1317
|
+
return element;
|
|
1318
|
+
}
|
|
1319
|
+
function createNocturneShowcase() {
|
|
1320
|
+
const ink = "#f1f0ea";
|
|
1321
|
+
const muted = "#96958f";
|
|
1322
|
+
const acid = "#d7ff3f";
|
|
1323
|
+
const surface = "#151514";
|
|
1324
|
+
const border = "rgba(241,240,234,.14)";
|
|
1325
|
+
const elements = [];
|
|
1326
|
+
const section2 = (name, style) => {
|
|
1327
|
+
const item = node("Section", name, { widthMode: "fill", heightMode: "auto", layout: "stack", direction: "column", align: "center", ...style });
|
|
1328
|
+
elements.push(item);
|
|
1329
|
+
return item;
|
|
1330
|
+
};
|
|
1331
|
+
const container2 = (name, parentId, style) => {
|
|
1332
|
+
const item = node("Container", name, { widthMode: "fill", heightMode: "auto", layout: "stack", borderW: 0, padT: 0, padR: 0, padB: 0, padL: 0, ...style }, void 0, parentId);
|
|
1333
|
+
elements.push(item);
|
|
1334
|
+
return item;
|
|
1335
|
+
};
|
|
1336
|
+
const nav = section2("Nocturne navigation", { bg: "#0b0b0a", padT: 22, padR: 40, padB: 22, padL: 40, borderW: 0 });
|
|
1337
|
+
const navInner = responsive(container2("Navigation inner", nav.id, { direction: "row", justify: "between", align: "center", gap: 28, w: 1180, widthMode: "fixed" }), { w: 860 }, { widthMode: "fill" });
|
|
1338
|
+
elements.push(node("Heading", "Wordmark", { widthMode: "auto", heightMode: "auto", color: ink, fontSize: 17, fontWeight: "800", letterSpacing: -0.7 }, "NOCTURNE\xAE", navInner.id));
|
|
1339
|
+
const links = responsive(container2("Navigation links", navInner.id, { widthMode: "auto", direction: "row", align: "center", gap: 28 }), {}, { hidden: true });
|
|
1340
|
+
for (const [label, slug] of [["Work", "work"], ["Studio", "studio"], ["Contact", "contact"]]) {
|
|
1341
|
+
const link = node("Button", `Nav ${label}`, { widthMode: "auto", heightMode: "auto", bg: "transparent", color: muted, fontSize: 13, fontWeight: "600", padT: 4, padR: 0, padB: 4, padL: 0, cursor: "pointer" }, label, links.id);
|
|
1342
|
+
link.interaction = { trigger: "click", action: "navigate", value: `/s/${slug}`, target: "_self" };
|
|
1343
|
+
link.hover = { color: ink };
|
|
1344
|
+
elements.push(link);
|
|
1345
|
+
}
|
|
1346
|
+
const navCta = node("Button", "Navigation CTA", { widthMode: "auto", heightMode: "auto", bg: acid, color: "#0b0b0a", radius: 999, padT: 11, padR: 18, padB: 11, padL: 18, fontSize: 12, fontWeight: "800", cursor: "pointer" }, "Start a project \u2197", navInner.id);
|
|
1347
|
+
navCta.hover = { scale: 101.8, bg: "#edff9a", shadow: "0 10px 32px rgba(215,255,63,.18)" };
|
|
1348
|
+
navCta.press = { scale: 97.5 };
|
|
1349
|
+
navCta.overrides = { mobile: { hidden: true } };
|
|
1350
|
+
elements.push(navCta);
|
|
1351
|
+
const menuButton = node("Button", "Mobile menu trigger", { widthMode: "auto", heightMode: "auto", bg: "transparent", color: ink, borderW: 1, borderC: border, radius: 999, padT: 9, padR: 14, padB: 9, padL: 14, fontSize: 11, fontWeight: "800", hidden: true, cursor: "pointer" }, "MENU +", navInner.id);
|
|
1352
|
+
menuButton.overrides = { mobile: { hidden: false } };
|
|
1353
|
+
menuButton.press = { scale: 97 };
|
|
1354
|
+
elements.push(menuButton);
|
|
1355
|
+
const mobileMenu = node("Container", "Mobile navigation menu", { widthMode: "fill", heightMode: "auto", layout: "stack", direction: "column", gap: 0, bg: "#151514", borderW: 1, borderC: border, radius: 14, padT: 8, padR: 8, padB: 8, padL: 8, hidden: true }, void 0, nav.id);
|
|
1356
|
+
elements.push(mobileMenu);
|
|
1357
|
+
menuButton.interaction = { trigger: "click", action: "toggle-layer", value: mobileMenu.id };
|
|
1358
|
+
for (const [label, slug] of [["Work", "work"], ["Studio", "studio"], ["Contact", "contact"]]) {
|
|
1359
|
+
const item = node("Button", `Mobile ${label}`, { widthMode: "fill", heightMode: "auto", bg: "transparent", color: ink, borderW: 0, radius: 9, padT: 14, padR: 14, padB: 14, padL: 14, fontSize: 14, fontWeight: "700", textAlign: "left", cursor: "pointer" }, `${label} \u2197`, mobileMenu.id);
|
|
1360
|
+
item.interaction = { trigger: "click", action: "navigate", value: `/s/${slug}`, target: "_self" };
|
|
1361
|
+
item.hover = { bg: "rgba(255,255,255,.06)" };
|
|
1362
|
+
elements.push(item);
|
|
1363
|
+
}
|
|
1364
|
+
const hero = section2("Hero", { bg: "#0b0b0a", padT: 86, padR: 40, padB: 72, padL: 40, overflow: "hidden" });
|
|
1365
|
+
const heroInner = responsive(container2("Hero composition", hero.id, { direction: "row", justify: "between", align: "end", gap: 64, w: 1180, widthMode: "fixed" }), { w: 860, gap: 36 }, { widthMode: "fill", direction: "column", align: "stretch", gap: 44 });
|
|
1366
|
+
const heroCopy = container2("Hero copy", heroInner.id, { widthMode: "fixed", w: 650, direction: "column", gap: 26 });
|
|
1367
|
+
heroCopy.overrides = { tablet: { w: 500 }, mobile: { widthMode: "fill" } };
|
|
1368
|
+
elements.push(enter(node("Text", "Hero index", { widthMode: "auto", heightMode: "auto", color: acid, fontSize: 11, fontWeight: "700", letterSpacing: 2.2, textTransform: "uppercase" }, "Independent creative practice \u2014 Istanbul / Worldwide", heroCopy.id), 0, "fade"));
|
|
1369
|
+
const title = enter(node("Heading", "Hero title", { widthMode: "fill", heightMode: "auto", color: ink, fontSize: 96, fontWeight: "600", lineHeight: 0.9, letterSpacing: -6.5 }, "WE DESIGN\nTHE UNEXPECTED.", heroCopy.id), 90);
|
|
1370
|
+
title.overrides = { tablet: { fontSize: 68, letterSpacing: -4 }, mobile: { fontSize: 52, letterSpacing: -3, lineHeight: 0.94 } };
|
|
1371
|
+
elements.push(title);
|
|
1372
|
+
const heroBottom = container2("Hero statement", heroCopy.id, { direction: "row", justify: "between", align: "end", gap: 28, padT: 12, borderW: 0 });
|
|
1373
|
+
heroBottom.overrides = { mobile: { direction: "column", align: "start" } };
|
|
1374
|
+
const intro = enter(node("Text", "Hero intro", { widthMode: "fixed", w: 390, heightMode: "auto", color: muted, fontSize: 16, lineHeight: 1.65 }, "A strategy-led design studio shaping identities and digital experiences for ambitious people building what comes next.", heroBottom.id), 180);
|
|
1375
|
+
intro.overrides = { mobile: { widthMode: "fill" } };
|
|
1376
|
+
elements.push(intro);
|
|
1377
|
+
const scroll = node("Text", "Scroll cue", { widthMode: "auto", heightMode: "auto", color: ink, fontSize: 11, fontWeight: "700", letterSpacing: 1.4, textTransform: "uppercase" }, "Scroll to explore \u2193", heroBottom.id);
|
|
1378
|
+
scroll.loop = { type: "float", duration: 2600 };
|
|
1379
|
+
elements.push(scroll);
|
|
1380
|
+
const visual = enter(node("Frame", "Hero artwork", { widthMode: "fixed", w: 390, heightMode: "fixed", h: 540, layout: "absolute", bg: surface, gradient: "radial-gradient(circle at 68% 28%, #d7ff3f 0%, #7f9c16 11%, #27291d 28%, #151514 58%)", radius: 2, overflow: "hidden", borderW: 1, borderC: border, rotate: 2, shadow: "0 40px 100px rgba(0,0,0,.45)" }, void 0, heroInner.id), 160, "zoom");
|
|
1381
|
+
visual.overrides = { tablet: { w: 310, h: 460 }, mobile: { widthMode: "fill", w: 320, h: 420, rotate: 0 } };
|
|
1382
|
+
elements.push(visual);
|
|
1383
|
+
const artLabel = node("Text", "Artwork label", { x: 24, y: 24, w: 190, h: 30, widthMode: "fixed", heightMode: "fixed", color: "#0b0b0a", fontSize: 10, fontWeight: "800", letterSpacing: 1.5, textTransform: "uppercase" }, "Form / Tension / 001", visual.id);
|
|
1384
|
+
elements.push(artLabel);
|
|
1385
|
+
const orb = node("Frame", "Kinetic orb", { x: 220, y: 350, w: 110, h: 110, widthMode: "fixed", heightMode: "fixed", bg: acid, radius: 999, blur: 1, shadow: "0 0 70px rgba(215,255,63,.42)" }, void 0, visual.id);
|
|
1386
|
+
orb.loop = { type: "pulse", duration: 4200 };
|
|
1387
|
+
elements.push(orb);
|
|
1388
|
+
elements.push(node("Heading", "Artwork number", { x: 24, y: 436, w: 260, h: 82, widthMode: "fixed", heightMode: "fixed", color: ink, fontSize: 64, fontWeight: "500", letterSpacing: -4 }, "24\u201426", visual.id));
|
|
1389
|
+
const ticker = section2("Ticker", { bg: acid, padT: 17, padR: 24, padB: 17, padL: 24 });
|
|
1390
|
+
elements.push(node("Text", "Ticker line", { widthMode: "fill", heightMode: "auto", color: "#0b0b0a", fontSize: 12, fontWeight: "800", letterSpacing: 2, textAlign: "center", textTransform: "uppercase" }, "Brand systems \u2726 Digital direction \u2726 Interactive experiences \u2726 Art direction \u2726 Strategy", ticker.id));
|
|
1391
|
+
const manifesto = section2("Manifesto", { bg: "#f1f0ea", padT: 140, padR: 40, padB: 140, padL: 40 });
|
|
1392
|
+
const manifestoInner = responsive(container2("Manifesto inner", manifesto.id, { direction: "column", gap: 64, w: 1180, widthMode: "fixed" }), { w: 860 }, { widthMode: "fill", gap: 40 });
|
|
1393
|
+
elements.push(node("Text", "Manifesto eyebrow", { widthMode: "auto", heightMode: "auto", color: "#5d5d58", fontSize: 11, fontWeight: "800", letterSpacing: 2, textTransform: "uppercase" }, "01 / Our point of view", manifestoInner.id));
|
|
1394
|
+
const statement = enter(node("Heading", "Manifesto statement", { widthMode: "fill", heightMode: "auto", color: "#11110f", fontSize: 70, fontWeight: "500", lineHeight: 1.03, letterSpacing: -4.4 }, "Safe ideas disappear.\nDistinct ones build culture.", manifestoInner.id), 40);
|
|
1395
|
+
statement.overrides = { tablet: { fontSize: 54 }, mobile: { fontSize: 40, letterSpacing: -2.4 } };
|
|
1396
|
+
elements.push(statement);
|
|
1397
|
+
const manifestoBottom = responsive(container2("Manifesto details", manifestoInner.id, { direction: "row", justify: "end", gap: 60 }), {}, { direction: "column", gap: 24 });
|
|
1398
|
+
const body = node("Text", "Manifesto body", { widthMode: "fixed", w: 500, heightMode: "auto", color: "#5d5d58", fontSize: 17, lineHeight: 1.75 }, "We partner with founders and cultural teams at inflection points\u2014when the old language no longer fits and a sharper expression can unlock the next chapter.", manifestoBottom.id);
|
|
1399
|
+
body.overrides = { mobile: { widthMode: "fill" } };
|
|
1400
|
+
elements.push(body);
|
|
1401
|
+
elements.push(node("Text", "Manifesto services", { widthMode: "fixed", w: 220, heightMode: "auto", color: "#11110f", fontSize: 13, fontWeight: "700", lineHeight: 2 }, "POSITIONING\nIDENTITY SYSTEMS\nDIGITAL PRODUCTS\nCAMPAIGN WORLDS", manifestoBottom.id));
|
|
1402
|
+
const work = section2("Selected work", { bg: "#0b0b0a", padT: 128, padR: 40, padB: 128, padL: 40 });
|
|
1403
|
+
const workInner = responsive(container2("Work inner", work.id, { direction: "column", gap: 42, w: 1180, widthMode: "fixed" }), { w: 860 }, { widthMode: "fill" });
|
|
1404
|
+
const workHead = container2("Work heading", workInner.id, { direction: "row", justify: "between", align: "end", gap: 20 });
|
|
1405
|
+
workHead.overrides = { mobile: { direction: "column", align: "start" } };
|
|
1406
|
+
elements.push(node("Heading", "Work title", { widthMode: "auto", heightMode: "auto", color: ink, fontSize: 48, fontWeight: "600", letterSpacing: -2.5 }, "SELECTED WORK", workHead.id));
|
|
1407
|
+
elements.push(node("Text", "Work count", { widthMode: "auto", heightMode: "auto", color: muted, fontSize: 12, letterSpacing: 1.2 }, "2024\u20142026 / 03 PROJECTS", workHead.id));
|
|
1408
|
+
const grid = responsive(node("Grid", "Projects grid", { widthMode: "fill", heightMode: "auto", layout: "stack", columns: 2, gap: 18 }, void 0, workInner.id), { columns: 2 }, { columns: 1 });
|
|
1409
|
+
elements.push(grid);
|
|
1410
|
+
const projects = [
|
|
1411
|
+
["AER / Mobility", "Identity + Digital", "linear-gradient(135deg,#29352a,#a9c6a2 55%,#e4ecdf)"],
|
|
1412
|
+
["KERN / Objects", "Strategy + Commerce", "linear-gradient(145deg,#4b251c,#e06d45 48%,#f3c7a8)"],
|
|
1413
|
+
["OTHER / Culture", "Campaign + Experience", "linear-gradient(145deg,#172d43,#3b83a8 48%,#b8ddea)"]
|
|
1414
|
+
];
|
|
1415
|
+
projects.forEach(([titleText, meta, gradient], index2) => {
|
|
1416
|
+
const card = enter(node("Container", `Project ${index2 + 1}`, { widthMode: "fill", heightMode: "auto", layout: "stack", direction: "column", gap: 18, bg: "transparent", borderW: 0, padT: 0, padR: 0, padB: 18, padL: 0 }, void 0, grid.id), index2 * 90);
|
|
1417
|
+
if (index2 === 2) card.base = { ...card.base, columns: 2 };
|
|
1418
|
+
card.hover = { shadow: "0 26px 70px rgba(0,0,0,.28)", opacity: 96 };
|
|
1419
|
+
elements.push(card);
|
|
1420
|
+
const image = node("Frame", "Project image", { widthMode: "fill", heightMode: "fixed", h: index2 === 0 ? 520 : 360, bg: surface, gradient, overflow: "hidden", radius: 2 }, void 0, card.id);
|
|
1421
|
+
image.overrides = { mobile: { h: 320 } };
|
|
1422
|
+
elements.push(image);
|
|
1423
|
+
const caption = container2("Project caption", card.id, { direction: "row", justify: "between", gap: 20 });
|
|
1424
|
+
elements.push(node("Heading", "Project name", { widthMode: "auto", heightMode: "auto", color: ink, fontSize: 17, fontWeight: "700", letterSpacing: -0.5 }, titleText, caption.id));
|
|
1425
|
+
elements.push(node("Text", "Project discipline", { widthMode: "auto", heightMode: "auto", color: muted, fontSize: 12 }, meta, caption.id));
|
|
1426
|
+
});
|
|
1427
|
+
const cta = section2("Closing CTA", { bg: acid, padT: 120, padR: 40, padB: 120, padL: 40 });
|
|
1428
|
+
const ctaInner = responsive(container2("CTA inner", cta.id, { direction: "column", align: "center", gap: 30, w: 980, widthMode: "fixed", textAlign: "center" }), { w: 760 }, { widthMode: "fill" });
|
|
1429
|
+
elements.push(node("Text", "CTA eyebrow", { widthMode: "auto", heightMode: "auto", color: "#30340d", fontSize: 11, fontWeight: "800", letterSpacing: 2, textTransform: "uppercase" }, "Have a meaningful problem?", ctaInner.id));
|
|
1430
|
+
const ctaTitle = node("Heading", "CTA title", { widthMode: "fill", heightMode: "auto", color: "#0b0b0a", fontSize: 76, fontWeight: "600", lineHeight: 0.98, letterSpacing: -4.5, textAlign: "center" }, "LET\u2019S MAKE IT\nIMPOSSIBLE TO IGNORE.", ctaInner.id);
|
|
1431
|
+
ctaTitle.overrides = { mobile: { fontSize: 44, letterSpacing: -2.5 } };
|
|
1432
|
+
elements.push(ctaTitle);
|
|
1433
|
+
const button = node("Button", "CTA button", { widthMode: "auto", heightMode: "auto", bg: "#0b0b0a", color: ink, radius: 999, padT: 15, padR: 24, padB: 15, padL: 24, fontSize: 13, fontWeight: "800", cursor: "pointer" }, "Tell us what you\u2019re building \u2197", ctaInner.id);
|
|
1434
|
+
button.hover = { scale: 101.8, bg: "#282821", shadow: "0 16px 44px rgba(11,11,10,.2)" };
|
|
1435
|
+
button.press = { scale: 97.5 };
|
|
1436
|
+
elements.push(button);
|
|
1437
|
+
const footer = section2("Footer", { bg: "#0b0b0a", padT: 42, padR: 40, padB: 42, padL: 40 });
|
|
1438
|
+
const footerInner = responsive(container2("Footer inner", footer.id, { direction: "row", justify: "between", align: "end", gap: 32, w: 1180, widthMode: "fixed" }), { w: 860 }, { widthMode: "fill", direction: "column", align: "start" });
|
|
1439
|
+
elements.push(node("Heading", "Footer brand", { widthMode: "auto", heightMode: "auto", color: ink, fontSize: 28, fontWeight: "800", letterSpacing: -1.5 }, "NOCTURNE\xAE", footerInner.id));
|
|
1440
|
+
elements.push(node("Text", "Footer meta", { widthMode: "auto", heightMode: "auto", color: muted, fontSize: 11, lineHeight: 1.8, textAlign: "right" }, "ISTANBUL / WORLDWIDE\n\xA9 2026 \u2014 ALL RIGHTS RESERVED", footerInner.id));
|
|
1441
|
+
return {
|
|
1442
|
+
elements,
|
|
1443
|
+
rootStyle: {
|
|
1444
|
+
...DEFAULT_ROOT_STYLE,
|
|
1445
|
+
documentMode: "page",
|
|
1446
|
+
fullWidth: true,
|
|
1447
|
+
maxWidth: 1200,
|
|
1448
|
+
canvasHeight: 5200,
|
|
1449
|
+
bg: "#0b0b0a",
|
|
1450
|
+
layout: "stack",
|
|
1451
|
+
direction: "column",
|
|
1452
|
+
gap: 0,
|
|
1453
|
+
align: "stretch",
|
|
1454
|
+
padT: 0,
|
|
1455
|
+
padR: 0,
|
|
1456
|
+
padB: 0,
|
|
1457
|
+
padL: 0,
|
|
1458
|
+
fontFamily: '"Manrope Showcase", sans-serif',
|
|
1459
|
+
customFonts: [{ id: "showcase-manrope", name: "Manrope Showcase", url: "/api/pagiera/assets/manrope-variable.woff2", weight: 400, style: "normal" }],
|
|
1460
|
+
variables: [
|
|
1461
|
+
{ id: "nocturne-bg", name: "Nocturne / Background", type: "color", value: "#0b0b0a" },
|
|
1462
|
+
{ id: "nocturne-ink", name: "Nocturne / Ink", type: "color", value: ink },
|
|
1463
|
+
{ id: "nocturne-acid", name: "Nocturne / Acid", type: "color", value: acid },
|
|
1464
|
+
{ id: "nocturne-muted", name: "Nocturne / Muted", type: "color", value: muted }
|
|
1465
|
+
]
|
|
1466
|
+
}
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
function nocturneInnerPage(name, slug, index2, title, intro, columns) {
|
|
1470
|
+
const source = createNocturneShowcase();
|
|
1471
|
+
const roots = source.elements.filter((element) => element.name === "Nocturne navigation" || element.name === "Footer");
|
|
1472
|
+
const keep = /* @__PURE__ */ new Set();
|
|
1473
|
+
const visit = (id) => {
|
|
1474
|
+
keep.add(id);
|
|
1475
|
+
source.elements.filter((element) => element.parentId === id).forEach((element) => visit(element.id));
|
|
1476
|
+
};
|
|
1477
|
+
roots.forEach((root2) => visit(root2.id));
|
|
1478
|
+
const nav = roots.find((root2) => root2.name === "Nocturne navigation");
|
|
1479
|
+
const footer = roots.find((root2) => root2.name === "Footer");
|
|
1480
|
+
const navTree = source.elements.filter((element) => keep.has(element.id) && (element.id === nav?.id || (() => {
|
|
1481
|
+
let cursor = element.parentId;
|
|
1482
|
+
while (cursor) {
|
|
1483
|
+
if (cursor === nav?.id) return true;
|
|
1484
|
+
cursor = source.elements.find((item) => item.id === cursor)?.parentId;
|
|
1485
|
+
}
|
|
1486
|
+
return false;
|
|
1487
|
+
})()));
|
|
1488
|
+
const footerTree = source.elements.filter((element) => keep.has(element.id) && (element.id === footer?.id || (() => {
|
|
1489
|
+
let cursor = element.parentId;
|
|
1490
|
+
while (cursor) {
|
|
1491
|
+
if (cursor === footer?.id) return true;
|
|
1492
|
+
cursor = source.elements.find((item) => item.id === cursor)?.parentId;
|
|
1493
|
+
}
|
|
1494
|
+
return false;
|
|
1495
|
+
})()));
|
|
1496
|
+
const elements = [...navTree];
|
|
1497
|
+
const hero = node("Section", `${name} hero`, { widthMode: "fill", heightMode: "auto", layout: "stack", direction: "column", align: "center", gap: 54, bg: "#0b0b0a", padT: 120, padR: 40, padB: 130, padL: 40 });
|
|
1498
|
+
elements.push(hero);
|
|
1499
|
+
const inner = responsive(node("Container", `${name} hero inner`, { widthMode: "fixed", w: 1180, heightMode: "auto", layout: "stack", direction: "column", gap: 34, borderW: 0, padT: 0, padR: 0, padB: 0, padL: 0 }, void 0, hero.id), { w: 860 }, { widthMode: "fill" });
|
|
1500
|
+
elements.push(inner);
|
|
1501
|
+
elements.push(enter(node("Text", `${name} index`, { widthMode: "auto", heightMode: "auto", color: "#d7ff3f", fontSize: 11, fontWeight: "800", letterSpacing: 2, textTransform: "uppercase" }, `${index2} / ${name}`, inner.id), 0, "fade"));
|
|
1502
|
+
const heading = enter(node("Heading", `${name} title`, { widthMode: "fill", heightMode: "auto", color: "#f1f0ea", fontSize: 92, fontWeight: "600", lineHeight: 0.92, letterSpacing: -6 }, title, inner.id), 70);
|
|
1503
|
+
heading.overrides = { tablet: { fontSize: 66 }, mobile: { fontSize: 48, letterSpacing: -3 } };
|
|
1504
|
+
elements.push(heading);
|
|
1505
|
+
const introNode = enter(node("Text", `${name} intro`, { widthMode: "fixed", w: 620, heightMode: "auto", color: "#96958f", fontSize: 18, lineHeight: 1.7 }, intro, inner.id), 150);
|
|
1506
|
+
introNode.overrides = { mobile: { widthMode: "fill" } };
|
|
1507
|
+
elements.push(introNode);
|
|
1508
|
+
const content = node("Section", `${name} content`, { widthMode: "fill", heightMode: "auto", layout: "stack", direction: "column", align: "center", bg: "#f1f0ea", padT: 110, padR: 40, padB: 120, padL: 40 });
|
|
1509
|
+
elements.push(content);
|
|
1510
|
+
const grid = responsive(node("Grid", `${name} grid`, { widthMode: "fixed", w: 1180, heightMode: "auto", layout: "stack", columns: 2, gap: 1, bg: "#b9b8b0" }, void 0, content.id), { w: 860 }, { widthMode: "fill", columns: 1 });
|
|
1511
|
+
elements.push(grid);
|
|
1512
|
+
columns.forEach(([label, copy], position) => {
|
|
1513
|
+
const card = enter(node("Container", `${name} ${label}`, { widthMode: "fill", heightMode: "auto", layout: "stack", direction: "column", gap: 26, bg: "#f1f0ea", borderW: 0, padT: 44, padR: 44, padB: 50, padL: 44 }, void 0, grid.id), position * 80);
|
|
1514
|
+
elements.push(card);
|
|
1515
|
+
elements.push(node("Text", `${label} number`, { widthMode: "auto", heightMode: "auto", color: "#74736d", fontSize: 10, fontWeight: "800", letterSpacing: 1.8 }, `0${position + 1} \u2014 ${label.toUpperCase()}`, card.id));
|
|
1516
|
+
elements.push(node("Text", `${label} copy`, { widthMode: "fill", heightMode: "auto", color: "#171714", fontSize: 21, lineHeight: 1.5, letterSpacing: -0.5 }, copy, card.id));
|
|
1517
|
+
});
|
|
1518
|
+
elements.push(...footerTree);
|
|
1519
|
+
return { name, slug, elements, rootStyle: { ...source.rootStyle, canvasHeight: 3e3 } };
|
|
1520
|
+
}
|
|
1521
|
+
function createNocturneSiteTemplate() {
|
|
1522
|
+
const home = createNocturneShowcase();
|
|
1523
|
+
return [
|
|
1524
|
+
{ name: "Home", slug: "home", ...home },
|
|
1525
|
+
nocturneInnerPage("Work", "work", "02", "SELECTED\nENGAGEMENTS.", "A focused archive of identities, products and campaign worlds created with teams in motion.", [["Aer", "A new identity and booking experience for an electric mobility company built around quiet confidence."], ["Kern", "A tactile commerce system for collectible objects, balancing material detail with digital clarity."], ["Other", "A cultural platform transformed into an evolving editorial identity and participatory campaign."], ["Field", "A data product made human through a precise interface, direct language and a flexible visual system."]]),
|
|
1526
|
+
nocturneInnerPage("Studio", "studio", "03", "SMALL TEAM.\nSERIOUS RANGE.", "Nocturne is an independent strategy and design practice for moments when a familiar answer is no longer enough.", [["Approach", "We begin with the tension underneath the brief, then build one clear idea that can carry an entire system."], ["Team", "A senior, hands-on core supported by a trusted network of specialists selected around each challenge."], ["Principles", "Clarity over decoration. Character over trends. Systems that stay useful after the launch moment."], ["Partners", "We work directly with founders, cultural leaders and internal teams who value an honest creative exchange."]]),
|
|
1527
|
+
nocturneInnerPage("Contact", "contact", "04", "LET\u2019S START\nWITH THE TRUTH.", "Tell us what is changing, what feels stuck and why this moment matters. We will take it from there.", [["New business", "projects@nocturne.studio\nShare your ambition, timing and the people involved."], ["Elsewhere", "Instagram / LinkedIn / Are.na\nFollow current work, fragments and studio notes."], ["Istanbul", "Karak\xF6y, Istanbul\nWorking locally and collaborating worldwide."], ["Availability", "Selected partnerships for Q4 2026.\nTypical engagements begin with a focused strategy sprint."]])
|
|
1528
|
+
];
|
|
1529
|
+
}
|
|
1530
|
+
var init_showcase = __esm({
|
|
1531
|
+
"src/internal/lib/editor/showcase.ts"() {
|
|
1532
|
+
"use strict";
|
|
1533
|
+
init_tree();
|
|
1534
|
+
init_types();
|
|
1535
|
+
}
|
|
1536
|
+
});
|
|
1537
|
+
|
|
1538
|
+
// src/internal/lib/editor/site-templates.ts
|
|
1539
|
+
var site_templates_exports = {};
|
|
1540
|
+
__export(site_templates_exports, {
|
|
1541
|
+
createSiteTemplate: () => createSiteTemplate
|
|
1542
|
+
});
|
|
1543
|
+
function el(type, name, style, content, parentId) {
|
|
1544
|
+
const item = createElement(type, { x: 0, y: 0, z: 0, parentId });
|
|
1545
|
+
item.name = name;
|
|
1546
|
+
item.base = { ...item.base, ...style };
|
|
1547
|
+
if (content !== void 0) item.content = content;
|
|
1548
|
+
return item;
|
|
1549
|
+
}
|
|
1550
|
+
function root(bg, ink, accent) {
|
|
1551
|
+
return {
|
|
1552
|
+
...DEFAULT_ROOT_STYLE,
|
|
1553
|
+
fullWidth: true,
|
|
1554
|
+
canvasHeight: 3600,
|
|
1555
|
+
bg,
|
|
1556
|
+
layout: "stack",
|
|
1557
|
+
direction: "column",
|
|
1558
|
+
align: "stretch",
|
|
1559
|
+
gap: 0,
|
|
1560
|
+
padT: 0,
|
|
1561
|
+
padR: 0,
|
|
1562
|
+
padB: 0,
|
|
1563
|
+
padL: 0,
|
|
1564
|
+
fontFamily: '"Manrope Showcase", sans-serif',
|
|
1565
|
+
customFonts: [{ id: "template-manrope", name: "Manrope Showcase", url: "/api/pagiera/assets/manrope-variable.woff2", weight: 400, style: "normal" }],
|
|
1566
|
+
variables: [
|
|
1567
|
+
{ id: "template-bg", name: "Template / Background", type: "color", value: bg },
|
|
1568
|
+
{ id: "template-ink", name: "Template / Ink", type: "color", value: ink },
|
|
1569
|
+
{ id: "template-accent", name: "Template / Accent", type: "color", value: accent }
|
|
1570
|
+
]
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
function section(elements, name, bg, padding = 64) {
|
|
1574
|
+
const item = el("Section", name, { widthMode: "fill", heightMode: "auto", layout: "stack", direction: "column", align: "center", bg, padT: padding, padR: 32, padB: padding, padL: 32 });
|
|
1575
|
+
elements.push(item);
|
|
1576
|
+
return item;
|
|
1577
|
+
}
|
|
1578
|
+
function container(elements, name, parentId, style = {}) {
|
|
1579
|
+
const item = el("Container", name, { widthMode: "fixed", w: 1120, heightMode: "auto", layout: "stack", direction: "column", gap: 24, borderW: 0, padT: 0, padR: 0, padB: 0, padL: 0, ...style }, void 0, parentId);
|
|
1580
|
+
item.overrides = { tablet: { w: 820 }, mobile: { widthMode: "fill", direction: style.direction === "row" ? "column" : style.direction } };
|
|
1581
|
+
elements.push(item);
|
|
1582
|
+
return item;
|
|
1583
|
+
}
|
|
1584
|
+
function motion(item, delay = 0) {
|
|
1585
|
+
item.base = { ...item.base, entrance: "up", entranceDuration: 720, entranceDelay: delay, entranceBezier: "0.16, 1, 0.3, 1" };
|
|
1586
|
+
return item;
|
|
1587
|
+
}
|
|
1588
|
+
function editorialNav(elements) {
|
|
1589
|
+
const nav = section(elements, "Editorial navigation", "#f3efe6", 22);
|
|
1590
|
+
const inner = container(elements, "Navigation inner", nav.id, { direction: "row", justify: "between", align: "center" });
|
|
1591
|
+
elements.push(el("Heading", "Editorial wordmark", { widthMode: "auto", heightMode: "auto", color: "#171714", fontSize: 20, fontWeight: "800", letterSpacing: -1 }, "FIELD NOTES", inner.id));
|
|
1592
|
+
const links = el("Container", "Navigation links", { widthMode: "auto", heightMode: "auto", layout: "stack", direction: "row", gap: 24, borderW: 0, padT: 0, padR: 0, padB: 0, padL: 0 }, void 0, inner.id);
|
|
1593
|
+
links.overrides = { mobile: { hidden: true } };
|
|
1594
|
+
elements.push(links);
|
|
1595
|
+
for (const [label, href] of [["Journal", "/blog"], ["About", "/about"]]) {
|
|
1596
|
+
const link = el("Button", `${label} link`, { widthMode: "auto", heightMode: "auto", bg: "transparent", color: "#58564f", padT: 4, padR: 0, padB: 4, padL: 0, fontSize: 13, cursor: "pointer" }, label, links.id);
|
|
1597
|
+
link.href = href;
|
|
1598
|
+
link.hover = { color: "#db4b2d" };
|
|
1599
|
+
elements.push(link);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
function createEditorialBlogTemplate() {
|
|
1603
|
+
const posts = { id: "editorial-posts", name: "Published posts", url: "https://dummyjson.com/posts", path: "posts", method: "GET", params: [{ key: "limit", value: "6" }] };
|
|
1604
|
+
const post = { id: "editorial-post", name: "Current post", url: "https://dummyjson.com/posts/{{params.slug}}", path: "", method: "GET", onNotFound: "page-404" };
|
|
1605
|
+
const list = [];
|
|
1606
|
+
editorialNav(list);
|
|
1607
|
+
const hero = section(list, "Journal hero", "#f3efe6", 90);
|
|
1608
|
+
const heroIn = container(list, "Journal hero inner", hero.id, { gap: 30 });
|
|
1609
|
+
list.push(motion(el("Text", "Issue label", { widthMode: "auto", heightMode: "auto", color: "#db4b2d", fontSize: 11, fontWeight: "800", letterSpacing: 2, textTransform: "uppercase" }, "Independent journal / Vol. 04", heroIn.id)));
|
|
1610
|
+
const title = motion(el("Heading", "Journal title", { widthMode: "fill", heightMode: "auto", color: "#171714", fontSize: 96, fontWeight: "500", lineHeight: 0.9, letterSpacing: -7 }, "IDEAS FOR A\nMORE HUMAN WEB.", heroIn.id), 70);
|
|
1611
|
+
title.overrides = { tablet: { fontSize: 68 }, mobile: { fontSize: 48, letterSpacing: -3 } };
|
|
1612
|
+
list.push(title);
|
|
1613
|
+
list.push(motion(el("Text", "Journal intro", { widthMode: "fixed", w: 570, heightMode: "auto", color: "#656158", fontSize: 17, lineHeight: 1.7 }, "Essays on design, technology and the quiet decisions that shape products people choose to keep.", heroIn.id), 140));
|
|
1614
|
+
const feed = section(list, "Latest writing", "#171714", 90);
|
|
1615
|
+
const feedIn = container(list, "Feed inner", feed.id, { gap: 38 });
|
|
1616
|
+
list.push(el("Heading", "Feed title", { widthMode: "fill", heightMode: "auto", color: "#f3efe6", fontSize: 38, fontWeight: "600" }, "Latest writing", feedIn.id));
|
|
1617
|
+
const repeat = el("Repeat", "Posts request grid", { widthMode: "fill", heightMode: "auto", layout: "stack", direction: "row", columns: 2, wrap: true, gap: 18 }, void 0, feedIn.id);
|
|
1618
|
+
repeat.sourceId = posts.id;
|
|
1619
|
+
repeat.overrides = { mobile: { columns: 1 } };
|
|
1620
|
+
list.push(repeat);
|
|
1621
|
+
const card = motion(el("Container", "Post card", { widthMode: "fill", heightMode: "auto", layout: "stack", direction: "column", gap: 20, bg: "#24231f", borderW: 1, borderC: "#393832", radius: 18, padT: 30, padR: 30, padB: 34, padL: 30 }, void 0, repeat.id));
|
|
1622
|
+
card.hover = { scale: 101.5, shadow: "0 24px 70px rgba(0,0,0,.28)" };
|
|
1623
|
+
list.push(card);
|
|
1624
|
+
const number = el("Text", "Post number", { widthMode: "auto", heightMode: "auto", color: "#db4b2d", fontSize: 11, fontWeight: "800" }, "01", card.id);
|
|
1625
|
+
number.binding = "id";
|
|
1626
|
+
list.push(number);
|
|
1627
|
+
const postTitle = el("Heading", "Post title", { widthMode: "fill", heightMode: "auto", color: "#f3efe6", fontSize: 27, fontWeight: "600", lineHeight: 1.15, letterSpacing: -1 }, "Article title", card.id);
|
|
1628
|
+
postTitle.binding = "title";
|
|
1629
|
+
list.push(postTitle);
|
|
1630
|
+
const excerpt = el("Text", "Post excerpt", { widthMode: "fill", heightMode: "auto", color: "#aaa69b", fontSize: 14, lineHeight: 1.65 }, "Article excerpt", card.id);
|
|
1631
|
+
excerpt.binding = "body";
|
|
1632
|
+
list.push(excerpt);
|
|
1633
|
+
const detail = [];
|
|
1634
|
+
editorialNav(detail);
|
|
1635
|
+
const article = section(detail, "Article", "#f3efe6", 100);
|
|
1636
|
+
const articleIn = container(detail, "Article inner", article.id, { w: 820, gap: 34 });
|
|
1637
|
+
detail.push(el("Text", "Dynamic route", { widthMode: "auto", heightMode: "auto", color: "#db4b2d", fontSize: 11, fontWeight: "800", letterSpacing: 1.6, textTransform: "uppercase" }, "Journal / Dynamic article", articleIn.id));
|
|
1638
|
+
const request = el("Request", "Current post request", { widthMode: "fill", heightMode: "auto", layout: "stack", direction: "column", gap: 34, align: "stretch" }, void 0, articleIn.id);
|
|
1639
|
+
request.sourceId = post.id;
|
|
1640
|
+
detail.push(request);
|
|
1641
|
+
const detailTitle = motion(el("Heading", "Dynamic title", { widthMode: "fill", heightMode: "auto", color: "#171714", fontSize: 68, fontWeight: "500", lineHeight: 1, letterSpacing: -4, textTransform: "capitalize" }, "Article title", request.id));
|
|
1642
|
+
detailTitle.binding = "title";
|
|
1643
|
+
detail.push(detailTitle);
|
|
1644
|
+
const body = el("Text", "Article body", { widthMode: "fill", heightMode: "auto", color: "#4f4c45", fontSize: 20, lineHeight: 1.85 }, "Article body", request.id);
|
|
1645
|
+
body.binding = "body";
|
|
1646
|
+
detail.push(body);
|
|
1647
|
+
const about = [];
|
|
1648
|
+
editorialNav(about);
|
|
1649
|
+
const aboutHero = section(about, "About Field Notes", "#db4b2d", 120);
|
|
1650
|
+
const aboutIn = container(about, "About inner", aboutHero.id, { gap: 36 });
|
|
1651
|
+
about.push(motion(el("Heading", "About title", { widthMode: "fill", heightMode: "auto", color: "#171714", fontSize: 82, fontWeight: "500", lineHeight: 0.95, letterSpacing: -5 }, "WE WRITE TO\nNOTICE BETTER.", aboutIn.id)));
|
|
1652
|
+
about.push(el("Text", "About copy", { widthMode: "fixed", w: 620, heightMode: "auto", color: "#2e261f", fontSize: 19, lineHeight: 1.75 }, "Field Notes is an independent publication exploring how thoughtful digital work gets made\u2014and why restraint is often the boldest decision.", aboutIn.id));
|
|
1653
|
+
return [
|
|
1654
|
+
{ name: "Home", slug: "home", elements: structuredClone(list), rootStyle: root("#f3efe6", "#171714", "#db4b2d"), dataSources: [structuredClone(posts)] },
|
|
1655
|
+
{ name: "Journal", slug: "blog", elements: list, rootStyle: root("#f3efe6", "#171714", "#db4b2d"), dataSources: [posts] },
|
|
1656
|
+
{ name: "Article", slug: "blog/:slug", elements: detail, rootStyle: root("#f3efe6", "#171714", "#db4b2d"), dataSources: [post] },
|
|
1657
|
+
{ name: "About", slug: "about", elements: about, rootStyle: root("#db4b2d", "#171714", "#f3efe6"), dataSources: [] }
|
|
1658
|
+
];
|
|
1659
|
+
}
|
|
1660
|
+
function createOrbitTemplate() {
|
|
1661
|
+
const build = (name, slug, eyebrow, titleText, copy) => {
|
|
1662
|
+
const elements = [];
|
|
1663
|
+
const nav = section(elements, "Orbit navigation", "#070914", 20);
|
|
1664
|
+
const navIn = container(elements, "Orbit navigation inner", nav.id, { direction: "row", justify: "between", align: "center" });
|
|
1665
|
+
elements.push(el("Heading", "Orbit logo", { widthMode: "auto", heightMode: "auto", color: "#f5f7ff", fontSize: 19, fontWeight: "800" }, "ORBIT/OS", navIn.id));
|
|
1666
|
+
const cta = el("Button", "Navigation action", { widthMode: "auto", heightMode: "auto", bg: "#7357ff", color: "#ffffff", radius: 999, padT: 11, padR: 18, padB: 11, padL: 18, fontSize: 12, fontWeight: "700" }, "Start building", navIn.id);
|
|
1667
|
+
cta.hover = { scale: 103, shadow: "0 14px 40px rgba(115,87,255,.35)" };
|
|
1668
|
+
elements.push(cta);
|
|
1669
|
+
const hero = section(elements, `${name} hero`, "#070914", 110);
|
|
1670
|
+
const heroIn = container(elements, "Orbit hero inner", hero.id, { align: "center", gap: 28 });
|
|
1671
|
+
elements.push(motion(el("Text", "Orbit eyebrow", { widthMode: "auto", heightMode: "auto", color: "#9e8cff", fontSize: 11, fontWeight: "800", letterSpacing: 2, textTransform: "uppercase" }, eyebrow, heroIn.id)));
|
|
1672
|
+
const heading = motion(el("Heading", "Orbit heading", { widthMode: "fill", heightMode: "auto", color: "#f5f7ff", fontSize: 80, fontWeight: "600", lineHeight: 0.95, letterSpacing: -5, textAlign: "center" }, titleText, heroIn.id), 70);
|
|
1673
|
+
heading.overrides = { tablet: { fontSize: 62 }, mobile: { fontSize: 44, letterSpacing: -2.5 } };
|
|
1674
|
+
elements.push(heading);
|
|
1675
|
+
elements.push(motion(el("Text", "Orbit copy", { widthMode: "fixed", w: 620, heightMode: "auto", color: "#9298b3", fontSize: 17, lineHeight: 1.7, textAlign: "center" }, copy, heroIn.id), 140));
|
|
1676
|
+
const visual = motion(el("Frame", "Product interface", { widthMode: "fill", heightMode: "fixed", h: 520, bg: "#101426", gradient: "radial-gradient(circle at 50% 0%,rgba(115,87,255,.42),transparent 46%),linear-gradient(145deg,#11162b,#090b16)", borderW: 1, borderC: "#282e4d", radius: 24, shadow: "0 60px 140px rgba(0,0,0,.48)", overflow: "hidden" }, void 0, heroIn.id), 210);
|
|
1677
|
+
visual.overrides = { mobile: { h: 330 } };
|
|
1678
|
+
elements.push(visual);
|
|
1679
|
+
const metrics = section(elements, "Product metrics", "#0d1020", 80);
|
|
1680
|
+
const grid = container(elements, "Metrics grid", metrics.id, { direction: "row", gap: 16 });
|
|
1681
|
+
[["32%", "faster launches"], ["4.8\xD7", "team velocity"], ["99.99%", "platform uptime"]].forEach(([value, label], index2) => {
|
|
1682
|
+
const card = motion(el("Container", `Metric ${index2 + 1}`, { widthMode: "fill", heightMode: "auto", layout: "stack", direction: "column", gap: 9, bg: "#14182b", borderW: 1, borderC: "#272c47", radius: 18, padT: 28, padR: 28, padB: 28, padL: 28 }, void 0, grid.id), index2 * 80);
|
|
1683
|
+
elements.push(card, el("Heading", "Metric value", { widthMode: "fill", heightMode: "auto", color: "#f5f7ff", fontSize: 36, fontWeight: "700" }, value, card.id), el("Text", "Metric label", { widthMode: "fill", heightMode: "auto", color: "#858ca7", fontSize: 13 }, label, card.id));
|
|
1684
|
+
});
|
|
1685
|
+
return { name, slug, elements, rootStyle: root("#070914", "#f5f7ff", "#7357ff"), dataSources: [] };
|
|
1686
|
+
};
|
|
1687
|
+
return [
|
|
1688
|
+
build("Home", "home", "The adaptive product platform", "SHIP THE NEXT\nVERSION OF YOU.", "Orbit gives product teams one composable workspace for planning, building and learning\u2014without the operational drag."),
|
|
1689
|
+
build("Product", "product", "One system, every workflow", "FROM SIGNAL\nTO SHIPPED.", "Connect research, decisions, design and delivery in a system that keeps context alive from first insight to final release."),
|
|
1690
|
+
build("Pricing", "pricing", "Simple by design", "PRICING THAT\nSCALES CALMLY.", "Start with the essentials, then expand across teams without surprise platform fees or a maze of artificial limits.")
|
|
1691
|
+
];
|
|
1692
|
+
}
|
|
1693
|
+
function createSiteTemplate(id) {
|
|
1694
|
+
if (id === "nocturne") return createNocturneSiteTemplate();
|
|
1695
|
+
if (id === "editorial-blog") return createEditorialBlogTemplate();
|
|
1696
|
+
return createOrbitTemplate();
|
|
1697
|
+
}
|
|
1698
|
+
var init_site_templates = __esm({
|
|
1699
|
+
"src/internal/lib/editor/site-templates.ts"() {
|
|
1700
|
+
"use strict";
|
|
1701
|
+
init_tree();
|
|
1702
|
+
init_types();
|
|
1703
|
+
init_showcase();
|
|
1704
|
+
}
|
|
1705
|
+
});
|
|
1706
|
+
|
|
1707
|
+
// src/internal/app/api/ai-design/route.ts
|
|
1708
|
+
var route_exports = {};
|
|
1709
|
+
__export(route_exports, {
|
|
1710
|
+
POST: () => POST,
|
|
1711
|
+
maxDuration: () => maxDuration
|
|
1712
|
+
});
|
|
1713
|
+
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
1714
|
+
import { generateText, Output } from "ai";
|
|
1715
|
+
import { z } from "zod";
|
|
1716
|
+
async function POST(request) {
|
|
1717
|
+
const apiKey = process.env.OPENROUTER_API_KEY;
|
|
1718
|
+
if (!apiKey) return Response.json({ error: "OPENROUTER_API_KEY is not configured." }, { status: 503 });
|
|
1719
|
+
try {
|
|
1720
|
+
const body = await request.json();
|
|
1721
|
+
if (typeof body.prompt !== "string" || !body.prompt.trim()) return Response.json({ error: "Prompt is required." }, { status: 400 });
|
|
1722
|
+
const openrouter = createOpenRouter({ apiKey });
|
|
1723
|
+
const model = openrouter(
|
|
1724
|
+
process.env.OPENROUTER_MODEL ?? "anthropic/claude-sonnet-4.5"
|
|
1725
|
+
);
|
|
1726
|
+
const requestContext = {
|
|
1727
|
+
request: body.prompt.slice(0, 4e3),
|
|
1728
|
+
activeBreakpoint: body.breakpoint,
|
|
1729
|
+
recentConversation: body.history,
|
|
1730
|
+
document: body.document
|
|
1731
|
+
};
|
|
1732
|
+
const { output: blueprint } = await generateText({
|
|
1733
|
+
model,
|
|
1734
|
+
maxRetries: 2,
|
|
1735
|
+
maxOutputTokens: 5e3,
|
|
1736
|
+
output: Output.object({ schema: blueprintSchema, name: "pagiera_blueprint" }),
|
|
1737
|
+
system: `You are an award-winning digital art director and UX architect. Plan before building.
|
|
1738
|
+
Create a specific, contemporary visual direction\u2014not generic SaaS filler. Establish one exact palette, typography hierarchy, spacing rhythm and a section-by-section composition. The signature must name one repeatable visual motif, a concrete asymmetric hero composition, a motion rhythm and explicit clich\xE9s to avoid.
|
|
1739
|
+
For a full page, plan 5-8 substantial sections with meaningful visual variety: navigation, hero, proof, editorial or feature storytelling, metrics/testimonial, conversion CTA and footer. Each section needs layered details, not only a heading and paragraph.
|
|
1740
|
+
Avoid the default AI look: endless equal cards, centered text in every section, neon purple gradients, excessive pills, fake dashboard rectangles, glow on everything, and identical rounded boxes. Mix editorial whitespace with one or two dense moments. Each section must have a distinct silhouette while sharing the same system.
|
|
1741
|
+
Use the existing document intelligently. If the user asks a conversational question, set intent=conversation and keep sections empty. Preserve good existing work unless replacement was requested.
|
|
1742
|
+
Treat motion as part of the visual direction: define a restrained entrance rhythm, one shared easing character, and a clear focal moment. Animation must reinforce hierarchy rather than decorate every node.`,
|
|
1743
|
+
prompt: JSON.stringify(requestContext).slice(0, 6e4)
|
|
1744
|
+
});
|
|
1745
|
+
const { output: draft } = await generateText({
|
|
1746
|
+
model,
|
|
1747
|
+
maxRetries: 2,
|
|
1748
|
+
maxOutputTokens: 24e3,
|
|
1749
|
+
output: Output.object({ schema: planSchema, name: "pagiera_design_plan" }),
|
|
1750
|
+
system: `You are Pagiera's senior web designer. Turn the request into precise editor operations.
|
|
1751
|
+
Use only existing element IDs from the document for update/remove/parentId. For newly added parents, reference their ref in later parentId fields.
|
|
1752
|
+
Build polished, production-ready layouts with strong hierarchy, spacing, responsive-friendly fill/auto sizing, accessible contrast, and concise real copy. Follow approvedBlueprint.signature literally so the result has an identifiable art direction.
|
|
1753
|
+
Compose complete pages from recognizable sections: Navbar, Hero, social proof, feature grid, CTA and Footer. A Navbar is a row Section with justify=between and align=center; a Footer is a spacious Section with grouped link Containers. Do not output a random pile of Text and Button nodes.
|
|
1754
|
+
Sections and containers should normally use stack layout. Prefer full-width Sections, auto heights and nested Containers. Put siblings in a stack parent rather than positioning them manually.
|
|
1755
|
+
Use Frame for bounded visual compositions, Stack for row/column groups, and Grid for repeated cards. For full page builds create page variables for the exact palette and core spacing values, then bind repeated style properties with styleBindings. IDs in styleBindings must match those page variables.
|
|
1756
|
+
Alignment rules are strict: justify controls the parent's main axis; align controls its cross axis. To center a column vertically and horizontally use justify=center and align=center. A child with widthMode=fill cannot visibly center on the cross axis, so use fixed/auto for centered content and fill only when it should stretch. Use textAlign=center for centered copy.
|
|
1757
|
+
Before operations, provide 2-6 short implementation steps in the steps field.
|
|
1758
|
+
Create one coherent design system per page: choose exactly one page background, one surface color, one primary accent, one main text color and one muted text color. Reuse those exact values throughout all operations. Do not introduce unrelated colors section by section. Use at most one subtle gradient and only when it supports the chosen palette.
|
|
1759
|
+
Use consistent spacing (8/12/16/24/32/48/64/96), restrained radii, modern typography, generous whitespace, and no placeholder copy. Never invent unsupported element types.
|
|
1760
|
+
Implement every blueprint section with enough nested elements to make it visually complete. Full-page builds should normally contain 22-55 purposeful operations. Quality, composition and hierarchy matter more than node count. Add responsive tabletStyle/mobileStyle for structural, spacing and typography changes.
|
|
1761
|
+
Do not make every section a centered column or every feature an equal card. Use at least three composition patterns across a full page: asymmetric split, editorial text/media, structured grid, horizontal proof strip, oversized statement or layered visual frame. Keep readable content inside a consistent inner Container while Section backgrounds may fill the viewport.
|
|
1762
|
+
Headings should use a deliberate type scale and tight line height; body copy should stay readable and constrained. Buttons must size to content unless intentionally full width on mobile. Never use fixed heights for text-heavy sections.
|
|
1763
|
+
Create Framer-quality motion choreography. Stagger related entranceDelay values by 60-120ms, reuse a coherent entranceBezier, and choose spring only for expressive focal elements. Add restrained hoverStyle to interactive buttons/cards and pressStyle to primary controls. At most one subtle ornamental element may use a pulse, float, or spin loop. Enable draggable only when it has an intentional product interaction. Never animate every element or mix unrelated easing styles.
|
|
1764
|
+
Scale values are percentages: 100 is the resting size, a subtle hover is 101-103, and a press is 97-99. Never output CSS multipliers such as 1.02 or 0.98.
|
|
1765
|
+
Use custom font families already registered in document.rootStyle.customFonts by their exact names when they suit the direction. Never invent a font URL or an unregistered custom family; otherwise use the document's existing font stack.
|
|
1766
|
+
Use interaction for real navigation buttons and scroll-to navigation items. Build reusable visual patterns consistently so they can later become component masters and variants.
|
|
1767
|
+
Responsive navbars should create the hidden mobile menu layer before the menu trigger, then set the trigger interaction to toggle-layer using that earlier ref. Use show-layer/hide-layer for overlays and close buttons. Tablet/mobile layouts must reorganize rather than merely shrink.
|
|
1768
|
+
If the request is conversational or needs no visual change, return no operations and answer in message. Do not remove existing work unless explicitly asked.`,
|
|
1769
|
+
prompt: JSON.stringify({ ...requestContext, approvedBlueprint: blueprint }).slice(0, 9e4)
|
|
1770
|
+
});
|
|
1771
|
+
if (!draft) throw new Error("The model returned an empty design plan.");
|
|
1772
|
+
let finalPlan = draft;
|
|
1773
|
+
if (blueprint.intent === "build-page" && draft.operations.length > 0) {
|
|
1774
|
+
try {
|
|
1775
|
+
const { output: polished } = await generateText({
|
|
1776
|
+
model,
|
|
1777
|
+
maxRetries: 1,
|
|
1778
|
+
maxOutputTokens: 26e3,
|
|
1779
|
+
output: Output.object({ schema: planSchema, name: "pagiera_polished_design_plan" }),
|
|
1780
|
+
system: `You are the final design director and implementation reviewer for Pagiera. Return a complete replacement plan, not commentary.
|
|
1781
|
+
Audit the draft ruthlessly, then rewrite its operations into a refined, buildable landing page. Preserve the user's intent and approved palette, but remove generic AI aesthetics.
|
|
1782
|
+
|
|
1783
|
+
QUALITY GATE:
|
|
1784
|
+
- Every add parentId must reference an existing document ID or an earlier add ref. Parents always precede children.
|
|
1785
|
+
- Page uses stack/column flow. Full-width Sections contain bounded inner Containers. Text-heavy sections use auto height.
|
|
1786
|
+
- Desktop composition feels art-directed, not templated. Use varied silhouettes and asymmetric hierarchy without random absolute positioning.
|
|
1787
|
+
- Tablet and mobile overrides prevent overflow, collapse rows/grids, reduce padding and maintain readable type. Mobile must never be a scaled-down desktop screenshot.
|
|
1788
|
+
- Use one exact color system, one radius language and one shadow language. Remove gratuitous gradients, glass, glow, pills and borders.
|
|
1789
|
+
- Motion is a coherent sequence: restrained staggered entrances, meaningful hover/press feedback, no more than one ambient loop.
|
|
1790
|
+
- Copy is specific and credible. Remove placeholder metrics, lorem ipsum and repetitive labels.
|
|
1791
|
+
- Keep 22-55 purposeful operations when building a full page. Do not lose required sections while simplifying.
|
|
1792
|
+
|
|
1793
|
+
Return only the corrected plan matching the schema. Its message should briefly describe the finished visual direction and its steps should describe what was actually built.`,
|
|
1794
|
+
prompt: JSON.stringify({ requestContext, approvedBlueprint: blueprint, draftPlan: draft }).slice(0, 115e3)
|
|
1795
|
+
});
|
|
1796
|
+
if (polished?.operations.length) finalPlan = polished;
|
|
1797
|
+
} catch (polishError) {
|
|
1798
|
+
console.warn("AI polish pass failed; returning valid draft", polishError);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
return Response.json(finalPlan);
|
|
1802
|
+
} catch (reason) {
|
|
1803
|
+
const rawMessage = reason instanceof Error ? reason.message : "Unknown AI provider error";
|
|
1804
|
+
console.error("AI design request failed", reason);
|
|
1805
|
+
const truncated = /unexpected end|unterminated|parse|json|no object generated|length|token/i.test(rawMessage);
|
|
1806
|
+
return Response.json(
|
|
1807
|
+
{
|
|
1808
|
+
error: truncated ? "AI yan\u0131t\u0131 tamamlanmadan kesildi. \u0130stek otomatik olarak korunuyor; tekrar deneyebilir veya daha k\xFC\xE7\xFCk bir b\xF6l\xFCm isteyebilirsin." : `AI tasar\u0131m\u0131 olu\u015Fturulamad\u0131: ${rawMessage.slice(0, 240)}`,
|
|
1809
|
+
code: truncated ? "INCOMPLETE_AI_RESPONSE" : "AI_PROVIDER_ERROR"
|
|
1810
|
+
},
|
|
1811
|
+
{ status: truncated ? 422 : 502 }
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
var maxDuration, styleSchema, bindingsSchema, interactionSchema, loopSchema, planSchema, blueprintSchema;
|
|
1816
|
+
var init_route = __esm({
|
|
1817
|
+
"src/internal/app/api/ai-design/route.ts"() {
|
|
1818
|
+
"use strict";
|
|
1819
|
+
init_types();
|
|
1820
|
+
maxDuration = 120;
|
|
1821
|
+
styleSchema = z.object({
|
|
1822
|
+
x: z.number().optional(),
|
|
1823
|
+
y: z.number().optional(),
|
|
1824
|
+
constraintX: z.enum(["start", "center", "end", "stretch"]).optional(),
|
|
1825
|
+
constraintY: z.enum(["start", "center", "end", "stretch"]).optional(),
|
|
1826
|
+
w: z.number().positive().optional(),
|
|
1827
|
+
h: z.number().positive().optional(),
|
|
1828
|
+
widthMode: z.enum(["fixed", "fill", "auto"]).optional(),
|
|
1829
|
+
heightMode: z.enum(["fixed", "fill", "auto"]).optional(),
|
|
1830
|
+
layout: z.enum(["absolute", "stack"]).optional(),
|
|
1831
|
+
direction: z.enum(["row", "column"]).optional(),
|
|
1832
|
+
gap: z.number().nonnegative().optional(),
|
|
1833
|
+
padT: z.number().nonnegative().optional(),
|
|
1834
|
+
padR: z.number().nonnegative().optional(),
|
|
1835
|
+
padB: z.number().nonnegative().optional(),
|
|
1836
|
+
padL: z.number().nonnegative().optional(),
|
|
1837
|
+
justify: z.enum(["start", "center", "end", "between"]).optional(),
|
|
1838
|
+
align: z.enum(["start", "center", "end", "stretch"]).optional(),
|
|
1839
|
+
wrap: z.boolean().optional(),
|
|
1840
|
+
columns: z.number().int().min(1).max(12).optional(),
|
|
1841
|
+
bg: z.string().optional(),
|
|
1842
|
+
gradient: z.string().optional(),
|
|
1843
|
+
color: z.string().optional(),
|
|
1844
|
+
radius: z.number().nonnegative().optional(),
|
|
1845
|
+
opacity: z.number().min(0).max(100).optional(),
|
|
1846
|
+
borderW: z.number().nonnegative().optional(),
|
|
1847
|
+
borderC: z.string().optional(),
|
|
1848
|
+
borderStyle: z.enum(["solid", "dashed", "dotted"]).optional(),
|
|
1849
|
+
shadow: z.string().optional(),
|
|
1850
|
+
rotate: z.number().optional(),
|
|
1851
|
+
fontSize: z.number().positive().optional(),
|
|
1852
|
+
fontWeight: z.string().optional(),
|
|
1853
|
+
lineHeight: z.number().positive().optional(),
|
|
1854
|
+
letterSpacing: z.number().optional(),
|
|
1855
|
+
textAlign: z.enum(["left", "center", "right", "justify"]).optional(),
|
|
1856
|
+
textTransform: z.enum(["none", "uppercase", "lowercase", "capitalize"]).optional(),
|
|
1857
|
+
overflow: z.enum(["visible", "hidden", "auto", "scroll"]).optional(),
|
|
1858
|
+
position: z.enum(["static", "sticky"]).optional(),
|
|
1859
|
+
stickyOffset: z.number().optional(),
|
|
1860
|
+
bgImage: z.string().optional(),
|
|
1861
|
+
bgSize: z.enum(["cover", "contain", "auto"]).optional(),
|
|
1862
|
+
bgPosition: z.string().optional(),
|
|
1863
|
+
backdropBlur: z.number().nonnegative().optional(),
|
|
1864
|
+
blur: z.number().nonnegative().optional(),
|
|
1865
|
+
scale: z.number().min(1).max(500).optional().describe("Percentage scale: 100 is normal, 102 is two percent larger"),
|
|
1866
|
+
aspectRatio: z.string().optional(),
|
|
1867
|
+
entrance: z.enum(["none", "fade", "up", "down", "left", "right", "zoom"]).optional(),
|
|
1868
|
+
entranceDuration: z.number().nonnegative().optional(),
|
|
1869
|
+
entranceDelay: z.number().nonnegative().optional(),
|
|
1870
|
+
entranceCurve: z.enum(["ease", "spring"]).optional(),
|
|
1871
|
+
entranceBezier: z.string().optional(),
|
|
1872
|
+
springStiffness: z.number().positive().optional(),
|
|
1873
|
+
springDamping: z.number().positive().optional(),
|
|
1874
|
+
cursor: z.enum(["auto", "default", "pointer", "text", "grab", "zoom-in", "none"]).optional(),
|
|
1875
|
+
hidden: z.boolean().optional()
|
|
1876
|
+
}).strict();
|
|
1877
|
+
bindingsSchema = z.record(z.string(), z.string().max(60)).optional();
|
|
1878
|
+
interactionSchema = z.object({ trigger: z.literal("click"), action: z.enum(["navigate", "scroll-to", "toggle-layer", "show-layer", "hide-layer"]), value: z.string().max(2e3), target: z.enum(["_self", "_blank"]).optional() }).optional();
|
|
1879
|
+
loopSchema = z.object({ type: z.enum(["pulse", "float", "spin"]), duration: z.number().min(100).max(2e4) }).optional();
|
|
1880
|
+
planSchema = z.object({
|
|
1881
|
+
message: z.string().max(600),
|
|
1882
|
+
steps: z.array(z.string().max(180)).min(1).max(6),
|
|
1883
|
+
operations: z.array(
|
|
1884
|
+
z.discriminatedUnion("kind", [
|
|
1885
|
+
z.object({ kind: z.literal("add"), ref: z.string().min(1).max(60), type: z.enum(ELEMENT_TYPES), parentId: z.string().nullable().optional(), content: z.string().max(5e3).optional(), src: z.string().max(2e3).optional(), href: z.string().max(2e3).optional(), style: styleSchema.optional(), tabletStyle: styleSchema.optional(), mobileStyle: styleSchema.optional(), hoverStyle: styleSchema.optional(), pressStyle: styleSchema.optional(), loop: loopSchema, draggable: z.boolean().optional(), styleBindings: bindingsSchema, interaction: interactionSchema }),
|
|
1886
|
+
z.object({ kind: z.literal("update"), id: z.string(), content: z.string().max(5e3).optional(), src: z.string().max(2e3).optional(), href: z.string().max(2e3).optional(), style: styleSchema.optional(), tabletStyle: styleSchema.optional(), mobileStyle: styleSchema.optional(), hoverStyle: styleSchema.optional(), pressStyle: styleSchema.optional(), loop: loopSchema, draggable: z.boolean().optional(), styleBindings: bindingsSchema, interaction: interactionSchema }),
|
|
1887
|
+
z.object({ kind: z.literal("remove"), id: z.string() }),
|
|
1888
|
+
z.object({ kind: z.literal("page"), style: z.object({ documentMode: z.enum(["page", "component"]).optional(), maxWidth: z.number().min(1).max(4e3).optional(), canvasHeight: z.number().min(1).max(12e3).optional(), fullWidth: z.boolean().optional(), bg: z.string().optional(), layout: z.enum(["absolute", "stack"]).optional(), direction: z.enum(["row", "column"]).optional(), gap: z.number().nonnegative().optional(), padT: z.number().nonnegative().optional(), padR: z.number().nonnegative().optional(), padB: z.number().nonnegative().optional(), padL: z.number().nonnegative().optional(), align: z.enum(["start", "center", "end", "stretch"]).optional(), fontFamily: z.string().optional(), variables: z.array(z.object({ id: z.string().max(60), name: z.string().max(60), type: z.enum(["color", "number"]), value: z.union([z.string(), z.number()]) })).max(24).optional() }).strict() })
|
|
1889
|
+
])
|
|
1890
|
+
).max(100)
|
|
1891
|
+
});
|
|
1892
|
+
blueprintSchema = z.object({
|
|
1893
|
+
intent: z.enum(["conversation", "modify", "build-page"]),
|
|
1894
|
+
direction: z.string().max(500),
|
|
1895
|
+
palette: z.object({
|
|
1896
|
+
background: z.string(),
|
|
1897
|
+
surface: z.string(),
|
|
1898
|
+
primary: z.string(),
|
|
1899
|
+
text: z.string(),
|
|
1900
|
+
muted: z.string(),
|
|
1901
|
+
border: z.string()
|
|
1902
|
+
}),
|
|
1903
|
+
typography: z.object({
|
|
1904
|
+
heading: z.string(),
|
|
1905
|
+
body: z.string(),
|
|
1906
|
+
scale: z.array(z.number()).min(3).max(7)
|
|
1907
|
+
}),
|
|
1908
|
+
signature: z.object({
|
|
1909
|
+
visualMotif: z.string().max(240),
|
|
1910
|
+
heroComposition: z.string().max(320),
|
|
1911
|
+
motionRhythm: z.string().max(240),
|
|
1912
|
+
avoid: z.array(z.string().max(120)).min(3).max(8)
|
|
1913
|
+
}),
|
|
1914
|
+
sections: z.array(
|
|
1915
|
+
z.object({
|
|
1916
|
+
name: z.string(),
|
|
1917
|
+
purpose: z.string(),
|
|
1918
|
+
composition: z.string(),
|
|
1919
|
+
details: z.array(z.string()).min(2).max(8)
|
|
1920
|
+
})
|
|
1921
|
+
).max(12)
|
|
1922
|
+
});
|
|
1923
|
+
}
|
|
1924
|
+
});
|
|
1925
|
+
|
|
1926
|
+
// src/internal/lib/render/load-data.ts
|
|
1927
|
+
var load_data_exports = {};
|
|
1928
|
+
__export(load_data_exports, {
|
|
1929
|
+
PageDataNotFoundError: () => PageDataNotFoundError,
|
|
1930
|
+
loadPageData: () => loadPageData
|
|
1931
|
+
});
|
|
1932
|
+
async function loadPageData(elements, sources, options) {
|
|
1933
|
+
const used = new Set(
|
|
1934
|
+
elements.filter((el2) => el2.sourceId).map((el2) => el2.sourceId)
|
|
1935
|
+
);
|
|
1936
|
+
const wanted = sources.filter((source) => used.has(source.id) && source.url);
|
|
1937
|
+
if (wanted.length === 0) return {};
|
|
1938
|
+
const results = await Promise.all(
|
|
1939
|
+
wanted.map(async (source) => {
|
|
1940
|
+
try {
|
|
1941
|
+
const { rows } = await loadSource(source, options);
|
|
1942
|
+
return [source.id, rows];
|
|
1943
|
+
} catch (error) {
|
|
1944
|
+
if (error instanceof DataSourceError && error.status === 404 && source.onNotFound === "page-404") {
|
|
1945
|
+
throw new PageDataNotFoundError(source.id);
|
|
1946
|
+
}
|
|
1947
|
+
console.warn(
|
|
1948
|
+
`Data source "${source.name}" failed:`,
|
|
1949
|
+
error instanceof DataSourceError ? error.message : error
|
|
1950
|
+
);
|
|
1951
|
+
return [source.id, []];
|
|
1952
|
+
}
|
|
1953
|
+
})
|
|
1954
|
+
);
|
|
1955
|
+
return Object.fromEntries(results);
|
|
1956
|
+
}
|
|
1957
|
+
var PageDataNotFoundError;
|
|
1958
|
+
var init_load_data = __esm({
|
|
1959
|
+
"src/internal/lib/render/load-data.ts"() {
|
|
1960
|
+
"use strict";
|
|
1961
|
+
init_source();
|
|
1962
|
+
PageDataNotFoundError = class extends Error {
|
|
1963
|
+
constructor(sourceId) {
|
|
1964
|
+
super(`Data source ${sourceId} returned 404.`);
|
|
1965
|
+
this.sourceId = sourceId;
|
|
1966
|
+
this.name = "PageDataNotFoundError";
|
|
1967
|
+
}
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
});
|
|
1971
|
+
|
|
1972
|
+
// src/server-source.ts
|
|
1973
|
+
import { Pool as Pool2 } from "pg";
|
|
1974
|
+
import { createClient } from "redis";
|
|
1975
|
+
|
|
1976
|
+
// src/manrope-data.ts
|
|
1977
|
+
var manropeDataUrl = "data:font/woff2;base64,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";
|
|
1978
|
+
var manrope_data_default = manropeDataUrl;
|
|
1979
|
+
|
|
1980
|
+
// src/server-source.ts
|
|
1981
|
+
var SCHEMA_SQL = `
|
|
1982
|
+
CREATE TABLE IF NOT EXISTS sites (
|
|
1983
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), name text NOT NULL, slug text NOT NULL UNIQUE,
|
|
1984
|
+
created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now()
|
|
1985
|
+
);
|
|
1986
|
+
CREATE TABLE IF NOT EXISTS pages (
|
|
1987
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), site_id uuid NOT NULL REFERENCES sites(id) ON DELETE CASCADE,
|
|
1988
|
+
name text NOT NULL, slug text NOT NULL, elements jsonb NOT NULL DEFAULT '[]', root_style jsonb NOT NULL DEFAULT '{}',
|
|
1989
|
+
data_sources jsonb NOT NULL DEFAULT '[]', version integer NOT NULL DEFAULT 1,
|
|
1990
|
+
published_elements jsonb, published_root_style jsonb, published_data_sources jsonb, published_at timestamptz,
|
|
1991
|
+
created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE(site_id, slug)
|
|
1992
|
+
);
|
|
1993
|
+
CREATE INDEX IF NOT EXISTS pages_site_id_idx ON pages(site_id);
|
|
1994
|
+
CREATE TABLE IF NOT EXISTS page_revisions (
|
|
1995
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), page_id uuid NOT NULL REFERENCES pages(id) ON DELETE CASCADE,
|
|
1996
|
+
version integer NOT NULL, elements jsonb NOT NULL, root_style jsonb, created_at timestamptz NOT NULL DEFAULT now()
|
|
1997
|
+
);
|
|
1998
|
+
CREATE INDEX IF NOT EXISTS page_revisions_page_id_created_at_idx ON page_revisions(page_id, created_at);
|
|
1999
|
+
`;
|
|
2000
|
+
function required(name, value) {
|
|
2001
|
+
if (!value?.trim()) throw new Error(`Pagiera config is missing ${name}.`);
|
|
2002
|
+
}
|
|
2003
|
+
async function createPagieraServer(config) {
|
|
2004
|
+
required("postgresUrl", config.postgresUrl);
|
|
2005
|
+
required("redisUrl", config.redisUrl);
|
|
2006
|
+
required("openRouterApiKey", config.openRouterApiKey);
|
|
2007
|
+
required("openRouterModel", config.openRouterModel);
|
|
2008
|
+
process.env.DATABASE_URL = config.postgresUrl;
|
|
2009
|
+
process.env.OPENROUTER_API_KEY = config.openRouterApiKey;
|
|
2010
|
+
process.env.OPENROUTER_MODEL = config.openRouterModel;
|
|
2011
|
+
const pool2 = new Pool2({ connectionString: config.postgresUrl, max: 10, idleTimeoutMillis: 3e4 });
|
|
2012
|
+
await pool2.query(SCHEMA_SQL);
|
|
2013
|
+
const redis = createClient({ url: config.redisUrl });
|
|
2014
|
+
redis.on("error", (error) => console.error("Pagiera Redis error", error));
|
|
2015
|
+
await redis.connect();
|
|
2016
|
+
const pages2 = await Promise.resolve().then(() => (init_pages(), pages_exports));
|
|
2017
|
+
const validation = await Promise.resolve().then(() => (init_validate(), validate_exports));
|
|
2018
|
+
const sourceRuntime = await Promise.resolve().then(() => (init_source(), source_exports));
|
|
2019
|
+
const templates = await Promise.resolve().then(() => (init_site_templates(), site_templates_exports));
|
|
2020
|
+
const ai = await Promise.resolve().then(() => (init_route(), route_exports));
|
|
2021
|
+
const renderData = await Promise.resolve().then(() => (init_load_data(), load_data_exports));
|
|
2022
|
+
const basePath = (config.basePath ?? "/api/pagiera").replace(/\/$/, "");
|
|
2023
|
+
const invalidate = async () => {
|
|
2024
|
+
const keys = await redis.keys("pagiera:published:*");
|
|
2025
|
+
if (keys.length) await redis.del(keys);
|
|
2026
|
+
};
|
|
2027
|
+
const bodyOf = async (request) => request.json().catch(() => ({}));
|
|
2028
|
+
const ok = (value, status = 200) => Response.json(value, { status });
|
|
2029
|
+
const fail = (error, status = 400) => ok({ error: error instanceof Error ? error.message : String(error) }, status);
|
|
2030
|
+
function matchPagePath(pattern, pathname) {
|
|
2031
|
+
const patternParts = pattern.split("/").filter(Boolean);
|
|
2032
|
+
const pathParts = pathname.split("/").filter(Boolean);
|
|
2033
|
+
if (patternParts.length !== pathParts.length) return void 0;
|
|
2034
|
+
const params = {};
|
|
2035
|
+
for (let index2 = 0; index2 < patternParts.length; index2 += 1) {
|
|
2036
|
+
const part = patternParts[index2];
|
|
2037
|
+
const value = pathParts[index2];
|
|
2038
|
+
if (part.startsWith(":")) params[part.slice(1)] = value;
|
|
2039
|
+
else if (part !== value) return void 0;
|
|
2040
|
+
}
|
|
2041
|
+
return params;
|
|
2042
|
+
}
|
|
2043
|
+
async function resolvePublishedPath(pathname) {
|
|
2044
|
+
const normalized = pathname.replace(/^\/+|\/+$/g, "") || "home";
|
|
2045
|
+
const exact = await pages2.getPublishedPage(normalized);
|
|
2046
|
+
if (exact) return { page: exact, pattern: normalized, params: {} };
|
|
2047
|
+
const patterns = (await pages2.listPublishedSlugs()).map((entry) => entry.slug).filter((slug) => slug.includes(":")).sort((left, right) => {
|
|
2048
|
+
const staticDifference = right.split("/").filter((part) => !part.startsWith(":")).length - left.split("/").filter((part) => !part.startsWith(":")).length;
|
|
2049
|
+
return staticDifference || right.length - left.length;
|
|
2050
|
+
});
|
|
2051
|
+
for (const pattern of patterns) {
|
|
2052
|
+
const params = matchPagePath(pattern, normalized);
|
|
2053
|
+
if (!params) continue;
|
|
2054
|
+
const page = await pages2.getPublishedPage(pattern);
|
|
2055
|
+
if (page) return { page, pattern, params };
|
|
2056
|
+
}
|
|
2057
|
+
return void 0;
|
|
2058
|
+
}
|
|
2059
|
+
async function getPublishedDocument(slug) {
|
|
2060
|
+
const normalized = slug.replace(/^\/+|\/+$/g, "") || "home";
|
|
2061
|
+
const key = `pagiera:published:${normalized}`;
|
|
2062
|
+
const cached = await redis.get(key);
|
|
2063
|
+
if (cached) return JSON.parse(cached);
|
|
2064
|
+
const resolved = await resolvePublishedPath(normalized);
|
|
2065
|
+
if (!resolved) return void 0;
|
|
2066
|
+
const document = { ...resolved.page, routePattern: resolved.pattern, routeParams: resolved.params };
|
|
2067
|
+
await redis.set(key, JSON.stringify(document), { EX: 60 });
|
|
2068
|
+
return document;
|
|
2069
|
+
}
|
|
2070
|
+
async function getPublishedPage2(slug, context) {
|
|
2071
|
+
const page = await getPublishedDocument(slug);
|
|
2072
|
+
if (!page) return void 0;
|
|
2073
|
+
let data;
|
|
2074
|
+
try {
|
|
2075
|
+
data = await renderData.loadPageData(page.elements, page.dataSources ?? [], {
|
|
2076
|
+
context: {
|
|
2077
|
+
query: {},
|
|
2078
|
+
page: { slug: page.routePattern ?? slug },
|
|
2079
|
+
...context ?? {},
|
|
2080
|
+
params: { ...page.routeParams ?? {}, ...context?.params ?? {} }
|
|
2081
|
+
}
|
|
2082
|
+
});
|
|
2083
|
+
} catch (error) {
|
|
2084
|
+
if (error instanceof renderData.PageDataNotFoundError) return void 0;
|
|
2085
|
+
throw error;
|
|
2086
|
+
}
|
|
2087
|
+
return { ...page, data };
|
|
2088
|
+
}
|
|
2089
|
+
async function getEditorBootstrap(pageId) {
|
|
2090
|
+
const home = await pages2.getOrCreateDefaultPage();
|
|
2091
|
+
const page = pageId ? await pages2.getPage(pageId) : home;
|
|
2092
|
+
if (!page) return void 0;
|
|
2093
|
+
return { page, pages: await pages2.listPages(), library: await pages2.listPageDocuments() };
|
|
2094
|
+
}
|
|
2095
|
+
async function getPreviewPage(pageId, context) {
|
|
2096
|
+
const page = await pages2.getPage(pageId);
|
|
2097
|
+
if (!page) return void 0;
|
|
2098
|
+
let data;
|
|
2099
|
+
try {
|
|
2100
|
+
data = await renderData.loadPageData(page.elements, page.dataSources ?? [], {
|
|
2101
|
+
context: { query: {}, params: {}, page: { slug: page.slug }, ...context ?? {} },
|
|
2102
|
+
revalidate: false
|
|
2103
|
+
});
|
|
2104
|
+
} catch (error) {
|
|
2105
|
+
if (error instanceof renderData.PageDataNotFoundError) return void 0;
|
|
2106
|
+
throw error;
|
|
2107
|
+
}
|
|
2108
|
+
return { ...page, data };
|
|
2109
|
+
}
|
|
2110
|
+
async function handle(request) {
|
|
2111
|
+
try {
|
|
2112
|
+
const url = new URL(request.url);
|
|
2113
|
+
const path = url.pathname.startsWith(basePath) ? url.pathname.slice(basePath.length) || "/" : url.pathname;
|
|
2114
|
+
const parts = path.split("/").filter(Boolean);
|
|
2115
|
+
if (request.method === "GET" && path === "/health") {
|
|
2116
|
+
await pool2.query("SELECT 1");
|
|
2117
|
+
return ok({ status: "ok", postgres: true, redis: await redis.ping() === "PONG", model: config.openRouterModel });
|
|
2118
|
+
}
|
|
2119
|
+
if (request.method === "GET" && path === "/assets/manrope-variable.woff2") {
|
|
2120
|
+
const encoded = manrope_data_default.slice(manrope_data_default.indexOf(",") + 1);
|
|
2121
|
+
return new Response(Buffer.from(encoded, "base64"), {
|
|
2122
|
+
headers: {
|
|
2123
|
+
"Content-Type": "font/woff2",
|
|
2124
|
+
"Cache-Control": "public, max-age=31536000, immutable",
|
|
2125
|
+
"Access-Control-Allow-Origin": "*"
|
|
2126
|
+
}
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2129
|
+
if (request.method === "GET" && path === "/bootstrap") {
|
|
2130
|
+
const bootstrap = await getEditorBootstrap(url.searchParams.get("pageId") ?? void 0);
|
|
2131
|
+
return bootstrap ? ok(bootstrap) : fail("Page not found", 404);
|
|
2132
|
+
}
|
|
2133
|
+
if (request.method === "GET" && parts[0] === "published" && parts[1]) {
|
|
2134
|
+
const page = await getPublishedDocument(parts.slice(1).join("/"));
|
|
2135
|
+
if (!page) return fail("Published page not found", 404);
|
|
2136
|
+
return ok(page);
|
|
2137
|
+
}
|
|
2138
|
+
if (request.method === "GET" && parts[0] === "pages" && parts[1]) {
|
|
2139
|
+
const page = await pages2.getPage(parts[1]);
|
|
2140
|
+
return page ? ok(page) : fail("Page not found", 404);
|
|
2141
|
+
}
|
|
2142
|
+
if (request.method === "POST" && path === "/pages") {
|
|
2143
|
+
const body = await bodyOf(request);
|
|
2144
|
+
const name = validation.parseName(body.name);
|
|
2145
|
+
const slug = validation.parseSlug(body.slug ?? body.name);
|
|
2146
|
+
if (!name || !slug) return fail("A valid name and slug are required.");
|
|
2147
|
+
const pageId = await pages2.createPage(name, slug);
|
|
2148
|
+
return pageId ? ok({ status: "ok", pageId }, 201) : fail("Slug already exists", 409);
|
|
2149
|
+
}
|
|
2150
|
+
if (parts[0] === "pages" && parts[1] && request.method === "PATCH" && parts.length === 2) {
|
|
2151
|
+
const body = await bodyOf(request);
|
|
2152
|
+
const name = validation.parseName(body.name);
|
|
2153
|
+
const slug = validation.parseSlug(body.slug);
|
|
2154
|
+
if (!name || !slug) return fail("A valid name and slug are required.");
|
|
2155
|
+
const current = await pages2.getPage(parts[1]);
|
|
2156
|
+
if (current?.slug === "home" && slug !== "home") return fail("The home page path is required and cannot be changed.", 409);
|
|
2157
|
+
const pageId = await pages2.renamePage(parts[1], name, slug);
|
|
2158
|
+
await invalidate();
|
|
2159
|
+
return pageId ? ok({ status: "ok", pageId }) : fail("Page not found", 404);
|
|
2160
|
+
}
|
|
2161
|
+
if (parts[0] === "pages" && parts[1] && request.method === "DELETE") {
|
|
2162
|
+
const result = await pages2.deletePage(parts[1]);
|
|
2163
|
+
await invalidate();
|
|
2164
|
+
return result.deleted ? ok({ status: "ok", pageId: result.fallbackId }) : fail(result.reason === "home-required" ? "The home page is required and cannot be deleted." : "The final page cannot be deleted", 409);
|
|
2165
|
+
}
|
|
2166
|
+
if (parts[0] === "pages" && parts[1] && parts[2] === "save" && request.method === "POST") {
|
|
2167
|
+
const body = await bodyOf(request);
|
|
2168
|
+
const document = body.document ?? {};
|
|
2169
|
+
return ok(await pages2.savePageDocument(parts[1], validation.parseElements(document.elements), validation.parseRootStyle(document.rootStyle), validation.parseDataSources(document.dataSources), Number(body.expectedVersion)));
|
|
2170
|
+
}
|
|
2171
|
+
if (parts[0] === "pages" && parts[1] && parts[2] === "duplicate" && request.method === "POST") {
|
|
2172
|
+
const body = await bodyOf(request);
|
|
2173
|
+
const name = validation.parseName(body.name);
|
|
2174
|
+
const slug = validation.parseSlug(body.slug);
|
|
2175
|
+
if (!name || !slug) return fail("A valid name and slug are required.");
|
|
2176
|
+
const pageId = await pages2.duplicatePage(parts[1], name, slug);
|
|
2177
|
+
return pageId ? ok({ status: "ok", pageId }, 201) : fail("Could not duplicate page", 409);
|
|
2178
|
+
}
|
|
2179
|
+
if (parts[0] === "pages" && parts[1] && parts[2] === "publish" && request.method === "POST") {
|
|
2180
|
+
const published = await pages2.publishPage(parts[1]);
|
|
2181
|
+
await invalidate();
|
|
2182
|
+
return published ? ok({ status: "ok", slug: published.slug }) : fail("Page not found", 404);
|
|
2183
|
+
}
|
|
2184
|
+
if (parts[0] === "pages" && parts[1] && parts[2] === "unpublish" && request.method === "POST") {
|
|
2185
|
+
await pages2.unpublishPage(parts[1]);
|
|
2186
|
+
await invalidate();
|
|
2187
|
+
return ok({ status: "ok" });
|
|
2188
|
+
}
|
|
2189
|
+
if (parts[0] === "templates" && parts[1] && request.method === "POST") {
|
|
2190
|
+
if (!["nocturne", "editorial-blog", "orbit-saas"].includes(parts[1])) return fail("Unknown template", 404);
|
|
2191
|
+
const pageId = await pages2.installTemplatePages(templates.createSiteTemplate(parts[1]));
|
|
2192
|
+
await invalidate();
|
|
2193
|
+
return ok({ status: "ok", pageId });
|
|
2194
|
+
}
|
|
2195
|
+
if (path === "/data/preview" && request.method === "POST") {
|
|
2196
|
+
const body = await bodyOf(request);
|
|
2197
|
+
const [source] = validation.parseDataSources([body.source]);
|
|
2198
|
+
if (!source) return fail("Invalid source.");
|
|
2199
|
+
const query = Object.fromEntries(new URLSearchParams(typeof body.sampleQuery === "string" ? body.sampleQuery.slice(0, 500) : ""));
|
|
2200
|
+
const result = await sourceRuntime.loadSource(source, { context: { query, params: {}, page: { slug: "preview" } }, revalidate: false });
|
|
2201
|
+
return ok({ status: "ok", ...result, total: result.rows.length });
|
|
2202
|
+
}
|
|
2203
|
+
if (path === "/ai" && request.method === "POST") {
|
|
2204
|
+
const ip = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "local";
|
|
2205
|
+
const rateKey = `pagiera:ai-rate:${ip}:${Math.floor(Date.now() / 6e4)}`;
|
|
2206
|
+
const count = await redis.incr(rateKey);
|
|
2207
|
+
if (count === 1) await redis.expire(rateKey, 70);
|
|
2208
|
+
if (count > (config.aiRateLimitPerMinute ?? 10)) return fail("AI rate limit exceeded.", 429);
|
|
2209
|
+
return ai.POST(request);
|
|
2210
|
+
}
|
|
2211
|
+
return fail("Pagiera API route not found", 404);
|
|
2212
|
+
} catch (error) {
|
|
2213
|
+
console.error("Pagiera API error", error);
|
|
2214
|
+
return fail(error, 500);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
return { handle, getEditorBootstrap, getPreviewPage, getPublishedDocument, getPublishedPage: getPublishedPage2, close: async () => {
|
|
2218
|
+
await redis.quit();
|
|
2219
|
+
await pool2.end();
|
|
2220
|
+
}, redis, pool: pool2 };
|
|
2221
|
+
}
|
|
2222
|
+
function pagieraConfigFromEnv(env = process.env) {
|
|
2223
|
+
return {
|
|
2224
|
+
postgresUrl: env.PAGIERA_POSTGRES_URL ?? env.DATABASE_URL ?? "",
|
|
2225
|
+
redisUrl: env.PAGIERA_REDIS_URL ?? env.REDIS_URL ?? "",
|
|
2226
|
+
openRouterApiKey: env.OPENROUTER_API_KEY ?? "",
|
|
2227
|
+
openRouterModel: env.OPENROUTER_MODEL ?? ""
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
function createPagieraRouteHandlers(config) {
|
|
2231
|
+
let server;
|
|
2232
|
+
const handle = async (request) => {
|
|
2233
|
+
try {
|
|
2234
|
+
return await (await (server ??= getPagieraServer(config))).handle(request);
|
|
2235
|
+
} catch (error) {
|
|
2236
|
+
server = void 0;
|
|
2237
|
+
return Response.json(
|
|
2238
|
+
{ error: error instanceof Error ? error.message : "Pagiera server could not start." },
|
|
2239
|
+
{ status: 503 }
|
|
2240
|
+
);
|
|
2241
|
+
}
|
|
2242
|
+
};
|
|
2243
|
+
return { GET: handle, POST: handle, PUT: handle, PATCH: handle, DELETE: handle };
|
|
2244
|
+
}
|
|
2245
|
+
var globalServers = globalThis;
|
|
2246
|
+
var SERVER_RUNTIME_REVISION = 4;
|
|
2247
|
+
function getPagieraServer(config) {
|
|
2248
|
+
const key = `${SERVER_RUNTIME_REVISION}
|
|
2249
|
+
${config.postgresUrl}
|
|
2250
|
+
${config.redisUrl}
|
|
2251
|
+
${config.openRouterModel}`;
|
|
2252
|
+
const servers = globalServers.__pagieraServers ??= /* @__PURE__ */ new Map();
|
|
2253
|
+
let server = servers.get(key);
|
|
2254
|
+
if (!server) {
|
|
2255
|
+
server = createPagieraServer(config);
|
|
2256
|
+
servers.set(key, server);
|
|
2257
|
+
server.catch(() => servers.delete(key));
|
|
2258
|
+
}
|
|
2259
|
+
return server;
|
|
2260
|
+
}
|
|
2261
|
+
export {
|
|
2262
|
+
createPagieraRouteHandlers,
|
|
2263
|
+
createPagieraServer,
|
|
2264
|
+
getPagieraServer,
|
|
2265
|
+
pagieraConfigFromEnv
|
|
2266
|
+
};
|
|
2267
|
+
//# sourceMappingURL=server.js.map
|