@ampless/admin 0.2.0-alpha.6 → 0.2.0-alpha.8

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.
@@ -4,7 +4,7 @@ import {
4
4
  publicMediaUrl,
5
5
  setAdminMediaContext,
6
6
  translate
7
- } from "./chunk-FI7CM4LH.js";
7
+ } from "./chunk-KKM2MCM4.js";
8
8
  import {
9
9
  invalidateSiteSettingsCache
10
10
  } from "./chunk-VXEVLHGL.js";
@@ -82,6 +82,18 @@ function decodeBody(value) {
82
82
  return value;
83
83
  }
84
84
  }
85
+ function decodeMetadata(value) {
86
+ if (value === null || value === void 0) return void 0;
87
+ const parsed = typeof value === "string" ? safeJsonParse(value) : value;
88
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
89
+ }
90
+ function safeJsonParse(value) {
91
+ try {
92
+ return JSON.parse(value);
93
+ } catch {
94
+ return value;
95
+ }
96
+ }
85
97
  function toCorePost(p) {
86
98
  return {
87
99
  postId: p.postId,
@@ -93,7 +105,8 @@ function toCorePost(p) {
93
105
  body: decodeBody(p.body),
94
106
  status: p.status ?? "draft",
95
107
  publishedAt: p.publishedAt ?? void 0,
96
- tags: (p.tags ?? []).filter((t) => typeof t === "string")
108
+ tags: (p.tags ?? []).filter((t) => typeof t === "string"),
109
+ metadata: decodeMetadata(p.metadata)
97
110
  };
98
111
  }
99
112
  function postTagEntries(post) {
@@ -190,6 +203,7 @@ function installAdminPostsProvider() {
190
203
  status: input.status,
191
204
  publishedAt: input.publishedAt,
192
205
  tags: input.tags,
206
+ ...input.metadata !== void 0 && { metadata: encodeBody(input.metadata) },
193
207
  // Denormalized GSI keys. Must match every change to slug /
194
208
  // status — see the update() branch below.
195
209
  siteIdStatus: composeSiteIdStatus(input.siteId, input.status),
@@ -216,6 +230,7 @@ function installAdminPostsProvider() {
216
230
  ...patch.status !== void 0 && { status: patch.status },
217
231
  ...patch.publishedAt !== void 0 && { publishedAt: patch.publishedAt },
218
232
  ...patch.tags !== void 0 && { tags: patch.tags },
233
+ ...patch.metadata !== void 0 && { metadata: encodeBody(patch.metadata) },
219
234
  ...patch.status !== void 0 && nextStatus && { siteIdStatus: composeSiteIdStatus(siteId, nextStatus) },
220
235
  ...patch.slug !== void 0 && nextSlug && { siteIdSlug: composeSiteIdSlug(siteId, nextSlug) }
221
236
  });
@@ -351,12 +366,12 @@ function AdminDashboard() {
351
366
  }, []);
352
367
  const published = posts.filter((p) => p.status === "published").length;
353
368
  const drafts = posts.filter((p) => p.status === "draft").length;
354
- return /* @__PURE__ */ jsxs("div", { className: "p-8", children: [
355
- /* @__PURE__ */ jsxs("div", { className: "mb-8 flex items-center justify-between", children: [
356
- /* @__PURE__ */ jsx3("h1", { className: "text-3xl font-bold", children: t("dashboard.title") }),
369
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
370
+ /* @__PURE__ */ jsxs("div", { className: "mb-6 flex flex-wrap items-center justify-between gap-3 md:mb-8", children: [
371
+ /* @__PURE__ */ jsx3("h1", { className: "text-2xl font-bold md:text-3xl", children: t("dashboard.title") }),
357
372
  /* @__PURE__ */ jsx3(Button, { asChild: true, children: /* @__PURE__ */ jsx3(Link, { href: "/admin/posts/new", children: t("dashboard.newPost") }) })
358
373
  ] }),
359
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-3", children: [
374
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3", children: [
360
375
  /* @__PURE__ */ jsxs(Card, { children: [
361
376
  /* @__PURE__ */ jsx3(CardHeader, { children: /* @__PURE__ */ jsx3(CardTitle, { children: t("dashboard.totalPosts") }) }),
362
377
  /* @__PURE__ */ jsxs(CardContent, { children: [
@@ -398,15 +413,15 @@ function PostsList() {
398
413
  const siteId = readAdminSiteIdFromCookie();
399
414
  listPosts2({ status: "all", siteId }).then(setPosts).finally(() => setLoading(false));
400
415
  }, []);
401
- return /* @__PURE__ */ jsxs2("div", { className: "p-8", children: [
402
- /* @__PURE__ */ jsxs2("div", { className: "mb-8 flex items-center justify-between", children: [
403
- /* @__PURE__ */ jsx4("h1", { className: "text-3xl font-bold", children: t("posts.list.title") }),
416
+ return /* @__PURE__ */ jsxs2("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
417
+ /* @__PURE__ */ jsxs2("div", { className: "mb-6 flex flex-wrap items-center justify-between gap-3 md:mb-8", children: [
418
+ /* @__PURE__ */ jsx4("h1", { className: "text-2xl font-bold md:text-3xl", children: t("posts.list.title") }),
404
419
  /* @__PURE__ */ jsx4(Button2, { asChild: true, children: /* @__PURE__ */ jsx4(Link2, { href: "/admin/posts/new", children: t("posts.list.newButton") }) })
405
420
  ] }),
406
421
  loading ? /* @__PURE__ */ jsx4("p", { className: "text-muted-foreground", children: t("common.loading") }) : posts.length === 0 ? /* @__PURE__ */ jsxs2("div", { className: "rounded-md border p-12 text-center", children: [
407
422
  /* @__PURE__ */ jsx4("p", { className: "text-muted-foreground", children: t("posts.list.empty") }),
408
423
  /* @__PURE__ */ jsx4(Button2, { asChild: true, className: "mt-4", children: /* @__PURE__ */ jsx4(Link2, { href: "/admin/posts/new", children: t("posts.list.createFirst") }) })
409
- ] }) : /* @__PURE__ */ jsx4("div", { className: "rounded-md border", children: /* @__PURE__ */ jsxs2(Table, { children: [
424
+ ] }) : /* @__PURE__ */ jsx4("div", { className: "overflow-x-auto rounded-md border", children: /* @__PURE__ */ jsxs2(Table, { children: [
410
425
  /* @__PURE__ */ jsx4(TableHeader, { children: /* @__PURE__ */ jsxs2(TableRow, { children: [
411
426
  /* @__PURE__ */ jsx4(TableHead, { children: t("posts.list.columnTitle") }),
412
427
  /* @__PURE__ */ jsx4(TableHead, { children: t("posts.list.columnStatus") }),
@@ -916,7 +931,7 @@ function MediaPicker({ trigger, onSelect }) {
916
931
  }
917
932
 
918
933
  // src/components/post-form.tsx
919
- import { useRef as useRef3, useState as useState5 } from "react";
934
+ import { useRef as useRef3, useState as useState6 } from "react";
920
935
  import { useRouter } from "next/navigation";
921
936
  import { Image as ImageIcon3 } from "lucide-react";
922
937
  import {
@@ -932,7 +947,7 @@ import {
932
947
  markdownToHtml,
933
948
  htmlToMarkdown
934
949
  } from "@ampless/runtime";
935
- import { Button as Button7, Input as Input2, Label as Label2, Textarea } from "@ampless/runtime/ui";
950
+ import { Button as Button8, Input as Input2, Label as Label2, Textarea } from "@ampless/runtime/ui";
936
951
 
937
952
  // src/editor/tiptap-editor.tsx
938
953
  import { useEditor, EditorContent } from "@tiptap/react";
@@ -1029,7 +1044,7 @@ function ImageBubbleMenu({ editor }) {
1029
1044
  if (alt === null) return;
1030
1045
  editor.chain().focus().updateAttributes("image", { alt }).run();
1031
1046
  };
1032
- const remove2 = () => {
1047
+ const remove3 = () => {
1033
1048
  editor.chain().focus().deleteSelection().run();
1034
1049
  };
1035
1050
  const setDisplay = (display) => {
@@ -1080,7 +1095,7 @@ function ImageBubbleMenu({ editor }) {
1080
1095
  /* @__PURE__ */ jsx8(Pencil, { className: "mr-1 h-3 w-3" }),
1081
1096
  t("editor.image.alt")
1082
1097
  ] }),
1083
- /* @__PURE__ */ jsxs6(Button6, { type: "button", variant: "ghost", size: "sm", onClick: remove2, children: [
1098
+ /* @__PURE__ */ jsxs6(Button6, { type: "button", variant: "ghost", size: "sm", onClick: remove3, children: [
1084
1099
  /* @__PURE__ */ jsx8(Trash2, { className: "mr-1 h-3 w-3" }),
1085
1100
  t("editor.image.delete")
1086
1101
  ] })
@@ -1134,8 +1149,397 @@ function TiptapEditor({ initialContent, onChange }) {
1134
1149
  ] });
1135
1150
  }
1136
1151
 
1137
- // src/components/post-form.tsx
1152
+ // src/components/static-uploader.tsx
1153
+ import { useState as useState5 } from "react";
1154
+ import { FileText, AlertTriangle, FileArchive, X } from "lucide-react";
1155
+ import { Button as Button7 } from "@ampless/runtime/ui";
1156
+
1157
+ // src/lib/static-bundle.ts
1158
+ import JSZip from "jszip";
1159
+ import { uploadData as uploadData2, list as list2, remove } from "aws-amplify/storage";
1160
+ var DEFAULT_ENTRYPOINT = "index.html";
1161
+ var MAX_BUNDLE_BYTES = 50 * 1024 * 1024;
1162
+ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm", ".css", ".svg"]);
1163
+ var MIME_TYPES = {
1164
+ ".html": "text/html; charset=utf-8",
1165
+ ".htm": "text/html; charset=utf-8",
1166
+ ".css": "text/css; charset=utf-8",
1167
+ ".js": "application/javascript; charset=utf-8",
1168
+ ".mjs": "application/javascript; charset=utf-8",
1169
+ ".json": "application/json; charset=utf-8",
1170
+ ".svg": "image/svg+xml",
1171
+ ".png": "image/png",
1172
+ ".jpg": "image/jpeg",
1173
+ ".jpeg": "image/jpeg",
1174
+ ".gif": "image/gif",
1175
+ ".webp": "image/webp",
1176
+ ".avif": "image/avif",
1177
+ ".ico": "image/x-icon",
1178
+ ".woff": "font/woff",
1179
+ ".woff2": "font/woff2",
1180
+ ".ttf": "font/ttf",
1181
+ ".otf": "font/otf",
1182
+ ".eot": "application/vnd.ms-fontobject",
1183
+ ".txt": "text/plain; charset=utf-8",
1184
+ ".xml": "application/xml; charset=utf-8",
1185
+ ".map": "application/json; charset=utf-8",
1186
+ ".pdf": "application/pdf"
1187
+ };
1188
+ function mimeTypeFor(path) {
1189
+ const lower = path.toLowerCase();
1190
+ const dot = lower.lastIndexOf(".");
1191
+ if (dot < 0) return "application/octet-stream";
1192
+ return MIME_TYPES[lower.slice(dot)] ?? "application/octet-stream";
1193
+ }
1194
+ function validateBundlePath(path) {
1195
+ if (path === "" || path.endsWith("/")) return "directory entry";
1196
+ if (path.includes("\0")) return "contains null byte";
1197
+ if (path.startsWith("/") || path.startsWith("\\")) return "absolute path";
1198
+ if (path.split(/[/\\]/).some((seg) => seg === "..")) return "parent-directory traversal";
1199
+ if (path.startsWith("__MACOSX/") || /(^|\/)\._/.test(path)) return "macOS resource fork";
1200
+ if (/(^|\/)\.DS_Store$/.test(path)) return ".DS_Store junk";
1201
+ if (/(^|\/)Thumbs\.db$/i.test(path)) return "Thumbs.db junk";
1202
+ return null;
1203
+ }
1204
+ var HTML_URL_ATTR_RE = /\b(?:href|src|action|data|poster|cite|formaction|manifest|srcset)\s*=\s*["']([^"']+)["']/gi;
1205
+ var CSS_URL_RE = /url\(\s*["']?([^"')\s]+)["']?\s*\)|@import\s+["']([^"']+)["']/gi;
1206
+ function findAbsolutePathRefs(path, content) {
1207
+ const ext = path.toLowerCase().slice(path.lastIndexOf("."));
1208
+ if (!TEXT_EXTENSIONS.has(ext)) return [];
1209
+ const issues = [];
1210
+ function check(url, lineHint) {
1211
+ const trimmed = url.trim();
1212
+ if (!trimmed) return;
1213
+ if (trimmed.startsWith("#")) return;
1214
+ if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed)) return;
1215
+ if (trimmed.startsWith("//")) {
1216
+ issues.push({ path, reason: `protocol-relative URL: ${lineHint}` });
1217
+ return;
1218
+ }
1219
+ if (trimmed.startsWith("/")) {
1220
+ issues.push({ path, reason: `absolute path: ${lineHint}` });
1221
+ return;
1222
+ }
1223
+ }
1224
+ if (ext === ".html" || ext === ".htm" || ext === ".svg") {
1225
+ HTML_URL_ATTR_RE.lastIndex = 0;
1226
+ let m;
1227
+ while ((m = HTML_URL_ATTR_RE.exec(content)) !== null) {
1228
+ const val = m[1] ?? "";
1229
+ if (/\bsrcset\s*=/i.test(m[0])) {
1230
+ for (const candidate of val.split(",")) {
1231
+ const urlPart = candidate.trim().split(/\s+/)[0];
1232
+ if (urlPart) check(urlPart, candidate.trim());
1233
+ }
1234
+ } else {
1235
+ check(val, m[0]);
1236
+ }
1237
+ }
1238
+ }
1239
+ if (ext === ".css" || ext === ".svg") {
1240
+ CSS_URL_RE.lastIndex = 0;
1241
+ let m;
1242
+ while ((m = CSS_URL_RE.exec(content)) !== null) {
1243
+ const val = (m[1] ?? m[2] ?? "").trim();
1244
+ check(val, m[0]);
1245
+ }
1246
+ }
1247
+ return issues;
1248
+ }
1249
+ async function extractZip(file) {
1250
+ const zip = await JSZip.loadAsync(file);
1251
+ const files = [];
1252
+ const issues = [];
1253
+ let totalBytes = 0;
1254
+ for (const entry of Object.values(zip.files)) {
1255
+ if (entry.dir) continue;
1256
+ const reason = validateBundlePath(entry.name);
1257
+ if (reason) {
1258
+ const silent = reason === "macOS resource fork" || reason === ".DS_Store junk" || reason === "Thumbs.db junk";
1259
+ if (!silent) issues.push({ path: entry.name, reason });
1260
+ continue;
1261
+ }
1262
+ const data = await entry.async("uint8array");
1263
+ totalBytes += data.byteLength;
1264
+ files.push({ path: entry.name, data });
1265
+ }
1266
+ return { files: stripCommonPrefix(files), issues, totalBytes };
1267
+ }
1268
+ function stripCommonPrefix(files) {
1269
+ if (files.length === 0) return files;
1270
+ const firstSlash = files[0].path.indexOf("/");
1271
+ if (firstSlash < 0) return files;
1272
+ const prefix = files[0].path.slice(0, firstSlash + 1);
1273
+ if (!files.every((f) => f.path.startsWith(prefix))) return files;
1274
+ return files.map((f) => ({ ...f, path: f.path.slice(prefix.length) }));
1275
+ }
1276
+ function validateBundle(files) {
1277
+ const issues = [];
1278
+ const decoder = new TextDecoder("utf-8", { fatal: false });
1279
+ for (const f of files) {
1280
+ const ext = f.path.toLowerCase().slice(f.path.lastIndexOf("."));
1281
+ if (!TEXT_EXTENSIONS.has(ext)) continue;
1282
+ const text = decoder.decode(f.data);
1283
+ issues.push(...findAbsolutePathRefs(f.path, text));
1284
+ }
1285
+ return issues;
1286
+ }
1287
+ function bundlePrefix(siteId, slug) {
1288
+ return `public/static/${siteId}/${slug}/`;
1289
+ }
1290
+ async function uploadBundle(opts) {
1291
+ if (opts.files.length === 0) {
1292
+ throw new Error("Bundle is empty.");
1293
+ }
1294
+ const totalBytes = opts.files.reduce((sum, f) => sum + f.data.byteLength, 0);
1295
+ if (totalBytes > MAX_BUNDLE_BYTES) {
1296
+ throw new Error(
1297
+ `Bundle too large: ${Math.round(totalBytes / 1024 / 1024)} MB exceeds the ${Math.round(MAX_BUNDLE_BYTES / 1024 / 1024)} MB ceiling for browser-side upload.`
1298
+ );
1299
+ }
1300
+ const entrypoint = opts.entrypoint ?? pickDefaultEntrypoint(opts.files);
1301
+ if (!opts.files.some((f) => f.path === entrypoint)) {
1302
+ throw new Error(`Entrypoint "${entrypoint}" is not present in the bundle.`);
1303
+ }
1304
+ await deleteBundle(opts.siteId, opts.slug).catch(() => void 0);
1305
+ const prefix = bundlePrefix(opts.siteId, opts.slug);
1306
+ let uploaded = 0;
1307
+ for (const f of opts.files) {
1308
+ const task = uploadData2({
1309
+ path: `${prefix}${f.path}`,
1310
+ data: f.data,
1311
+ // Forcing Content-Type at upload means CloudFront / browsers see
1312
+ // it directly when serving the file via the public bucket URL.
1313
+ // (The runtime route handler overrides it for the proxied path,
1314
+ // but tooling that hits S3 directly benefits from a correct CT.)
1315
+ options: { contentType: mimeTypeFor(f.path) }
1316
+ });
1317
+ await task.result;
1318
+ uploaded += f.data.byteLength;
1319
+ opts.onProgress?.(uploaded, totalBytes);
1320
+ }
1321
+ return {
1322
+ entrypoint,
1323
+ files: opts.files.map((f) => f.path).sort(),
1324
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
1325
+ };
1326
+ }
1327
+ async function deleteBundle(siteId, slug) {
1328
+ const prefix = bundlePrefix(siteId, slug);
1329
+ const result = await list2({ path: prefix });
1330
+ for (const item of result.items) {
1331
+ await remove({ path: item.path });
1332
+ }
1333
+ }
1334
+ function pickDefaultEntrypoint(files) {
1335
+ const exact = files.find((f) => f.path === DEFAULT_ENTRYPOINT);
1336
+ if (exact) return exact.path;
1337
+ const altRoot = files.find((f) => f.path === "index.htm");
1338
+ if (altRoot) return altRoot.path;
1339
+ const htmlRoot = files.filter((f) => /^[^/]+\.html?$/.test(f.path)).sort((a, b) => a.path.localeCompare(b.path));
1340
+ if (htmlRoot.length > 0) return htmlRoot[0].path;
1341
+ const htmlAny = files.filter((f) => /\.html?$/.test(f.path)).sort((a, b) => a.path.localeCompare(b.path));
1342
+ if (htmlAny.length > 0) return htmlAny[0].path;
1343
+ return DEFAULT_ENTRYPOINT;
1344
+ }
1345
+
1346
+ // src/components/static-uploader.tsx
1138
1347
  import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1348
+ function StaticUploader({ initial, onFilesReady, onClear }) {
1349
+ const t = useT();
1350
+ const [pending, setPending] = useState5(null);
1351
+ const [issues, setIssues] = useState5([]);
1352
+ const [busy, setBusy] = useState5(false);
1353
+ const [error, setError] = useState5(null);
1354
+ async function handleZip(file) {
1355
+ setBusy(true);
1356
+ setError(null);
1357
+ try {
1358
+ const { files, issues: structuralIssues } = await extractZip(file);
1359
+ if (files.length === 0) {
1360
+ setError(t("posts.form.static.emptyBundle"));
1361
+ return;
1362
+ }
1363
+ const contentIssues = validateBundle(files);
1364
+ const all = [...structuralIssues, ...contentIssues];
1365
+ setPending(files);
1366
+ setIssues(all);
1367
+ if (all.length === 0) {
1368
+ onFilesReady(files, guessEntrypoint(files));
1369
+ }
1370
+ } catch (e) {
1371
+ setError(e instanceof Error ? e.message : String(e));
1372
+ } finally {
1373
+ setBusy(false);
1374
+ }
1375
+ }
1376
+ async function handleLooseFiles(files) {
1377
+ setBusy(true);
1378
+ setError(null);
1379
+ try {
1380
+ const extracted = [];
1381
+ const structural = [];
1382
+ for (const f of Array.from(files)) {
1383
+ const rel = f.webkitRelativePath ?? f.name;
1384
+ const stripped = rel.includes("/") ? rel.slice(rel.indexOf("/") + 1) : rel;
1385
+ const reason = validateBundlePath(stripped);
1386
+ if (reason) {
1387
+ structural.push({ path: stripped, reason });
1388
+ continue;
1389
+ }
1390
+ const buf = new Uint8Array(await f.arrayBuffer());
1391
+ extracted.push({ path: stripped, data: buf });
1392
+ }
1393
+ if (extracted.length === 0) {
1394
+ setError(t("posts.form.static.emptyBundle"));
1395
+ return;
1396
+ }
1397
+ const content = validateBundle(extracted);
1398
+ const all = [...structural, ...content];
1399
+ setPending(extracted);
1400
+ setIssues(all);
1401
+ if (all.length === 0) {
1402
+ onFilesReady(extracted, guessEntrypoint(extracted));
1403
+ }
1404
+ } catch (e) {
1405
+ setError(e instanceof Error ? e.message : String(e));
1406
+ } finally {
1407
+ setBusy(false);
1408
+ }
1409
+ }
1410
+ function onPickerChange(e) {
1411
+ const fl = e.target.files;
1412
+ if (!fl || fl.length === 0) return;
1413
+ e.target.value = "";
1414
+ if (fl.length === 1 && fl[0].name.toLowerCase().endsWith(".zip")) {
1415
+ void handleZip(fl[0]);
1416
+ } else {
1417
+ void handleLooseFiles(fl);
1418
+ }
1419
+ }
1420
+ function clearPending() {
1421
+ setPending(null);
1422
+ setIssues([]);
1423
+ setError(null);
1424
+ onClear();
1425
+ }
1426
+ const showCurrent = !pending && initial && initial.files.length > 0;
1427
+ return /* @__PURE__ */ jsxs8("div", { className: "space-y-4", children: [
1428
+ /* @__PURE__ */ jsxs8("div", { className: "rounded-md border border-dashed p-4", children: [
1429
+ /* @__PURE__ */ jsxs8("label", { className: "flex flex-col items-start gap-2 text-sm", children: [
1430
+ /* @__PURE__ */ jsx10("span", { className: "font-medium", children: t("posts.form.static.pick") }),
1431
+ /* @__PURE__ */ jsx10(
1432
+ "input",
1433
+ {
1434
+ type: "file",
1435
+ accept: ".zip,application/zip,*/*",
1436
+ multiple: true,
1437
+ onChange: onPickerChange,
1438
+ disabled: busy
1439
+ }
1440
+ ),
1441
+ /* @__PURE__ */ jsx10("span", { className: "text-xs text-muted-foreground", children: t("posts.form.static.pickHint") })
1442
+ ] }),
1443
+ busy && /* @__PURE__ */ jsx10("p", { className: "mt-2 text-sm text-muted-foreground", children: t("common.loading") }),
1444
+ error && /* @__PURE__ */ jsx10("p", { className: "mt-2 text-sm text-destructive", children: error })
1445
+ ] }),
1446
+ showCurrent && /* @__PURE__ */ jsx10(CurrentBundle, { body: initial }),
1447
+ pending && /* @__PURE__ */ jsx10(
1448
+ PendingBundle,
1449
+ {
1450
+ files: pending,
1451
+ issues,
1452
+ onClear: clearPending
1453
+ }
1454
+ )
1455
+ ] });
1456
+ }
1457
+ function CurrentBundle({ body }) {
1458
+ const t = useT();
1459
+ return /* @__PURE__ */ jsxs8("div", { className: "rounded-md border bg-muted/30 p-3", children: [
1460
+ /* @__PURE__ */ jsxs8("div", { className: "mb-2 flex items-center gap-2 text-sm font-medium", children: [
1461
+ /* @__PURE__ */ jsx10(FileArchive, { className: "h-4 w-4" }),
1462
+ t("posts.form.static.currentBundle", {
1463
+ count: body.files.length,
1464
+ entrypoint: body.entrypoint
1465
+ })
1466
+ ] }),
1467
+ /* @__PURE__ */ jsx10(FileList, { files: body.files })
1468
+ ] });
1469
+ }
1470
+ function PendingBundle({
1471
+ files,
1472
+ issues,
1473
+ onClear
1474
+ }) {
1475
+ const t = useT();
1476
+ const totalBytes = files.reduce((sum, f) => sum + f.data.byteLength, 0);
1477
+ return /* @__PURE__ */ jsxs8("div", { className: "rounded-md border p-3", children: [
1478
+ /* @__PURE__ */ jsxs8("div", { className: "mb-2 flex items-center justify-between gap-2 text-sm font-medium", children: [
1479
+ /* @__PURE__ */ jsxs8("span", { className: "flex items-center gap-2", children: [
1480
+ /* @__PURE__ */ jsx10(FileArchive, { className: "h-4 w-4" }),
1481
+ t("posts.form.static.pendingBundle", {
1482
+ count: files.length,
1483
+ size: formatBytes2(totalBytes)
1484
+ })
1485
+ ] }),
1486
+ /* @__PURE__ */ jsxs8(Button7, { type: "button", variant: "ghost", size: "sm", onClick: onClear, children: [
1487
+ /* @__PURE__ */ jsx10(X, { className: "mr-1 h-3 w-3" }),
1488
+ t("common.cancel")
1489
+ ] })
1490
+ ] }),
1491
+ issues.length > 0 && /* @__PURE__ */ jsxs8("div", { className: "mb-3 rounded-md border border-destructive/40 bg-destructive/5 p-2 text-sm", children: [
1492
+ /* @__PURE__ */ jsxs8("div", { className: "mb-1 flex items-center gap-2 font-medium text-destructive", children: [
1493
+ /* @__PURE__ */ jsx10(AlertTriangle, { className: "h-4 w-4" }),
1494
+ t("posts.form.static.issuesTitle", { count: issues.length })
1495
+ ] }),
1496
+ /* @__PURE__ */ jsxs8("ul", { className: "space-y-0.5 text-xs", children: [
1497
+ issues.slice(0, 20).map((issue, idx) => /* @__PURE__ */ jsxs8("li", { className: "font-mono", children: [
1498
+ issue.path,
1499
+ ": ",
1500
+ issue.reason
1501
+ ] }, `${issue.path}-${idx}`)),
1502
+ issues.length > 20 && /* @__PURE__ */ jsxs8("li", { className: "font-mono text-muted-foreground", children: [
1503
+ "\u2026 ",
1504
+ issues.length - 20,
1505
+ " more"
1506
+ ] })
1507
+ ] }),
1508
+ /* @__PURE__ */ jsx10("p", { className: "mt-2 text-xs text-muted-foreground", children: t("posts.form.static.issuesHint") })
1509
+ ] }),
1510
+ /* @__PURE__ */ jsx10(FileList, { files: files.map((f) => f.path) })
1511
+ ] });
1512
+ }
1513
+ function FileList({ files }) {
1514
+ return /* @__PURE__ */ jsxs8("ul", { className: "space-y-0.5 text-xs", children: [
1515
+ files.slice(0, 40).map((path) => /* @__PURE__ */ jsxs8("li", { className: "flex items-center gap-1.5 font-mono text-muted-foreground", children: [
1516
+ /* @__PURE__ */ jsx10(FileText, { className: "h-3 w-3 shrink-0" }),
1517
+ path
1518
+ ] }, path)),
1519
+ files.length > 40 && /* @__PURE__ */ jsxs8("li", { className: "font-mono text-xs text-muted-foreground", children: [
1520
+ "\u2026 ",
1521
+ files.length - 40,
1522
+ " more"
1523
+ ] })
1524
+ ] });
1525
+ }
1526
+ function formatBytes2(bytes) {
1527
+ if (bytes < 1024) return `${bytes} B`;
1528
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1529
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
1530
+ }
1531
+ function guessEntrypoint(files) {
1532
+ const exact = files.find((f) => f.path === "index.html");
1533
+ if (exact) return "index.html";
1534
+ const alt = files.find((f) => f.path === "index.htm");
1535
+ if (alt) return "index.htm";
1536
+ const rootHtml = files.filter((f) => /^[^/]+\.html?$/.test(f.path)).sort((a, b) => a.path.localeCompare(b.path))[0];
1537
+ if (rootHtml) return rootHtml.path;
1538
+ return files[0]?.path ?? "index.html";
1539
+ }
1540
+
1541
+ // src/components/post-form.tsx
1542
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1139
1543
  var EMPTY_TIPTAP_DOC = { type: "doc", content: [{ type: "paragraph" }] };
1140
1544
  var IMAGE_URL_RE = /\.(jpe?g|png|gif|webp|avif|svg|bmp|tiff?)(\?|$)/i;
1141
1545
  var STYLESHEET_URL_RE = /\.css(\?|$)/i;
@@ -1155,23 +1559,36 @@ function slugify(s) {
1155
1559
  }
1156
1560
  function defaultBodyForFormat(format) {
1157
1561
  if (format === "tiptap") return EMPTY_TIPTAP_DOC;
1562
+ if (format === "static") return null;
1158
1563
  return "";
1159
1564
  }
1565
+ function isStaticBody(value) {
1566
+ return !!value && typeof value === "object" && "entrypoint" in value && "files" in value && Array.isArray(value.files);
1567
+ }
1160
1568
  function PostForm({ post }) {
1161
1569
  const router = useRouter();
1162
1570
  const t = useT();
1163
1571
  const isEdit = !!post;
1164
1572
  const bodyTextareaRef = useRef3(null);
1165
- const [title, setTitle] = useState5(post?.title ?? "");
1166
- const [slug, setSlug] = useState5(post?.slug ?? "");
1167
- const [excerpt, setExcerpt] = useState5(post?.excerpt ?? "");
1168
- const [format, setFormat] = useState5(post?.format ?? "tiptap");
1169
- const [body, setBody] = useState5(post?.body ?? EMPTY_TIPTAP_DOC);
1170
- const [status, setStatus] = useState5(post?.status ?? "draft");
1171
- const [tagsInput, setTagsInput] = useState5((post?.tags ?? []).join(", "));
1172
- const [saving, setSaving] = useState5(false);
1173
- const [error, setError] = useState5(null);
1174
- const [view, setView] = useState5("edit");
1573
+ const [title, setTitle] = useState6(post?.title ?? "");
1574
+ const [slug, setSlug] = useState6(post?.slug ?? "");
1575
+ const [excerpt, setExcerpt] = useState6(post?.excerpt ?? "");
1576
+ const [format, setFormat] = useState6(post?.format ?? "tiptap");
1577
+ const [body, setBody] = useState6(post?.body ?? EMPTY_TIPTAP_DOC);
1578
+ const [status, setStatus] = useState6(post?.status ?? "draft");
1579
+ const [tagsInput, setTagsInput] = useState6((post?.tags ?? []).join(", "));
1580
+ const [noLayout, setNoLayout] = useState6(post?.metadata?.no_layout === true);
1581
+ const [saving, setSaving] = useState6(false);
1582
+ const [error, setError] = useState6(null);
1583
+ const [view, setView] = useState6("edit");
1584
+ const [pendingBundle, setPendingBundle] = useState6(null);
1585
+ const initialStaticBody = isStaticBody(post?.body) ? post.body : null;
1586
+ function buildMetadata() {
1587
+ const next = { ...post?.metadata ?? {} };
1588
+ if (noLayout && format === "html") next.no_layout = true;
1589
+ else delete next.no_layout;
1590
+ return Object.keys(next).length > 0 ? next : void 0;
1591
+ }
1175
1592
  function parseTags(raw) {
1176
1593
  return Array.from(
1177
1594
  new Set(
@@ -1203,31 +1620,37 @@ function PostForm({ post }) {
1203
1620
  function changeFormat(next) {
1204
1621
  if (next === format) return;
1205
1622
  let nextBody = body;
1206
- const k = `${format}\u2192${next}`;
1207
- switch (k) {
1208
- case "tiptap\u2192html":
1209
- nextBody = tiptapToHtml(body);
1210
- break;
1211
- case "tiptap\u2192markdown":
1212
- nextBody = tiptapToMarkdown(body);
1213
- break;
1214
- case "html\u2192tiptap":
1215
- nextBody = String(body ?? "");
1216
- break;
1217
- case "markdown\u2192tiptap":
1218
- nextBody = markdownToHtml(String(body ?? ""));
1219
- break;
1220
- case "html\u2192markdown":
1221
- nextBody = htmlToMarkdown(String(body ?? ""));
1222
- break;
1223
- case "markdown\u2192html":
1224
- nextBody = markdownToHtml(String(body ?? ""));
1225
- break;
1226
- default:
1227
- nextBody = defaultBodyForFormat(next);
1623
+ if (next === "static" || format === "static") {
1624
+ nextBody = defaultBodyForFormat(next);
1625
+ } else {
1626
+ const k = `${format}\u2192${next}`;
1627
+ switch (k) {
1628
+ case "tiptap\u2192html":
1629
+ nextBody = tiptapToHtml(body);
1630
+ break;
1631
+ case "tiptap\u2192markdown":
1632
+ nextBody = tiptapToMarkdown(body);
1633
+ break;
1634
+ case "html\u2192tiptap":
1635
+ nextBody = String(body ?? "");
1636
+ break;
1637
+ case "markdown\u2192tiptap":
1638
+ nextBody = markdownToHtml(String(body ?? ""));
1639
+ break;
1640
+ case "html\u2192markdown":
1641
+ nextBody = htmlToMarkdown(String(body ?? ""));
1642
+ break;
1643
+ case "markdown\u2192html":
1644
+ nextBody = markdownToHtml(String(body ?? ""));
1645
+ break;
1646
+ default:
1647
+ nextBody = defaultBodyForFormat(next);
1648
+ }
1228
1649
  }
1229
1650
  setFormat(next);
1230
1651
  setBody(nextBody);
1652
+ setPendingBundle(null);
1653
+ if (next !== "html") setNoLayout(false);
1231
1654
  }
1232
1655
  async function save(e) {
1233
1656
  e.preventDefault();
@@ -1235,32 +1658,52 @@ function PostForm({ post }) {
1235
1658
  setError(null);
1236
1659
  try {
1237
1660
  const tags = parseTags(tagsInput);
1661
+ const metadata = buildMetadata();
1662
+ const finalSlug = slug || slugify(title);
1663
+ const finalSiteId = post?.siteId ?? readAdminSiteIdFromCookie();
1664
+ let nextBody = body;
1665
+ if (format === "static") {
1666
+ if (pendingBundle) {
1667
+ nextBody = await uploadBundle({
1668
+ siteId: finalSiteId,
1669
+ slug: finalSlug,
1670
+ files: pendingBundle.files,
1671
+ entrypoint: pendingBundle.entrypoint
1672
+ });
1673
+ } else if (initialStaticBody) {
1674
+ nextBody = initialStaticBody;
1675
+ } else {
1676
+ throw new Error(t("posts.form.static.noBundle"));
1677
+ }
1678
+ }
1238
1679
  if (isEdit) {
1239
1680
  await updatePost(
1240
1681
  post.postId,
1241
1682
  {
1242
1683
  title,
1243
- slug: slug || slugify(title),
1684
+ slug: finalSlug,
1244
1685
  excerpt: excerpt || void 0,
1245
1686
  format,
1246
- body,
1687
+ body: nextBody,
1247
1688
  status,
1248
1689
  publishedAt: status === "published" ? post?.publishedAt ?? (/* @__PURE__ */ new Date()).toISOString() : void 0,
1249
- tags
1690
+ tags,
1691
+ metadata
1250
1692
  },
1251
1693
  { siteId: post.siteId }
1252
1694
  );
1253
1695
  } else {
1254
1696
  await createPost({
1255
- siteId: readAdminSiteIdFromCookie(),
1256
- slug: slug || slugify(title),
1697
+ siteId: finalSiteId,
1698
+ slug: finalSlug,
1257
1699
  title,
1258
1700
  excerpt: excerpt || void 0,
1259
1701
  format,
1260
- body,
1702
+ body: nextBody,
1261
1703
  status,
1262
1704
  publishedAt: status === "published" ? (/* @__PURE__ */ new Date()).toISOString() : void 0,
1263
- tags
1705
+ tags,
1706
+ metadata
1264
1707
  });
1265
1708
  }
1266
1709
  router.push("/admin/posts");
@@ -1276,6 +1719,9 @@ function PostForm({ post }) {
1276
1719
  if (!confirm(t("posts.form.deleteConfirm", { title: post.title }))) return;
1277
1720
  setSaving(true);
1278
1721
  try {
1722
+ if (post.format === "static") {
1723
+ await deleteBundle(post.siteId, post.slug).catch(() => void 0);
1724
+ }
1279
1725
  await deletePost(post.postId);
1280
1726
  router.push("/admin/posts");
1281
1727
  router.refresh();
@@ -1296,9 +1742,9 @@ function PostForm({ post }) {
1296
1742
  publishedAt: status === "published" ? post?.publishedAt ?? (/* @__PURE__ */ new Date()).toISOString() : void 0,
1297
1743
  tags: parseTags(tagsInput)
1298
1744
  };
1299
- return /* @__PURE__ */ jsxs8("form", { onSubmit: save, className: "space-y-6", children: [
1300
- /* @__PURE__ */ jsxs8("div", { className: "flex gap-1 border-b", children: [
1301
- /* @__PURE__ */ jsx10(
1745
+ return /* @__PURE__ */ jsxs9("form", { onSubmit: save, className: "space-y-6", children: [
1746
+ /* @__PURE__ */ jsxs9("div", { className: "flex gap-1 border-b", children: [
1747
+ /* @__PURE__ */ jsx11(
1302
1748
  "button",
1303
1749
  {
1304
1750
  type: "button",
@@ -1308,7 +1754,7 @@ function PostForm({ post }) {
1308
1754
  children: t("posts.form.tabEdit")
1309
1755
  }
1310
1756
  ),
1311
- /* @__PURE__ */ jsx10(
1757
+ /* @__PURE__ */ jsx11(
1312
1758
  "button",
1313
1759
  {
1314
1760
  type: "button",
@@ -1319,24 +1765,24 @@ function PostForm({ post }) {
1319
1765
  }
1320
1766
  )
1321
1767
  ] }),
1322
- view === "preview" && /* @__PURE__ */ jsxs8("article", { className: "space-y-4", children: [
1323
- /* @__PURE__ */ jsxs8("header", { className: "border-b pb-4", children: [
1324
- /* @__PURE__ */ jsx10("h1", { className: "text-3xl font-bold tracking-tight", children: title || /* @__PURE__ */ jsx10("span", { className: "text-muted-foreground italic", children: t("posts.form.previewNoTitle") }) }),
1325
- /* @__PURE__ */ jsxs8("p", { className: "mt-2 text-sm text-muted-foreground", children: [
1326
- previewPost.publishedAt ? /* @__PURE__ */ jsx10("time", { dateTime: previewPost.publishedAt, children: formatDate(previewPost.publishedAt) }) : /* @__PURE__ */ jsx10("span", { children: t("common.draft") }),
1327
- /* @__PURE__ */ jsx10("span", { className: "mx-2", children: "\xB7" }),
1328
- /* @__PURE__ */ jsx10("span", { className: "font-mono text-xs uppercase", children: format })
1768
+ view === "preview" && /* @__PURE__ */ jsxs9("article", { className: "space-y-4", children: [
1769
+ /* @__PURE__ */ jsxs9("header", { className: "border-b pb-4", children: [
1770
+ /* @__PURE__ */ jsx11("h1", { className: "text-3xl font-bold tracking-tight", children: title || /* @__PURE__ */ jsx11("span", { className: "text-muted-foreground italic", children: t("posts.form.previewNoTitle") }) }),
1771
+ /* @__PURE__ */ jsxs9("p", { className: "mt-2 text-sm text-muted-foreground", children: [
1772
+ previewPost.publishedAt ? /* @__PURE__ */ jsx11("time", { dateTime: previewPost.publishedAt, children: formatDate(previewPost.publishedAt) }) : /* @__PURE__ */ jsx11("span", { children: t("common.draft") }),
1773
+ /* @__PURE__ */ jsx11("span", { className: "mx-2", children: "\xB7" }),
1774
+ /* @__PURE__ */ jsx11("span", { className: "font-mono text-xs uppercase", children: format })
1329
1775
  ] }),
1330
- excerpt && /* @__PURE__ */ jsx10("p", { className: "mt-3 text-base text-muted-foreground", children: excerpt })
1776
+ excerpt && /* @__PURE__ */ jsx11("p", { className: "mt-3 text-base text-muted-foreground", children: excerpt })
1331
1777
  ] }),
1332
- /* @__PURE__ */ jsx10(
1778
+ format === "static" ? /* @__PURE__ */ jsx11("p", { className: "text-sm text-muted-foreground", children: t("posts.form.static.previewHint") }) : /* @__PURE__ */ jsx11(
1333
1779
  "div",
1334
1780
  {
1335
1781
  className: "prose prose-neutral dark:prose-invert max-w-none",
1336
1782
  dangerouslySetInnerHTML: { __html: renderBody(previewPost) }
1337
1783
  }
1338
1784
  ),
1339
- previewPost.tags && previewPost.tags.length > 0 && /* @__PURE__ */ jsx10("div", { className: "flex flex-wrap gap-2 border-t pt-4 text-sm", children: previewPost.tags.map((tag) => /* @__PURE__ */ jsxs8(
1785
+ previewPost.tags && previewPost.tags.length > 0 && /* @__PURE__ */ jsx11("div", { className: "flex flex-wrap gap-2 border-t pt-4 text-sm", children: previewPost.tags.map((tag) => /* @__PURE__ */ jsxs9(
1340
1786
  "span",
1341
1787
  {
1342
1788
  className: "rounded-full border px-2 py-0.5 text-xs text-muted-foreground",
@@ -1347,12 +1793,12 @@ function PostForm({ post }) {
1347
1793
  },
1348
1794
  tag
1349
1795
  )) }),
1350
- /* @__PURE__ */ jsx10("p", { className: "text-xs text-muted-foreground", children: t("posts.form.previewHint") })
1796
+ /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: t("posts.form.previewHint") })
1351
1797
  ] }),
1352
- /* @__PURE__ */ jsxs8("div", { className: view === "edit" ? "space-y-6" : "hidden", children: [
1353
- /* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
1354
- /* @__PURE__ */ jsx10(Label2, { htmlFor: "title", children: t("posts.form.title") }),
1355
- /* @__PURE__ */ jsx10(
1798
+ /* @__PURE__ */ jsxs9("div", { className: view === "edit" ? "space-y-6" : "hidden", children: [
1799
+ /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
1800
+ /* @__PURE__ */ jsx11(Label2, { htmlFor: "title", children: t("posts.form.title") }),
1801
+ /* @__PURE__ */ jsx11(
1356
1802
  Input2,
1357
1803
  {
1358
1804
  id: "title",
@@ -1365,9 +1811,9 @@ function PostForm({ post }) {
1365
1811
  }
1366
1812
  )
1367
1813
  ] }),
1368
- /* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
1369
- /* @__PURE__ */ jsx10(Label2, { htmlFor: "slug", children: t("posts.form.slug") }),
1370
- /* @__PURE__ */ jsx10(
1814
+ /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
1815
+ /* @__PURE__ */ jsx11(Label2, { htmlFor: "slug", children: t("posts.form.slug") }),
1816
+ /* @__PURE__ */ jsx11(
1371
1817
  Input2,
1372
1818
  {
1373
1819
  id: "slug",
@@ -1377,9 +1823,9 @@ function PostForm({ post }) {
1377
1823
  }
1378
1824
  )
1379
1825
  ] }),
1380
- /* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
1381
- /* @__PURE__ */ jsx10(Label2, { htmlFor: "excerpt", children: t("posts.form.excerpt") }),
1382
- /* @__PURE__ */ jsx10(
1826
+ /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
1827
+ /* @__PURE__ */ jsx11(Label2, { htmlFor: "excerpt", children: t("posts.form.excerpt") }),
1828
+ /* @__PURE__ */ jsx11(
1383
1829
  Textarea,
1384
1830
  {
1385
1831
  id: "excerpt",
@@ -1389,9 +1835,9 @@ function PostForm({ post }) {
1389
1835
  }
1390
1836
  )
1391
1837
  ] }),
1392
- /* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
1393
- /* @__PURE__ */ jsx10(Label2, { htmlFor: "format", children: t("posts.form.format") }),
1394
- /* @__PURE__ */ jsxs8(
1838
+ /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
1839
+ /* @__PURE__ */ jsx11(Label2, { htmlFor: "format", children: t("posts.form.format") }),
1840
+ /* @__PURE__ */ jsxs9(
1395
1841
  "select",
1396
1842
  {
1397
1843
  id: "format",
@@ -1399,33 +1845,41 @@ function PostForm({ post }) {
1399
1845
  onChange: (e) => changeFormat(e.target.value),
1400
1846
  className: "flex h-9 w-full max-w-xs rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm",
1401
1847
  children: [
1402
- /* @__PURE__ */ jsx10("option", { value: "tiptap", children: "Tiptap (rich editor)" }),
1403
- /* @__PURE__ */ jsx10("option", { value: "markdown", children: "Markdown" }),
1404
- /* @__PURE__ */ jsx10("option", { value: "html", children: "HTML" })
1848
+ /* @__PURE__ */ jsx11("option", { value: "tiptap", children: "Tiptap (rich editor)" }),
1849
+ /* @__PURE__ */ jsx11("option", { value: "markdown", children: "Markdown" }),
1850
+ /* @__PURE__ */ jsx11("option", { value: "html", children: "HTML" }),
1851
+ /* @__PURE__ */ jsx11("option", { value: "static", children: t("posts.form.formatStaticLabel") })
1405
1852
  ]
1406
1853
  }
1407
1854
  ),
1408
- /* @__PURE__ */ jsx10("p", { className: "text-xs text-muted-foreground", children: t("posts.form.formatHint") })
1855
+ /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: t("posts.form.formatHint") })
1409
1856
  ] }),
1410
- /* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
1411
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between", children: [
1412
- /* @__PURE__ */ jsx10(Label2, { children: t("posts.form.body") }),
1413
- format !== "tiptap" && // For textarea-based formats (markdown / html) there's no
1857
+ /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
1858
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between", children: [
1859
+ /* @__PURE__ */ jsx11(Label2, { children: t("posts.form.body") }),
1860
+ format !== "tiptap" && format !== "static" && // For textarea-based formats (markdown / html) there's no
1414
1861
  // embedded toolbar, so we surface the MediaPicker as a
1415
1862
  // standalone button. Selecting an asset inserts a
1416
1863
  // format-aware snippet at the cursor.
1417
- /* @__PURE__ */ jsx10(
1864
+ /* @__PURE__ */ jsx11(
1418
1865
  MediaPicker,
1419
1866
  {
1420
1867
  onSelect: insertMediaSnippet,
1421
- trigger: /* @__PURE__ */ jsxs8(Button7, { type: "button", variant: "outline", size: "sm", children: [
1422
- /* @__PURE__ */ jsx10(ImageIcon3, { className: "mr-2 h-3 w-3" }),
1868
+ trigger: /* @__PURE__ */ jsxs9(Button8, { type: "button", variant: "outline", size: "sm", children: [
1869
+ /* @__PURE__ */ jsx11(ImageIcon3, { className: "mr-2 h-3 w-3" }),
1423
1870
  t("posts.form.insertMedia")
1424
1871
  ] })
1425
1872
  }
1426
1873
  )
1427
1874
  ] }),
1428
- format === "tiptap" ? /* @__PURE__ */ jsx10(TiptapEditor, { initialContent: body, onChange: setBody }) : /* @__PURE__ */ jsx10(
1875
+ format === "tiptap" ? /* @__PURE__ */ jsx11(TiptapEditor, { initialContent: body, onChange: setBody }) : format === "static" ? /* @__PURE__ */ jsx11(
1876
+ StaticUploader,
1877
+ {
1878
+ initial: initialStaticBody,
1879
+ onFilesReady: (files, entrypoint) => setPendingBundle({ files, entrypoint }),
1880
+ onClear: () => setPendingBundle(null)
1881
+ }
1882
+ ) : /* @__PURE__ */ jsx11(
1429
1883
  Textarea,
1430
1884
  {
1431
1885
  ref: bodyTextareaRef,
@@ -1436,9 +1890,9 @@ function PostForm({ post }) {
1436
1890
  }
1437
1891
  )
1438
1892
  ] }),
1439
- /* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
1440
- /* @__PURE__ */ jsx10(Label2, { htmlFor: "tags", children: t("posts.form.tags") }),
1441
- /* @__PURE__ */ jsx10(
1893
+ /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
1894
+ /* @__PURE__ */ jsx11(Label2, { htmlFor: "tags", children: t("posts.form.tags") }),
1895
+ /* @__PURE__ */ jsx11(
1442
1896
  Input2,
1443
1897
  {
1444
1898
  id: "tags",
@@ -1447,11 +1901,11 @@ function PostForm({ post }) {
1447
1901
  placeholder: t("posts.form.tagsPlaceholder")
1448
1902
  }
1449
1903
  ),
1450
- /* @__PURE__ */ jsx10("p", { className: "text-xs text-muted-foreground", children: t("posts.form.tagsHint") })
1904
+ /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: t("posts.form.tagsHint") })
1451
1905
  ] }),
1452
- /* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
1453
- /* @__PURE__ */ jsx10(Label2, { htmlFor: "status", children: t("posts.form.status") }),
1454
- /* @__PURE__ */ jsxs8(
1906
+ /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
1907
+ /* @__PURE__ */ jsx11(Label2, { htmlFor: "status", children: t("posts.form.status") }),
1908
+ /* @__PURE__ */ jsxs9(
1455
1909
  "select",
1456
1910
  {
1457
1911
  id: "status",
@@ -1459,42 +1913,57 @@ function PostForm({ post }) {
1459
1913
  onChange: (e) => setStatus(e.target.value),
1460
1914
  className: "flex h-9 w-full max-w-xs rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm",
1461
1915
  children: [
1462
- /* @__PURE__ */ jsx10("option", { value: "draft", children: t("common.draft") }),
1463
- /* @__PURE__ */ jsx10("option", { value: "published", children: t("common.published") })
1916
+ /* @__PURE__ */ jsx11("option", { value: "draft", children: t("common.draft") }),
1917
+ /* @__PURE__ */ jsx11("option", { value: "published", children: t("common.published") })
1464
1918
  ]
1465
1919
  }
1466
1920
  )
1467
1921
  ] }),
1468
- error && /* @__PURE__ */ jsx10("p", { className: "text-sm text-destructive", children: error }),
1469
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
1470
- /* @__PURE__ */ jsx10(Button7, { type: "submit", disabled: saving, children: saving ? t("common.saving") : isEdit ? t("posts.form.saveChanges") : t("posts.form.createPost") }),
1471
- isEdit && /* @__PURE__ */ jsx10(Button7, { type: "button", variant: "destructive", onClick: handleDelete, disabled: saving, children: t("posts.form.delete") })
1922
+ format === "html" && /* @__PURE__ */ jsx11("div", { className: "space-y-2", children: /* @__PURE__ */ jsxs9("label", { className: "flex items-start gap-2 text-sm", children: [
1923
+ /* @__PURE__ */ jsx11(
1924
+ "input",
1925
+ {
1926
+ type: "checkbox",
1927
+ checked: noLayout,
1928
+ onChange: (e) => setNoLayout(e.target.checked),
1929
+ className: "mt-1"
1930
+ }
1931
+ ),
1932
+ /* @__PURE__ */ jsxs9("span", { children: [
1933
+ /* @__PURE__ */ jsx11("span", { className: "font-medium", children: t("posts.form.noLayout") }),
1934
+ /* @__PURE__ */ jsx11("span", { className: "block text-xs text-muted-foreground", children: t("posts.form.noLayoutHint") })
1935
+ ] })
1936
+ ] }) }),
1937
+ error && /* @__PURE__ */ jsx11("p", { className: "text-sm text-destructive", children: error }),
1938
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2", children: [
1939
+ /* @__PURE__ */ jsx11(Button8, { type: "submit", disabled: saving, children: saving ? t("common.saving") : isEdit ? t("posts.form.saveChanges") : t("posts.form.createPost") }),
1940
+ isEdit && /* @__PURE__ */ jsx11(Button8, { type: "button", variant: "destructive", onClick: handleDelete, disabled: saving, children: t("posts.form.delete") })
1472
1941
  ] })
1473
1942
  ] })
1474
1943
  ] });
1475
1944
  }
1476
1945
 
1477
1946
  // src/components/new-post-view.tsx
1478
- import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1947
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1479
1948
  function NewPostPage() {
1480
1949
  const t = useT();
1481
- return /* @__PURE__ */ jsxs9("div", { className: "p-8", children: [
1482
- /* @__PURE__ */ jsx11("h1", { className: "mb-8 text-3xl font-bold", children: t("posts.form.newTitle") }),
1483
- /* @__PURE__ */ jsx11(PostForm, {})
1950
+ return /* @__PURE__ */ jsxs10("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
1951
+ /* @__PURE__ */ jsx12("h1", { className: "mb-6 text-2xl font-bold md:mb-8 md:text-3xl", children: t("posts.form.newTitle") }),
1952
+ /* @__PURE__ */ jsx12(PostForm, {})
1484
1953
  ] });
1485
1954
  }
1486
1955
 
1487
1956
  // src/components/edit-post-view.tsx
1488
- import { useEffect as useEffect5, useState as useState6, use } from "react";
1957
+ import { useEffect as useEffect5, useState as useState7, use } from "react";
1489
1958
  import { notFound } from "next/navigation";
1490
1959
  import { getPostById } from "ampless";
1491
- import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1960
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1492
1961
  function EditPostPage({ params }) {
1493
1962
  const t = useT();
1494
1963
  const { postId } = use(params);
1495
- const [post, setPost] = useState6(null);
1496
- const [loading, setLoading] = useState6(true);
1497
- const [missing, setMissing] = useState6(false);
1964
+ const [post, setPost] = useState7(null);
1965
+ const [loading, setLoading] = useState7(true);
1966
+ const [missing, setMissing] = useState7(false);
1498
1967
  useEffect5(() => {
1499
1968
  const siteId = readAdminSiteIdFromCookie();
1500
1969
  getPostById(postId, { siteId }).then((p) => {
@@ -1502,21 +1971,22 @@ function EditPostPage({ params }) {
1502
1971
  else setPost(p);
1503
1972
  }).finally(() => setLoading(false));
1504
1973
  }, [postId]);
1505
- if (loading) return /* @__PURE__ */ jsx12("div", { className: "p-8", children: t("common.loading") });
1974
+ if (loading)
1975
+ return /* @__PURE__ */ jsx13("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: t("common.loading") });
1506
1976
  if (missing) notFound();
1507
- return /* @__PURE__ */ jsxs10("div", { className: "p-8", children: [
1508
- /* @__PURE__ */ jsx12("h1", { className: "mb-8 text-3xl font-bold", children: t("posts.form.editTitle") }),
1509
- post && /* @__PURE__ */ jsx12(PostForm, { post })
1977
+ return /* @__PURE__ */ jsxs11("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
1978
+ /* @__PURE__ */ jsx13("h1", { className: "mb-6 text-2xl font-bold md:mb-8 md:text-3xl", children: t("posts.form.editTitle") }),
1979
+ post && /* @__PURE__ */ jsx13(PostForm, { post })
1510
1980
  ] });
1511
1981
  }
1512
1982
 
1513
1983
  // src/components/media-uploader.tsx
1514
- import { useState as useState7, useEffect as useEffect6, useCallback, useRef as useRef4 } from "react";
1515
- import { uploadData as uploadData2, list as list2, remove, isCancelError } from "aws-amplify/storage";
1984
+ import { useState as useState8, useEffect as useEffect6, useCallback, useRef as useRef4 } from "react";
1985
+ import { uploadData as uploadData3, list as list3, remove as remove2, isCancelError } from "aws-amplify/storage";
1516
1986
  import { processImage as processImage2 } from "ampless/media";
1517
- import { Button as Button8, Input as Input3 } from "@ampless/runtime/ui";
1518
- import { Trash2 as Trash22, Copy, Check, FileText, Code2 } from "lucide-react";
1519
- import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1987
+ import { Button as Button9, Input as Input3 } from "@ampless/runtime/ui";
1988
+ import { Trash2 as Trash22, Copy, Check, FileText as FileText2, Code2 } from "lucide-react";
1989
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1520
1990
  var IMAGE_EXT_RE = /\.(jpe?g|png|gif|webp|avif|svg|bmp|tiff?)$/i;
1521
1991
  var STYLESHEET_EXT_RE = /\.css$/i;
1522
1992
  var SCRIPT_EXT_RE = /\.m?js$/i;
@@ -1541,16 +2011,16 @@ function sanitizeName2(name) {
1541
2011
  }
1542
2012
  function MediaUploader() {
1543
2013
  const t = useT();
1544
- const [items, setItems] = useState7([]);
1545
- const [queue, setQueue] = useState7([]);
1546
- const [uploading, setUploading] = useState7(false);
1547
- const [error, setError] = useState7(null);
1548
- const [copiedPath, setCopiedPath] = useState7(null);
2014
+ const [items, setItems] = useState8([]);
2015
+ const [queue, setQueue] = useState8([]);
2016
+ const [uploading, setUploading] = useState8(false);
2017
+ const [error, setError] = useState8(null);
2018
+ const [copiedPath, setCopiedPath] = useState8(null);
1549
2019
  const uploadTaskRef = useRef4(null);
1550
2020
  const cancelTokenRef = useRef4({ cancelled: false });
1551
2021
  const refresh = useCallback(async () => {
1552
2022
  try {
1553
- const result = await list2({ path: "public/media/" });
2023
+ const result = await list3({ path: "public/media/" });
1554
2024
  setItems(
1555
2025
  result.items.map((item) => ({
1556
2026
  path: item.path,
@@ -1588,7 +2058,7 @@ function MediaUploader() {
1588
2058
  const yyyy = now.getFullYear();
1589
2059
  const mm = String(now.getMonth() + 1).padStart(2, "0");
1590
2060
  const path = `public/media/${yyyy}/${mm}/${Date.now()}-${safeName}`;
1591
- const task = uploadData2({
2061
+ const task = uploadData3({
1592
2062
  path,
1593
2063
  data: processed.blob,
1594
2064
  options: { contentType: processed.mime }
@@ -1622,7 +2092,7 @@ function MediaUploader() {
1622
2092
  async function handleDelete(path) {
1623
2093
  if (!confirm(t("media.deleteConfirm"))) return;
1624
2094
  try {
1625
- await remove({ path });
2095
+ await remove2({ path });
1626
2096
  await refresh();
1627
2097
  } catch (err) {
1628
2098
  setError(err instanceof Error ? err.message : String(err));
@@ -1636,9 +2106,9 @@ function MediaUploader() {
1636
2106
  setTimeout(() => setCopiedPath((p) => p === key ? null : p), 1500);
1637
2107
  }
1638
2108
  const currentFile = queue[0] ?? null;
1639
- return /* @__PURE__ */ jsxs11("div", { className: "space-y-6", children: [
1640
- /* @__PURE__ */ jsxs11("div", { className: "rounded-md border p-4", children: [
1641
- /* @__PURE__ */ jsx13(
2109
+ return /* @__PURE__ */ jsxs12("div", { className: "space-y-6", children: [
2110
+ /* @__PURE__ */ jsxs12("div", { className: "rounded-md border p-4", children: [
2111
+ /* @__PURE__ */ jsx14(
1642
2112
  Input3,
1643
2113
  {
1644
2114
  type: "file",
@@ -1647,11 +2117,11 @@ function MediaUploader() {
1647
2117
  disabled: uploading
1648
2118
  }
1649
2119
  ),
1650
- uploading && /* @__PURE__ */ jsx13("p", { className: "mt-2 text-sm text-muted-foreground", children: t("media.uploading") }),
1651
- !uploading && queue.length > 0 && /* @__PURE__ */ jsx13("p", { className: "mt-2 text-sm text-muted-foreground", children: t("media.queued", { count: queue.length }) })
2120
+ uploading && /* @__PURE__ */ jsx14("p", { className: "mt-2 text-sm text-muted-foreground", children: t("media.uploading") }),
2121
+ !uploading && queue.length > 0 && /* @__PURE__ */ jsx14("p", { className: "mt-2 text-sm text-muted-foreground", children: t("media.queued", { count: queue.length }) })
1652
2122
  ] }),
1653
- error && /* @__PURE__ */ jsx13("p", { className: "text-sm text-destructive", children: error }),
1654
- /* @__PURE__ */ jsx13(
2123
+ error && /* @__PURE__ */ jsx14("p", { className: "text-sm text-destructive", children: error }),
2124
+ /* @__PURE__ */ jsx14(
1655
2125
  ImageUploadDialog,
1656
2126
  {
1657
2127
  file: currentFile,
@@ -1663,7 +2133,7 @@ function MediaUploader() {
1663
2133
  onCancel: handleDialogCancel
1664
2134
  }
1665
2135
  ),
1666
- /* @__PURE__ */ jsx13("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4", children: items.map((item) => {
2136
+ /* @__PURE__ */ jsx14("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4", children: items.map((item) => {
1667
2137
  const isImage = IMAGE_EXT_RE.test(item.path);
1668
2138
  const isStylesheet = STYLESHEET_EXT_RE.test(item.path);
1669
2139
  const isScript = SCRIPT_EXT_RE.test(item.path);
@@ -1673,14 +2143,14 @@ function MediaUploader() {
1673
2143
  const tagDiffersFromUrl = tagSnippet !== item.url;
1674
2144
  const urlCopied = copiedPath === `${item.path}:url`;
1675
2145
  const tagCopied = copiedPath === `${item.path}:tag`;
1676
- return /* @__PURE__ */ jsxs11(
2146
+ return /* @__PURE__ */ jsxs12(
1677
2147
  "div",
1678
2148
  {
1679
2149
  className: "group relative overflow-hidden rounded-md border bg-[var(--card)]",
1680
2150
  children: [
1681
2151
  isImage ? (
1682
2152
  // eslint-disable-next-line @next/next/no-img-element
1683
- /* @__PURE__ */ jsx13(
2153
+ /* @__PURE__ */ jsx14(
1684
2154
  "img",
1685
2155
  {
1686
2156
  src: item.url,
@@ -1688,18 +2158,18 @@ function MediaUploader() {
1688
2158
  className: "aspect-square w-full object-cover"
1689
2159
  }
1690
2160
  )
1691
- ) : /* @__PURE__ */ jsxs11("div", { className: "flex aspect-square w-full flex-col items-center justify-center gap-2 bg-muted text-muted-foreground", children: [
1692
- isStylesheet || isScript ? /* @__PURE__ */ jsx13(Code2, { className: "h-8 w-8" }) : /* @__PURE__ */ jsx13(FileText, { className: "h-8 w-8" }),
1693
- /* @__PURE__ */ jsxs11("span", { className: "font-mono text-xs font-semibold", children: [
2161
+ ) : /* @__PURE__ */ jsxs12("div", { className: "flex aspect-square w-full flex-col items-center justify-center gap-2 bg-muted text-muted-foreground", children: [
2162
+ isStylesheet || isScript ? /* @__PURE__ */ jsx14(Code2, { className: "h-8 w-8" }) : /* @__PURE__ */ jsx14(FileText2, { className: "h-8 w-8" }),
2163
+ /* @__PURE__ */ jsxs12("span", { className: "font-mono text-xs font-semibold", children: [
1694
2164
  ".",
1695
2165
  ext.toLowerCase()
1696
2166
  ] })
1697
2167
  ] }),
1698
- /* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between border-t px-2 py-1 text-xs", children: [
1699
- /* @__PURE__ */ jsx13("span", { className: "truncate", title: filename, children: filename }),
1700
- /* @__PURE__ */ jsxs11("div", { className: "flex shrink-0 items-center gap-0.5", children: [
1701
- /* @__PURE__ */ jsx13(
1702
- Button8,
2168
+ /* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between border-t px-2 py-1 text-xs", children: [
2169
+ /* @__PURE__ */ jsx14("span", { className: "truncate", title: filename, children: filename }),
2170
+ /* @__PURE__ */ jsxs12("div", { className: "flex shrink-0 items-center gap-0.5", children: [
2171
+ /* @__PURE__ */ jsx14(
2172
+ Button9,
1703
2173
  {
1704
2174
  type: "button",
1705
2175
  variant: "ghost",
@@ -1707,11 +2177,11 @@ function MediaUploader() {
1707
2177
  className: "h-6 w-6",
1708
2178
  onClick: () => handleCopy(item, "url"),
1709
2179
  title: t("media.copyUrl"),
1710
- children: urlCopied ? /* @__PURE__ */ jsx13(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx13(Copy, { className: "h-3 w-3" })
2180
+ children: urlCopied ? /* @__PURE__ */ jsx14(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx14(Copy, { className: "h-3 w-3" })
1711
2181
  }
1712
2182
  ),
1713
- tagDiffersFromUrl && /* @__PURE__ */ jsx13(
1714
- Button8,
2183
+ tagDiffersFromUrl && /* @__PURE__ */ jsx14(
2184
+ Button9,
1715
2185
  {
1716
2186
  type: "button",
1717
2187
  variant: "ghost",
@@ -1719,11 +2189,11 @@ function MediaUploader() {
1719
2189
  className: "h-6 w-6",
1720
2190
  onClick: () => handleCopy(item, "tag"),
1721
2191
  title: t("media.copyTag"),
1722
- children: tagCopied ? /* @__PURE__ */ jsx13(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx13(Code2, { className: "h-3 w-3" })
2192
+ children: tagCopied ? /* @__PURE__ */ jsx14(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx14(Code2, { className: "h-3 w-3" })
1723
2193
  }
1724
2194
  ),
1725
- /* @__PURE__ */ jsx13(
1726
- Button8,
2195
+ /* @__PURE__ */ jsx14(
2196
+ Button9,
1727
2197
  {
1728
2198
  type: "button",
1729
2199
  variant: "ghost",
@@ -1731,7 +2201,7 @@ function MediaUploader() {
1731
2201
  className: "h-6 w-6",
1732
2202
  onClick: () => handleDelete(item.path),
1733
2203
  title: t("media.delete"),
1734
- children: /* @__PURE__ */ jsx13(Trash22, { className: "h-3 w-3" })
2204
+ children: /* @__PURE__ */ jsx14(Trash22, { className: "h-3 w-3" })
1735
2205
  }
1736
2206
  )
1737
2207
  ] })
@@ -1741,22 +2211,22 @@ function MediaUploader() {
1741
2211
  item.path
1742
2212
  );
1743
2213
  }) }),
1744
- items.length === 0 && /* @__PURE__ */ jsx13("p", { className: "text-center text-sm text-muted-foreground", children: t("media.empty") })
2214
+ items.length === 0 && /* @__PURE__ */ jsx14("p", { className: "text-center text-sm text-muted-foreground", children: t("media.empty") })
1745
2215
  ] });
1746
2216
  }
1747
2217
 
1748
2218
  // src/components/media-view.tsx
1749
- import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
2219
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
1750
2220
  function MediaPage() {
1751
2221
  const t = useT();
1752
- return /* @__PURE__ */ jsxs12("div", { className: "p-8", children: [
1753
- /* @__PURE__ */ jsx14("h1", { className: "mb-8 text-3xl font-bold", children: t("media.title") }),
1754
- /* @__PURE__ */ jsx14(MediaUploader, {})
2222
+ return /* @__PURE__ */ jsxs13("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
2223
+ /* @__PURE__ */ jsx15("h1", { className: "mb-6 text-2xl font-bold md:mb-8 md:text-3xl", children: t("media.title") }),
2224
+ /* @__PURE__ */ jsx15(MediaUploader, {})
1755
2225
  ] });
1756
2226
  }
1757
2227
 
1758
2228
  // src/components/login-view.tsx
1759
- import { useState as useState8 } from "react";
2229
+ import { useState as useState9 } from "react";
1760
2230
  import { useRouter as useRouter2 } from "next/navigation";
1761
2231
  import {
1762
2232
  signIn,
@@ -1766,7 +2236,7 @@ import {
1766
2236
  confirmResetPassword
1767
2237
  } from "aws-amplify/auth";
1768
2238
  import {
1769
- Button as Button9,
2239
+ Button as Button10,
1770
2240
  Input as Input4,
1771
2241
  Label as Label3,
1772
2242
  Card as Card2,
@@ -1775,17 +2245,17 @@ import {
1775
2245
  CardTitle as CardTitle2,
1776
2246
  CardDescription
1777
2247
  } from "@ampless/runtime/ui";
1778
- import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
2248
+ import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
1779
2249
  function LoginPage() {
1780
2250
  const router = useRouter2();
1781
2251
  const t = useT();
1782
- const [mode, setMode] = useState8("signIn");
1783
- const [email, setEmail] = useState8("");
1784
- const [password, setPassword] = useState8("");
1785
- const [code, setCode] = useState8("");
1786
- const [error, setError] = useState8(null);
1787
- const [info, setInfo] = useState8(null);
1788
- const [loading, setLoading] = useState8(false);
2252
+ const [mode, setMode] = useState9("signIn");
2253
+ const [email, setEmail] = useState9("");
2254
+ const [password, setPassword] = useState9("");
2255
+ const [code, setCode] = useState9("");
2256
+ const [error, setError] = useState9(null);
2257
+ const [info, setInfo] = useState9(null);
2258
+ const [loading, setLoading] = useState9(false);
1789
2259
  function go(next) {
1790
2260
  setMode(next);
1791
2261
  setError(null);
@@ -1849,15 +2319,15 @@ function LoginPage() {
1849
2319
  const showEmail = mode !== "confirm" && mode !== "reset";
1850
2320
  const showPassword = mode === "signIn" || mode === "signUp" || mode === "reset";
1851
2321
  const showCode = mode === "confirm" || mode === "reset";
1852
- return /* @__PURE__ */ jsx15("main", { className: "flex min-h-screen items-center justify-center bg-muted/30 p-4", children: /* @__PURE__ */ jsxs13(Card2, { className: "w-full max-w-md", children: [
1853
- /* @__PURE__ */ jsxs13(CardHeader2, { children: [
1854
- /* @__PURE__ */ jsx15(CardTitle2, { children: t(`auth.${mode}.title`) }),
1855
- /* @__PURE__ */ jsx15(CardDescription, { children: t(`auth.${mode}.description`) })
2322
+ return /* @__PURE__ */ jsx16("main", { className: "flex min-h-screen items-center justify-center bg-muted/30 p-4", children: /* @__PURE__ */ jsxs14(Card2, { className: "w-full max-w-md", children: [
2323
+ /* @__PURE__ */ jsxs14(CardHeader2, { children: [
2324
+ /* @__PURE__ */ jsx16(CardTitle2, { children: t(`auth.${mode}.title`) }),
2325
+ /* @__PURE__ */ jsx16(CardDescription, { children: t(`auth.${mode}.description`) })
1856
2326
  ] }),
1857
- /* @__PURE__ */ jsx15(CardContent2, { children: /* @__PURE__ */ jsxs13("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
1858
- showEmail && /* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
1859
- /* @__PURE__ */ jsx15(Label3, { htmlFor: "email", children: t("auth.common.email") }),
1860
- /* @__PURE__ */ jsx15(
2327
+ /* @__PURE__ */ jsx16(CardContent2, { children: /* @__PURE__ */ jsxs14("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
2328
+ showEmail && /* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
2329
+ /* @__PURE__ */ jsx16(Label3, { htmlFor: "email", children: t("auth.common.email") }),
2330
+ /* @__PURE__ */ jsx16(
1861
2331
  Input4,
1862
2332
  {
1863
2333
  id: "email",
@@ -1869,9 +2339,9 @@ function LoginPage() {
1869
2339
  }
1870
2340
  )
1871
2341
  ] }),
1872
- showCode && /* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
1873
- /* @__PURE__ */ jsx15(Label3, { htmlFor: "code", children: t("auth.common.code") }),
1874
- /* @__PURE__ */ jsx15(
2342
+ showCode && /* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
2343
+ /* @__PURE__ */ jsx16(Label3, { htmlFor: "code", children: t("auth.common.code") }),
2344
+ /* @__PURE__ */ jsx16(
1875
2345
  Input4,
1876
2346
  {
1877
2347
  id: "code",
@@ -1882,9 +2352,9 @@ function LoginPage() {
1882
2352
  }
1883
2353
  )
1884
2354
  ] }),
1885
- showPassword && /* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
1886
- /* @__PURE__ */ jsx15(Label3, { htmlFor: "password", children: mode === "reset" ? t("auth.common.newPassword") : t("auth.common.password") }),
1887
- /* @__PURE__ */ jsx15(
2355
+ showPassword && /* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
2356
+ /* @__PURE__ */ jsx16(Label3, { htmlFor: "password", children: mode === "reset" ? t("auth.common.newPassword") : t("auth.common.password") }),
2357
+ /* @__PURE__ */ jsx16(
1888
2358
  Input4,
1889
2359
  {
1890
2360
  id: "password",
@@ -1896,14 +2366,14 @@ function LoginPage() {
1896
2366
  autoComplete: mode === "signIn" ? "current-password" : "new-password"
1897
2367
  }
1898
2368
  ),
1899
- (mode === "signUp" || mode === "reset") && /* @__PURE__ */ jsx15("p", { className: "text-xs text-muted-foreground", children: t("auth.common.passwordHint") })
2369
+ (mode === "signUp" || mode === "reset") && /* @__PURE__ */ jsx16("p", { className: "text-xs text-muted-foreground", children: t("auth.common.passwordHint") })
1900
2370
  ] }),
1901
- info && /* @__PURE__ */ jsx15("p", { className: "text-sm text-muted-foreground", children: info }),
1902
- error && /* @__PURE__ */ jsx15("p", { className: "text-sm text-destructive", children: error }),
1903
- /* @__PURE__ */ jsx15(Button9, { type: "submit", className: "w-full", disabled: loading, children: loading ? t("auth.common.working") : t(`auth.${mode}.submit`) }),
1904
- /* @__PURE__ */ jsxs13("div", { className: "space-y-1 text-center text-sm", children: [
1905
- mode === "signIn" && /* @__PURE__ */ jsxs13(Fragment4, { children: [
1906
- /* @__PURE__ */ jsx15("p", { children: /* @__PURE__ */ jsx15(
2371
+ info && /* @__PURE__ */ jsx16("p", { className: "text-sm text-muted-foreground", children: info }),
2372
+ error && /* @__PURE__ */ jsx16("p", { className: "text-sm text-destructive", children: error }),
2373
+ /* @__PURE__ */ jsx16(Button10, { type: "submit", className: "w-full", disabled: loading, children: loading ? t("auth.common.working") : t(`auth.${mode}.submit`) }),
2374
+ /* @__PURE__ */ jsxs14("div", { className: "space-y-1 text-center text-sm", children: [
2375
+ mode === "signIn" && /* @__PURE__ */ jsxs14(Fragment4, { children: [
2376
+ /* @__PURE__ */ jsx16("p", { children: /* @__PURE__ */ jsx16(
1907
2377
  "button",
1908
2378
  {
1909
2379
  type: "button",
@@ -1912,7 +2382,7 @@ function LoginPage() {
1912
2382
  children: t("auth.signIn.forgotPassword")
1913
2383
  }
1914
2384
  ) }),
1915
- /* @__PURE__ */ jsx15("p", { children: /* @__PURE__ */ jsx15(
2385
+ /* @__PURE__ */ jsx16("p", { children: /* @__PURE__ */ jsx16(
1916
2386
  "button",
1917
2387
  {
1918
2388
  type: "button",
@@ -1922,7 +2392,7 @@ function LoginPage() {
1922
2392
  }
1923
2393
  ) })
1924
2394
  ] }),
1925
- (mode === "signUp" || mode === "forgot" || mode === "reset") && /* @__PURE__ */ jsx15("p", { children: /* @__PURE__ */ jsx15(
2395
+ (mode === "signUp" || mode === "forgot" || mode === "reset") && /* @__PURE__ */ jsx16("p", { children: /* @__PURE__ */ jsx16(
1926
2396
  "button",
1927
2397
  {
1928
2398
  type: "button",
@@ -1931,7 +2401,7 @@ function LoginPage() {
1931
2401
  children: t("auth.signUp.backToSignIn")
1932
2402
  }
1933
2403
  ) }),
1934
- mode === "reset" && /* @__PURE__ */ jsx15("p", { children: /* @__PURE__ */ jsx15(
2404
+ mode === "reset" && /* @__PURE__ */ jsx16("p", { children: /* @__PURE__ */ jsx16(
1935
2405
  "button",
1936
2406
  {
1937
2407
  type: "button",
@@ -1946,83 +2416,156 @@ function LoginPage() {
1946
2416
  }
1947
2417
 
1948
2418
  // src/components/sidebar.tsx
2419
+ import { useEffect as useEffect7, useState as useState10 } from "react";
1949
2420
  import Link4 from "next/link";
1950
2421
  import { usePathname } from "next/navigation";
1951
2422
  import { signOut } from "aws-amplify/auth";
1952
- import { LayoutDashboard, FileText as FileText2, Image as Image2, Globe, LogOut, ExternalLink } from "lucide-react";
1953
- import { Button as Button10, cn as cn3 } from "@ampless/runtime/ui";
1954
- import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
2423
+ import {
2424
+ LayoutDashboard,
2425
+ FileText as FileText3,
2426
+ Image as Image2,
2427
+ Globe,
2428
+ Users,
2429
+ LogOut,
2430
+ ExternalLink,
2431
+ Menu,
2432
+ X as X2
2433
+ } from "lucide-react";
2434
+ import { Button as Button11, cn as cn3 } from "@ampless/runtime/ui";
2435
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
1955
2436
  var navItems = [
1956
2437
  { href: "/admin", key: "sidebar.dashboard", icon: LayoutDashboard },
1957
- { href: "/admin/posts", key: "sidebar.posts", icon: FileText2 },
2438
+ { href: "/admin/posts", key: "sidebar.posts", icon: FileText3 },
1958
2439
  { href: "/admin/media", key: "sidebar.media", icon: Image2 },
1959
- { href: "/admin/sites", key: "sidebar.sites", icon: Globe }
2440
+ { href: "/admin/sites", key: "sidebar.sites", icon: Globe },
2441
+ { href: "/admin/users", key: "sidebar.users", icon: Users, adminOnly: true }
1960
2442
  ];
1961
2443
  function Sidebar({
1962
2444
  email,
1963
- siteSelector
2445
+ siteSelector,
2446
+ isAdmin
1964
2447
  }) {
1965
2448
  const pathname = usePathname();
1966
2449
  const t = useT();
1967
- return /* @__PURE__ */ jsxs14("aside", { className: "flex h-screen w-60 flex-col border-r bg-muted/30", children: [
1968
- /* @__PURE__ */ jsx16("div", { className: "border-b p-4", children: /* @__PURE__ */ jsx16(Link4, { href: "/admin", className: "font-semibold", children: t("sidebar.brand") }) }),
1969
- siteSelector ? /* @__PURE__ */ jsx16("div", { className: "border-b", children: siteSelector }) : null,
1970
- /* @__PURE__ */ jsx16("nav", { className: "flex-1 space-y-1 p-2", children: navItems.map((item) => {
1971
- const Icon = item.icon;
1972
- const isActive = pathname === item.href || item.href !== "/admin" && pathname?.startsWith(item.href);
1973
- return /* @__PURE__ */ jsxs14(
1974
- Link4,
1975
- {
1976
- href: item.href,
1977
- className: cn3(
1978
- "flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors",
1979
- isActive ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
1980
- ),
1981
- children: [
1982
- /* @__PURE__ */ jsx16(Icon, { className: "h-4 w-4" }),
1983
- t(item.key)
1984
- ]
1985
- },
1986
- item.href
1987
- );
1988
- }) }),
1989
- /* @__PURE__ */ jsxs14("div", { className: "border-t p-2 space-y-1", children: [
1990
- /* @__PURE__ */ jsxs14(
1991
- Link4,
1992
- {
1993
- href: "/",
1994
- target: "_blank",
1995
- className: "flex items-center gap-3 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground",
1996
- children: [
1997
- /* @__PURE__ */ jsx16(ExternalLink, { className: "h-4 w-4" }),
1998
- t("sidebar.viewSite")
1999
- ]
2000
- }
2001
- ),
2002
- /* @__PURE__ */ jsx16("div", { className: "px-3 py-2 text-xs text-muted-foreground truncate", children: email }),
2003
- /* @__PURE__ */ jsxs14(
2004
- Button10,
2450
+ const [open, setOpen] = useState10(false);
2451
+ useEffect7(() => {
2452
+ setOpen(false);
2453
+ }, [pathname]);
2454
+ useEffect7(() => {
2455
+ if (!open) return;
2456
+ const prev = document.body.style.overflow;
2457
+ document.body.style.overflow = "hidden";
2458
+ return () => {
2459
+ document.body.style.overflow = prev;
2460
+ };
2461
+ }, [open]);
2462
+ return /* @__PURE__ */ jsxs15(Fragment5, { children: [
2463
+ /* @__PURE__ */ jsxs15("header", { className: "sticky top-0 z-30 flex h-14 items-center justify-between border-b bg-background px-4 md:hidden", children: [
2464
+ /* @__PURE__ */ jsx17(Link4, { href: "/admin", className: "font-semibold", children: t("sidebar.brand") }),
2465
+ /* @__PURE__ */ jsx17(
2466
+ Button11,
2005
2467
  {
2006
2468
  variant: "ghost",
2007
- size: "sm",
2008
- className: "w-full justify-start gap-3",
2009
- onClick: async () => {
2010
- await signOut();
2011
- window.location.href = "/login";
2012
- },
2013
- children: [
2014
- /* @__PURE__ */ jsx16(LogOut, { className: "h-4 w-4" }),
2015
- t("sidebar.signOut")
2016
- ]
2469
+ size: "icon",
2470
+ "aria-label": t("sidebar.openMenu"),
2471
+ "aria-expanded": open,
2472
+ onClick: () => setOpen(true),
2473
+ children: /* @__PURE__ */ jsx17(Menu, { className: "h-5 w-5" })
2017
2474
  }
2018
2475
  )
2019
- ] })
2476
+ ] }),
2477
+ open && /* @__PURE__ */ jsx17(
2478
+ "div",
2479
+ {
2480
+ className: "fixed inset-0 z-40 bg-black/40 md:hidden",
2481
+ "aria-hidden": "true",
2482
+ onClick: () => setOpen(false)
2483
+ }
2484
+ ),
2485
+ /* @__PURE__ */ jsxs15(
2486
+ "aside",
2487
+ {
2488
+ className: cn3(
2489
+ "fixed inset-y-0 left-0 z-50 flex w-60 flex-col border-r bg-muted/30 transition-transform md:sticky md:top-0 md:h-screen md:translate-x-0",
2490
+ open ? "translate-x-0" : "-translate-x-full md:translate-x-0"
2491
+ ),
2492
+ "aria-label": t("sidebar.brand"),
2493
+ children: [
2494
+ /* @__PURE__ */ jsxs15("div", { className: "flex items-center justify-between border-b p-4", children: [
2495
+ /* @__PURE__ */ jsx17(Link4, { href: "/admin", className: "font-semibold", children: t("sidebar.brand") }),
2496
+ /* @__PURE__ */ jsx17(
2497
+ Button11,
2498
+ {
2499
+ variant: "ghost",
2500
+ size: "icon",
2501
+ className: "md:hidden",
2502
+ "aria-label": t("sidebar.closeMenu"),
2503
+ onClick: () => setOpen(false),
2504
+ children: /* @__PURE__ */ jsx17(X2, { className: "h-5 w-5" })
2505
+ }
2506
+ )
2507
+ ] }),
2508
+ siteSelector ? /* @__PURE__ */ jsx17("div", { className: "border-b", children: siteSelector }) : null,
2509
+ /* @__PURE__ */ jsx17("nav", { className: "flex-1 space-y-1 overflow-y-auto p-2", children: navItems.map((item) => {
2510
+ if (item.adminOnly && !isAdmin) return null;
2511
+ const Icon = item.icon;
2512
+ const isActive = pathname === item.href || item.href !== "/admin" && pathname?.startsWith(item.href);
2513
+ return /* @__PURE__ */ jsxs15(
2514
+ Link4,
2515
+ {
2516
+ href: item.href,
2517
+ className: cn3(
2518
+ "flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors",
2519
+ isActive ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
2520
+ ),
2521
+ children: [
2522
+ /* @__PURE__ */ jsx17(Icon, { className: "h-4 w-4" }),
2523
+ t(item.key)
2524
+ ]
2525
+ },
2526
+ item.href
2527
+ );
2528
+ }) }),
2529
+ /* @__PURE__ */ jsxs15("div", { className: "border-t p-2 space-y-1", children: [
2530
+ /* @__PURE__ */ jsxs15(
2531
+ Link4,
2532
+ {
2533
+ href: "/",
2534
+ target: "_blank",
2535
+ className: "flex items-center gap-3 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground",
2536
+ children: [
2537
+ /* @__PURE__ */ jsx17(ExternalLink, { className: "h-4 w-4" }),
2538
+ t("sidebar.viewSite")
2539
+ ]
2540
+ }
2541
+ ),
2542
+ /* @__PURE__ */ jsx17("div", { className: "px-3 py-2 text-xs text-muted-foreground truncate", children: email }),
2543
+ /* @__PURE__ */ jsxs15(
2544
+ Button11,
2545
+ {
2546
+ variant: "ghost",
2547
+ size: "sm",
2548
+ className: "w-full justify-start gap-3",
2549
+ onClick: async () => {
2550
+ await signOut();
2551
+ window.location.href = "/login";
2552
+ },
2553
+ children: [
2554
+ /* @__PURE__ */ jsx17(LogOut, { className: "h-4 w-4" }),
2555
+ t("sidebar.signOut")
2556
+ ]
2557
+ }
2558
+ )
2559
+ ] })
2560
+ ]
2561
+ }
2562
+ )
2020
2563
  ] });
2021
2564
  }
2022
2565
 
2023
2566
  // src/components/site-selector.tsx
2024
2567
  import { useRouter as useRouter3 } from "next/navigation";
2025
- import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
2568
+ import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
2026
2569
  function SiteSelector({ current, sites }) {
2027
2570
  const router = useRouter3();
2028
2571
  const t = useT();
@@ -2031,26 +2574,26 @@ function SiteSelector({ current, sites }) {
2031
2574
  document.cookie = `${ADMIN_SITE_COOKIE}=${encodeURIComponent(next)}; Path=/; Max-Age=${60 * 60 * 24 * 365}; SameSite=Lax`;
2032
2575
  router.refresh();
2033
2576
  }
2034
- return /* @__PURE__ */ jsxs15("div", { className: "px-3 py-2", children: [
2035
- /* @__PURE__ */ jsx17("label", { className: "block text-xs uppercase tracking-wide text-muted-foreground mb-1", children: t("sites.selector.label") }),
2036
- /* @__PURE__ */ jsx17(
2577
+ return /* @__PURE__ */ jsxs16("div", { className: "px-3 py-2", children: [
2578
+ /* @__PURE__ */ jsx18("label", { className: "block text-xs uppercase tracking-wide text-muted-foreground mb-1", children: t("sites.selector.label") }),
2579
+ /* @__PURE__ */ jsx18(
2037
2580
  "select",
2038
2581
  {
2039
2582
  value: current,
2040
2583
  onChange,
2041
2584
  className: "w-full rounded-md border bg-background px-2 py-1.5 text-sm",
2042
- children: sites.map((s) => /* @__PURE__ */ jsx17("option", { value: s.id, children: s.name }, s.id))
2585
+ children: sites.map((s) => /* @__PURE__ */ jsx18("option", { value: s.id, children: s.name }, s.id))
2043
2586
  }
2044
2587
  )
2045
2588
  ] });
2046
2589
  }
2047
2590
 
2048
2591
  // src/components/site-settings-form.tsx
2049
- import { useState as useState9 } from "react";
2592
+ import { useState as useState11 } from "react";
2050
2593
  import { useRouter as useRouter4 } from "next/navigation";
2051
2594
  import { setSiteSetting } from "ampless";
2052
- import { Button as Button11, Input as Input5, Label as Label4, Textarea as Textarea2 } from "@ampless/runtime/ui";
2053
- import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
2595
+ import { Button as Button12, Input as Input5, Label as Label4, Textarea as Textarea2 } from "@ampless/runtime/ui";
2596
+ import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
2054
2597
  var KEYS = [
2055
2598
  "site.name",
2056
2599
  "site.url",
@@ -2063,10 +2606,10 @@ var KEYS = [
2063
2606
  function SiteSettingsForm({ siteId, initial, fallback }) {
2064
2607
  const router = useRouter4();
2065
2608
  const t = useT();
2066
- const [values, setValues] = useState9(initial);
2067
- const [saving, setSaving] = useState9(false);
2068
- const [error, setError] = useState9(null);
2069
- const [info, setInfo] = useState9(null);
2609
+ const [values, setValues] = useState11(initial);
2610
+ const [saving, setSaving] = useState11(false);
2611
+ const [error, setError] = useState11(null);
2612
+ const [info, setInfo] = useState11(null);
2070
2613
  function update(key, value) {
2071
2614
  setValues((prev) => ({ ...prev, [key]: value }));
2072
2615
  }
@@ -2091,12 +2634,12 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
2091
2634
  setSaving(false);
2092
2635
  }
2093
2636
  }
2094
- return /* @__PURE__ */ jsxs16("form", { onSubmit: save, className: "space-y-6 max-w-xl", children: [
2095
- /* @__PURE__ */ jsxs16("fieldset", { className: "space-y-4", children: [
2096
- /* @__PURE__ */ jsx18("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: t("sites.edit.site") }),
2097
- /* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
2098
- /* @__PURE__ */ jsx18(Label4, { htmlFor: "name", children: t("common.name") }),
2099
- /* @__PURE__ */ jsx18(
2637
+ return /* @__PURE__ */ jsxs17("form", { onSubmit: save, className: "space-y-6 max-w-xl", children: [
2638
+ /* @__PURE__ */ jsxs17("fieldset", { className: "space-y-4", children: [
2639
+ /* @__PURE__ */ jsx19("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: t("sites.edit.site") }),
2640
+ /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
2641
+ /* @__PURE__ */ jsx19(Label4, { htmlFor: "name", children: t("common.name") }),
2642
+ /* @__PURE__ */ jsx19(
2100
2643
  Input5,
2101
2644
  {
2102
2645
  id: "name",
@@ -2106,9 +2649,9 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
2106
2649
  }
2107
2650
  )
2108
2651
  ] }),
2109
- /* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
2110
- /* @__PURE__ */ jsx18(Label4, { htmlFor: "url", children: t("common.url") }),
2111
- /* @__PURE__ */ jsx18(
2652
+ /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
2653
+ /* @__PURE__ */ jsx19(Label4, { htmlFor: "url", children: t("common.url") }),
2654
+ /* @__PURE__ */ jsx19(
2112
2655
  Input5,
2113
2656
  {
2114
2657
  id: "url",
@@ -2118,9 +2661,9 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
2118
2661
  }
2119
2662
  )
2120
2663
  ] }),
2121
- /* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
2122
- /* @__PURE__ */ jsx18(Label4, { htmlFor: "description", children: t("common.description") }),
2123
- /* @__PURE__ */ jsx18(
2664
+ /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
2665
+ /* @__PURE__ */ jsx19(Label4, { htmlFor: "description", children: t("common.description") }),
2666
+ /* @__PURE__ */ jsx19(
2124
2667
  Textarea2,
2125
2668
  {
2126
2669
  id: "description",
@@ -2132,11 +2675,11 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
2132
2675
  )
2133
2676
  ] })
2134
2677
  ] }),
2135
- /* @__PURE__ */ jsxs16("fieldset", { className: "space-y-4", children: [
2136
- /* @__PURE__ */ jsx18("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: t("sites.edit.media") }),
2137
- /* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
2138
- /* @__PURE__ */ jsx18(Label4, { htmlFor: "imageDisplay", children: t("sites.edit.imageDisplay") }),
2139
- /* @__PURE__ */ jsxs16(
2678
+ /* @__PURE__ */ jsxs17("fieldset", { className: "space-y-4", children: [
2679
+ /* @__PURE__ */ jsx19("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: t("sites.edit.media") }),
2680
+ /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
2681
+ /* @__PURE__ */ jsx19(Label4, { htmlFor: "imageDisplay", children: t("sites.edit.imageDisplay") }),
2682
+ /* @__PURE__ */ jsxs17(
2140
2683
  "select",
2141
2684
  {
2142
2685
  id: "imageDisplay",
@@ -2144,18 +2687,18 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
2144
2687
  value: values["media.imageDisplay"] ?? "",
2145
2688
  onChange: (e) => update("media.imageDisplay", e.target.value),
2146
2689
  children: [
2147
- /* @__PURE__ */ jsx18("option", { value: "", children: t("sites.edit.defaultPlaceholder", {
2690
+ /* @__PURE__ */ jsx19("option", { value: "", children: t("sites.edit.defaultPlaceholder", {
2148
2691
  value: fallback["media.imageDisplay"] ?? "inline"
2149
2692
  }) }),
2150
- /* @__PURE__ */ jsx18("option", { value: "inline", children: t("sites.edit.imageDisplayInline") }),
2151
- /* @__PURE__ */ jsx18("option", { value: "lightbox", children: t("sites.edit.imageDisplayLightbox") })
2693
+ /* @__PURE__ */ jsx19("option", { value: "inline", children: t("sites.edit.imageDisplayInline") }),
2694
+ /* @__PURE__ */ jsx19("option", { value: "lightbox", children: t("sites.edit.imageDisplayLightbox") })
2152
2695
  ]
2153
2696
  }
2154
2697
  )
2155
2698
  ] }),
2156
- /* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
2157
- /* @__PURE__ */ jsx18(Label4, { htmlFor: "imageMaxWidth", children: t("sites.edit.imageMaxWidth") }),
2158
- /* @__PURE__ */ jsx18(
2699
+ /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
2700
+ /* @__PURE__ */ jsx19(Label4, { htmlFor: "imageMaxWidth", children: t("sites.edit.imageMaxWidth") }),
2701
+ /* @__PURE__ */ jsx19(
2159
2702
  Input5,
2160
2703
  {
2161
2704
  id: "imageMaxWidth",
@@ -2166,11 +2709,11 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
2166
2709
  )
2167
2710
  ] })
2168
2711
  ] }),
2169
- /* @__PURE__ */ jsxs16("fieldset", { className: "space-y-4", children: [
2170
- /* @__PURE__ */ jsx18("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: t("sites.edit.dateDisplay") }),
2171
- /* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
2172
- /* @__PURE__ */ jsx18(Label4, { htmlFor: "dateFormat", children: t("sites.edit.dateFormat") }),
2173
- /* @__PURE__ */ jsxs16(
2712
+ /* @__PURE__ */ jsxs17("fieldset", { className: "space-y-4", children: [
2713
+ /* @__PURE__ */ jsx19("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: t("sites.edit.dateDisplay") }),
2714
+ /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
2715
+ /* @__PURE__ */ jsx19(Label4, { htmlFor: "dateFormat", children: t("sites.edit.dateFormat") }),
2716
+ /* @__PURE__ */ jsxs17(
2174
2717
  "select",
2175
2718
  {
2176
2719
  id: "dateFormat",
@@ -2178,19 +2721,19 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
2178
2721
  value: values["dateFormat"] ?? "",
2179
2722
  onChange: (e) => update("dateFormat", e.target.value),
2180
2723
  children: [
2181
- /* @__PURE__ */ jsx18("option", { value: "", children: t("sites.edit.defaultPlaceholder", {
2724
+ /* @__PURE__ */ jsx19("option", { value: "", children: t("sites.edit.defaultPlaceholder", {
2182
2725
  value: fallback["dateFormat"] ?? "iso"
2183
2726
  }) }),
2184
- /* @__PURE__ */ jsx18("option", { value: "iso", children: t("sites.edit.dateFormatIso") }),
2185
- /* @__PURE__ */ jsx18("option", { value: "long", children: t("sites.edit.dateFormatLong") }),
2186
- /* @__PURE__ */ jsx18("option", { value: "locale", children: t("sites.edit.dateFormatLocale") })
2727
+ /* @__PURE__ */ jsx19("option", { value: "iso", children: t("sites.edit.dateFormatIso") }),
2728
+ /* @__PURE__ */ jsx19("option", { value: "long", children: t("sites.edit.dateFormatLong") }),
2729
+ /* @__PURE__ */ jsx19("option", { value: "locale", children: t("sites.edit.dateFormatLocale") })
2187
2730
  ]
2188
2731
  }
2189
2732
  )
2190
2733
  ] }),
2191
- /* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
2192
- /* @__PURE__ */ jsx18(Label4, { htmlFor: "timezone", children: t("sites.edit.timezone") }),
2193
- /* @__PURE__ */ jsx18(
2734
+ /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
2735
+ /* @__PURE__ */ jsx19(Label4, { htmlFor: "timezone", children: t("sites.edit.timezone") }),
2736
+ /* @__PURE__ */ jsx19(
2194
2737
  Input5,
2195
2738
  {
2196
2739
  id: "timezone",
@@ -2201,14 +2744,14 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
2201
2744
  )
2202
2745
  ] })
2203
2746
  ] }),
2204
- info && /* @__PURE__ */ jsx18("p", { className: "text-sm text-muted-foreground", children: info }),
2205
- error && /* @__PURE__ */ jsx18("p", { className: "text-sm text-destructive", children: error }),
2206
- /* @__PURE__ */ jsx18(Button11, { type: "submit", disabled: saving, children: saving ? t("common.saving") : t("sites.edit.saveButton") })
2747
+ info && /* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: info }),
2748
+ error && /* @__PURE__ */ jsx19("p", { className: "text-sm text-destructive", children: error }),
2749
+ /* @__PURE__ */ jsx19(Button12, { type: "submit", disabled: saving, children: saving ? t("common.saving") : t("sites.edit.saveButton") })
2207
2750
  ] });
2208
2751
  }
2209
2752
 
2210
2753
  // src/components/theme-settings-form.tsx
2211
- import { useState as useState10 } from "react";
2754
+ import { useState as useState12 } from "react";
2212
2755
  import { useRouter as useRouter5 } from "next/navigation";
2213
2756
  import {
2214
2757
  setSiteSetting as setSiteSetting2,
@@ -2219,8 +2762,8 @@ import {
2219
2762
  parseLinkList,
2220
2763
  stringifyLinkList
2221
2764
  } from "ampless";
2222
- import { Button as Button12, Input as Input6, Label as Label5 } from "@ampless/runtime/ui";
2223
- import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
2765
+ import { Button as Button13, Input as Input6, Label as Label5 } from "@ampless/runtime/ui";
2766
+ import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
2224
2767
  var CACHE_REBUILD_DELAY_MS = 8e3;
2225
2768
  function ThemeSettingsForm({
2226
2769
  siteId,
@@ -2232,14 +2775,14 @@ function ThemeSettingsForm({
2232
2775
  const router = useRouter5();
2233
2776
  const t = useT();
2234
2777
  const locale = useLocale();
2235
- const [state, setState] = useState10({ values: initial, touched: {} });
2236
- const [pendingTheme, setPendingTheme] = useState10(activeTheme);
2237
- const [optimisticActive, setOptimisticActive] = useState10(activeTheme);
2238
- const [saving, setSaving] = useState10(false);
2239
- const [switching, setSwitching] = useState10(false);
2240
- const [error, setError] = useState10(null);
2241
- const [info, setInfo] = useState10(null);
2242
- const [invalid, setInvalid] = useState10({});
2778
+ const [state, setState] = useState12({ values: initial, touched: {} });
2779
+ const [pendingTheme, setPendingTheme] = useState12(activeTheme);
2780
+ const [optimisticActive, setOptimisticActive] = useState12(activeTheme);
2781
+ const [saving, setSaving] = useState12(false);
2782
+ const [switching, setSwitching] = useState12(false);
2783
+ const [error, setError] = useState12(null);
2784
+ const [info, setInfo] = useState12(null);
2785
+ const [invalid, setInvalid] = useState12({});
2243
2786
  function update(key, value) {
2244
2787
  setState((prev) => ({
2245
2788
  values: { ...prev.values, [key]: value },
@@ -2325,23 +2868,23 @@ function ThemeSettingsForm({
2325
2868
  }
2326
2869
  }
2327
2870
  const groups = groupFields(manifest.fields);
2328
- return /* @__PURE__ */ jsxs17("div", { className: "space-y-8", children: [
2329
- /* @__PURE__ */ jsxs17("form", { onSubmit: switchTheme, className: "max-w-xl space-y-3 rounded-md border p-4", children: [
2330
- /* @__PURE__ */ jsxs17("div", { className: "space-y-1", children: [
2331
- /* @__PURE__ */ jsx19(Label5, { htmlFor: "active-theme", className: "text-sm font-medium", children: t("theme.activeLabel") }),
2332
- /* @__PURE__ */ jsxs17("p", { className: "text-xs text-muted-foreground", children: [
2871
+ return /* @__PURE__ */ jsxs18("div", { className: "space-y-8", children: [
2872
+ /* @__PURE__ */ jsxs18("form", { onSubmit: switchTheme, className: "max-w-xl space-y-3 rounded-md border p-4", children: [
2873
+ /* @__PURE__ */ jsxs18("div", { className: "space-y-1", children: [
2874
+ /* @__PURE__ */ jsx20(Label5, { htmlFor: "active-theme", className: "text-sm font-medium", children: t("theme.activeLabel") }),
2875
+ /* @__PURE__ */ jsxs18("p", { className: "text-xs text-muted-foreground", children: [
2333
2876
  t("theme.currentlyActive", { theme: optimisticActive }),
2334
2877
  optimisticActive !== activeTheme && t("theme.propagating")
2335
2878
  ] })
2336
2879
  ] }),
2337
- /* @__PURE__ */ jsx19(
2880
+ /* @__PURE__ */ jsx20(
2338
2881
  "select",
2339
2882
  {
2340
2883
  id: "active-theme",
2341
2884
  className: "w-full rounded-md border bg-background px-2 py-1.5 text-sm",
2342
2885
  value: pendingTheme,
2343
2886
  onChange: (e) => setPendingTheme(e.target.value),
2344
- children: themeOptions.map((opt) => /* @__PURE__ */ jsxs17("option", { value: opt.value, children: [
2887
+ children: themeOptions.map((opt) => /* @__PURE__ */ jsxs18("option", { value: opt.value, children: [
2345
2888
  resolveLocalized(opt.label, locale),
2346
2889
  " (",
2347
2890
  opt.value,
@@ -2351,10 +2894,10 @@ function ThemeSettingsForm({
2351
2894
  ),
2352
2895
  (() => {
2353
2896
  const desc = themeOptions.find((o) => o.value === pendingTheme)?.description;
2354
- return desc ? /* @__PURE__ */ jsx19("p", { className: "text-xs text-muted-foreground", children: resolveLocalized(desc, locale) }) : null;
2897
+ return desc ? /* @__PURE__ */ jsx20("p", { className: "text-xs text-muted-foreground", children: resolveLocalized(desc, locale) }) : null;
2355
2898
  })(),
2356
- /* @__PURE__ */ jsx19(
2357
- Button12,
2899
+ /* @__PURE__ */ jsx20(
2900
+ Button13,
2358
2901
  {
2359
2902
  type: "submit",
2360
2903
  disabled: switching || pendingTheme === optimisticActive,
@@ -2363,9 +2906,9 @@ function ThemeSettingsForm({
2363
2906
  }
2364
2907
  )
2365
2908
  ] }),
2366
- /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
2367
- /* @__PURE__ */ jsx19(Label5, { className: "text-sm font-medium", children: t("theme.previewLabel") }),
2368
- /* @__PURE__ */ jsx19(
2909
+ /* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
2910
+ /* @__PURE__ */ jsx20(Label5, { className: "text-sm font-medium", children: t("theme.previewLabel") }),
2911
+ /* @__PURE__ */ jsx20(
2369
2912
  "iframe",
2370
2913
  {
2371
2914
  src: `/?previewTheme=${encodeURIComponent(pendingTheme)}`,
@@ -2374,19 +2917,19 @@ function ThemeSettingsForm({
2374
2917
  },
2375
2918
  pendingTheme
2376
2919
  ),
2377
- /* @__PURE__ */ jsx19("p", { className: "text-xs text-muted-foreground", children: t("theme.previewHint") })
2920
+ /* @__PURE__ */ jsx20("p", { className: "text-xs text-muted-foreground", children: t("theme.previewHint") })
2378
2921
  ] }),
2379
- /* @__PURE__ */ jsxs17("form", { onSubmit: save, className: "max-w-xl space-y-6", children: [
2380
- /* @__PURE__ */ jsxs17("div", { children: [
2381
- /* @__PURE__ */ jsx19("h2", { className: "text-lg font-semibold", children: t("theme.customizationHeading", {
2922
+ /* @__PURE__ */ jsxs18("form", { onSubmit: save, className: "max-w-xl space-y-6", children: [
2923
+ /* @__PURE__ */ jsxs18("div", { children: [
2924
+ /* @__PURE__ */ jsx20("h2", { className: "text-lg font-semibold", children: t("theme.customizationHeading", {
2382
2925
  theme: resolveLocalized(manifest.label, locale)
2383
2926
  }) }),
2384
- manifest.description && /* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: resolveLocalized(manifest.description, locale) }),
2385
- /* @__PURE__ */ jsx19("p", { className: "mt-1 text-xs text-muted-foreground", children: t("theme.customizationHint") })
2927
+ manifest.description && /* @__PURE__ */ jsx20("p", { className: "text-sm text-muted-foreground", children: resolveLocalized(manifest.description, locale) }),
2928
+ /* @__PURE__ */ jsx20("p", { className: "mt-1 text-xs text-muted-foreground", children: t("theme.customizationHint") })
2386
2929
  ] }),
2387
- groups.map(({ key, name, fields }) => /* @__PURE__ */ jsxs17("fieldset", { className: "space-y-4", children: [
2388
- /* @__PURE__ */ jsx19("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: resolveLocalized(name, locale) }),
2389
- fields.map((field) => /* @__PURE__ */ jsx19(
2930
+ groups.map(({ key, name, fields }) => /* @__PURE__ */ jsxs18("fieldset", { className: "space-y-4", children: [
2931
+ /* @__PURE__ */ jsx20("legend", { className: "text-sm font-medium uppercase tracking-wide text-muted-foreground", children: resolveLocalized(name, locale) }),
2932
+ fields.map((field) => /* @__PURE__ */ jsx20(
2390
2933
  FieldRow,
2391
2934
  {
2392
2935
  field,
@@ -2397,9 +2940,9 @@ function ThemeSettingsForm({
2397
2940
  field.key
2398
2941
  ))
2399
2942
  ] }, key)),
2400
- info && /* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: info }),
2401
- error && /* @__PURE__ */ jsx19("p", { className: "text-sm text-destructive", children: error }),
2402
- /* @__PURE__ */ jsx19(Button12, { type: "submit", disabled: saving, children: saving ? t("theme.saving") : t("theme.saveButton") })
2943
+ info && /* @__PURE__ */ jsx20("p", { className: "text-sm text-muted-foreground", children: info }),
2944
+ error && /* @__PURE__ */ jsx20("p", { className: "text-sm text-destructive", children: error }),
2945
+ /* @__PURE__ */ jsx20(Button13, { type: "submit", disabled: saving, children: saving ? t("theme.saving") : t("theme.saveButton") })
2403
2946
  ] })
2404
2947
  ] });
2405
2948
  }
@@ -2421,16 +2964,16 @@ function FieldRow({ field, value, invalid, onChange }) {
2421
2964
  const t = useT();
2422
2965
  const locale = useLocale();
2423
2966
  const id = `theme-${field.key}`;
2424
- const labelEl = /* @__PURE__ */ jsx19(Label5, { htmlFor: id, className: invalid ? "text-destructive" : void 0, children: resolveLocalized(field.label, locale) });
2425
- const description = field.description ? /* @__PURE__ */ jsx19("p", { className: "text-xs text-muted-foreground", children: resolveLocalized(field.description, locale) }) : null;
2967
+ const labelEl = /* @__PURE__ */ jsx20(Label5, { htmlFor: id, className: invalid ? "text-destructive" : void 0, children: resolveLocalized(field.label, locale) });
2968
+ const description = field.description ? /* @__PURE__ */ jsx20("p", { className: "text-xs text-muted-foreground", children: resolveLocalized(field.description, locale) }) : null;
2426
2969
  switch (field.type) {
2427
2970
  case "color":
2428
- return /* @__PURE__ */ jsx19(ColorField, { field, id, labelEl, description, value, invalid, onChange });
2971
+ return /* @__PURE__ */ jsx20(ColorField, { field, id, labelEl, description, value, invalid, onChange });
2429
2972
  case "length":
2430
- return /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
2973
+ return /* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
2431
2974
  labelEl,
2432
2975
  description,
2433
- /* @__PURE__ */ jsx19(
2976
+ /* @__PURE__ */ jsx20(
2434
2977
  Input6,
2435
2978
  {
2436
2979
  id,
@@ -2441,14 +2984,14 @@ function FieldRow({ field, value, invalid, onChange }) {
2441
2984
  className: "font-mono text-xs"
2442
2985
  }
2443
2986
  ),
2444
- /* @__PURE__ */ jsx19("p", { className: "text-xs text-muted-foreground", children: t("theme.lengthHelp") })
2987
+ /* @__PURE__ */ jsx20("p", { className: "text-xs text-muted-foreground", children: t("theme.lengthHelp") })
2445
2988
  ] });
2446
2989
  case "select":
2447
2990
  case "fontFamily":
2448
- return /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
2991
+ return /* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
2449
2992
  labelEl,
2450
2993
  description,
2451
- /* @__PURE__ */ jsxs17(
2994
+ /* @__PURE__ */ jsxs18(
2452
2995
  "select",
2453
2996
  {
2454
2997
  id,
@@ -2457,17 +3000,17 @@ function FieldRow({ field, value, invalid, onChange }) {
2457
3000
  onChange: (e) => onChange(e.target.value),
2458
3001
  "aria-invalid": invalid,
2459
3002
  children: [
2460
- /* @__PURE__ */ jsx19("option", { value: "", children: t("common.default") }),
2461
- field.options.map((opt) => /* @__PURE__ */ jsx19("option", { value: opt.value, children: resolveLocalized(opt.label, locale) }, opt.value))
3003
+ /* @__PURE__ */ jsx20("option", { value: "", children: t("common.default") }),
3004
+ field.options.map((opt) => /* @__PURE__ */ jsx20("option", { value: opt.value, children: resolveLocalized(opt.label, locale) }, opt.value))
2462
3005
  ]
2463
3006
  }
2464
3007
  )
2465
3008
  ] });
2466
3009
  case "image":
2467
- return /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
3010
+ return /* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
2468
3011
  labelEl,
2469
3012
  description,
2470
- /* @__PURE__ */ jsx19(
3013
+ /* @__PURE__ */ jsx20(
2471
3014
  Input6,
2472
3015
  {
2473
3016
  id,
@@ -2479,10 +3022,10 @@ function FieldRow({ field, value, invalid, onChange }) {
2479
3022
  )
2480
3023
  ] });
2481
3024
  case "text":
2482
- return /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
3025
+ return /* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
2483
3026
  labelEl,
2484
3027
  description,
2485
- /* @__PURE__ */ jsx19(
3028
+ /* @__PURE__ */ jsx20(
2486
3029
  Input6,
2487
3030
  {
2488
3031
  id,
@@ -2495,7 +3038,7 @@ function FieldRow({ field, value, invalid, onChange }) {
2495
3038
  )
2496
3039
  ] });
2497
3040
  case "linkList":
2498
- return /* @__PURE__ */ jsx19(
3041
+ return /* @__PURE__ */ jsx20(
2499
3042
  LinkListField,
2500
3043
  {
2501
3044
  field,
@@ -2520,7 +3063,7 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
2520
3063
  if (items.length >= max) return;
2521
3064
  commit([...items, { label: "", url: "" }]);
2522
3065
  }
2523
- function remove2(idx) {
3066
+ function remove3(idx) {
2524
3067
  commit(items.filter((_, i) => i !== idx));
2525
3068
  }
2526
3069
  function move(idx, delta) {
@@ -2531,16 +3074,16 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
2531
3074
  next.splice(target, 0, moved);
2532
3075
  commit(next);
2533
3076
  }
2534
- return /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
3077
+ return /* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
2535
3078
  labelEl,
2536
3079
  description,
2537
- /* @__PURE__ */ jsxs17("div", { className: "space-y-2 rounded-md border bg-muted/20 p-3", children: [
2538
- items.length === 0 && /* @__PURE__ */ jsx19("p", { className: "text-xs text-muted-foreground", children: "No links yet." }),
3080
+ /* @__PURE__ */ jsxs18("div", { className: "space-y-2 rounded-md border bg-muted/20 p-3", children: [
3081
+ items.length === 0 && /* @__PURE__ */ jsx20("p", { className: "text-xs text-muted-foreground", children: "No links yet." }),
2539
3082
  items.map((item, idx) => {
2540
3083
  const isTagRef = /^tag:/.test(item.url.trim());
2541
- return /* @__PURE__ */ jsxs17("div", { className: "flex flex-wrap items-start gap-2", children: [
2542
- /* @__PURE__ */ jsxs17("div", { className: "grid flex-1 grid-cols-1 gap-2 sm:grid-cols-2", children: [
2543
- /* @__PURE__ */ jsx19(
3084
+ return /* @__PURE__ */ jsxs18("div", { className: "flex flex-wrap items-start gap-2", children: [
3085
+ /* @__PURE__ */ jsxs18("div", { className: "grid flex-1 grid-cols-1 gap-2 sm:grid-cols-2", children: [
3086
+ /* @__PURE__ */ jsx20(
2544
3087
  Input6,
2545
3088
  {
2546
3089
  value: item.label,
@@ -2548,7 +3091,7 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
2548
3091
  onChange: (e) => update(idx, { label: e.target.value })
2549
3092
  }
2550
3093
  ),
2551
- /* @__PURE__ */ jsx19(
3094
+ /* @__PURE__ */ jsx20(
2552
3095
  Input6,
2553
3096
  {
2554
3097
  value: item.url,
@@ -2558,9 +3101,9 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
2558
3101
  }
2559
3102
  )
2560
3103
  ] }),
2561
- /* @__PURE__ */ jsxs17("div", { className: "flex shrink-0 items-center gap-1", children: [
2562
- /* @__PURE__ */ jsx19(
2563
- Button12,
3104
+ /* @__PURE__ */ jsxs18("div", { className: "flex shrink-0 items-center gap-1", children: [
3105
+ /* @__PURE__ */ jsx20(
3106
+ Button13,
2564
3107
  {
2565
3108
  type: "button",
2566
3109
  variant: "ghost",
@@ -2571,8 +3114,8 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
2571
3114
  children: "\u2191"
2572
3115
  }
2573
3116
  ),
2574
- /* @__PURE__ */ jsx19(
2575
- Button12,
3117
+ /* @__PURE__ */ jsx20(
3118
+ Button13,
2576
3119
  {
2577
3120
  type: "button",
2578
3121
  variant: "ghost",
@@ -2583,13 +3126,13 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
2583
3126
  children: "\u2193"
2584
3127
  }
2585
3128
  ),
2586
- /* @__PURE__ */ jsx19(
2587
- Button12,
3129
+ /* @__PURE__ */ jsx20(
3130
+ Button13,
2588
3131
  {
2589
3132
  type: "button",
2590
3133
  variant: "ghost",
2591
3134
  size: "icon",
2592
- onClick: () => remove2(idx),
3135
+ onClick: () => remove3(idx),
2593
3136
  "aria-label": "Remove",
2594
3137
  children: "\xD7"
2595
3138
  }
@@ -2597,8 +3140,8 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
2597
3140
  ] })
2598
3141
  ] }, idx);
2599
3142
  }),
2600
- /* @__PURE__ */ jsx19(
2601
- Button12,
3143
+ /* @__PURE__ */ jsx20(
3144
+ Button13,
2602
3145
  {
2603
3146
  type: "button",
2604
3147
  variant: "outline",
@@ -2608,9 +3151,9 @@ function LinkListField({ field, labelEl, description, value, onChange }) {
2608
3151
  children: "+ Add link"
2609
3152
  }
2610
3153
  ),
2611
- /* @__PURE__ */ jsxs17("p", { className: "text-xs text-muted-foreground", children: [
3154
+ /* @__PURE__ */ jsxs18("p", { className: "text-xs text-muted-foreground", children: [
2612
3155
  "Tip: use ",
2613
- /* @__PURE__ */ jsx19("code", { children: "tag:<name>" }),
3156
+ /* @__PURE__ */ jsx20("code", { children: "tag:<name>" }),
2614
3157
  " as a URL to render a list of posts with that tag instead of a single link."
2615
3158
  ] })
2616
3159
  ] })
@@ -2627,11 +3170,11 @@ function ColorField({
2627
3170
  }) {
2628
3171
  const effective = value || field.default;
2629
3172
  const hex = useColorAsHex(effective);
2630
- return /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
3173
+ return /* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
2631
3174
  labelEl,
2632
3175
  description,
2633
- /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", children: [
2634
- /* @__PURE__ */ jsx19(
3176
+ /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-2", children: [
3177
+ /* @__PURE__ */ jsx20(
2635
3178
  "input",
2636
3179
  {
2637
3180
  type: "color",
@@ -2641,7 +3184,7 @@ function ColorField({
2641
3184
  "aria-label": `${field.label} swatch`
2642
3185
  }
2643
3186
  ),
2644
- /* @__PURE__ */ jsx19(
3187
+ /* @__PURE__ */ jsx20(
2645
3188
  Input6,
2646
3189
  {
2647
3190
  id,
@@ -2653,8 +3196,8 @@ function ColorField({
2653
3196
  }
2654
3197
  )
2655
3198
  ] }),
2656
- /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", children: [
2657
- /* @__PURE__ */ jsx19(
3199
+ /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-2", children: [
3200
+ /* @__PURE__ */ jsx20(
2658
3201
  "span",
2659
3202
  {
2660
3203
  className: "inline-block h-4 w-4 rounded border",
@@ -2662,7 +3205,7 @@ function ColorField({
2662
3205
  "aria-hidden": true
2663
3206
  }
2664
3207
  ),
2665
- /* @__PURE__ */ jsx19("code", { className: "text-xs text-muted-foreground", children: effective })
3208
+ /* @__PURE__ */ jsx20("code", { className: "text-xs text-muted-foreground", children: effective })
2666
3209
  ] })
2667
3210
  ] });
2668
3211
  }