@particle-academy/fancy-cms-ui 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/editor.cjs CHANGED
@@ -242,6 +242,17 @@ var DEFAULT_BREAKPOINTS = {
242
242
  md: 768,
243
243
  lg: 1024
244
244
  };
245
+ function emptyDoc(id, slug = "/") {
246
+ return {
247
+ id,
248
+ seq: 0,
249
+ meta: { title: "Untitled", slug, scrollMode: "smooth" },
250
+ theme: { name: "default" },
251
+ breakpoints: ["base", "md", "lg"],
252
+ sections: [],
253
+ nodes: {}
254
+ };
255
+ }
245
256
 
246
257
  // src/render/css.ts
247
258
  var selectorFor = (id) => `[data-cms="${id}"]`;
@@ -677,6 +688,8 @@ var input = {
677
688
  fontSize: 13
678
689
  };
679
690
  function Inspector({ doc, selection, apply }) {
691
+ const uid = react.useId();
692
+ const fieldId = (key) => `${uid}-${key}`;
680
693
  const node = selection ? doc.nodes[selection] : null;
681
694
  if (!node) {
682
695
  return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 16, color: "var(--fcms-muted)", fontFamily: "system-ui, sans-serif", fontSize: 13 }, children: "Select an element to edit." });
@@ -688,10 +701,12 @@ function Inspector({ doc, selection, apply }) {
688
701
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 13, fontWeight: 600, color: "var(--fcms-fg)" }, children: node.type }),
689
702
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "var(--fcms-muted)", marginBottom: 8 }, children: node.id }),
690
703
  node.type === "text" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
691
- /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Content" }),
704
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("content"), children: "Content" }),
692
705
  /* @__PURE__ */ jsxRuntime.jsx(
693
706
  "textarea",
694
707
  {
708
+ id: fieldId("content"),
709
+ "data-cms-field": "content",
695
710
  style: { ...input, minHeight: 64, resize: "vertical" },
696
711
  value: typeof node.props.content === "string" ? node.props.content : "",
697
712
  onChange: (e) => setProp("content", e.target.value)
@@ -699,60 +714,72 @@ function Inspector({ doc, selection, apply }) {
699
714
  )
700
715
  ] }) : null,
701
716
  node.type === "image" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
702
- /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Source" }),
717
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("src"), children: "Source" }),
703
718
  /* @__PURE__ */ jsxRuntime.jsx(
704
719
  "input",
705
720
  {
721
+ id: fieldId("src"),
722
+ "data-cms-field": "src",
706
723
  style: input,
707
724
  value: typeof node.props.src === "string" ? node.props.src : "",
708
725
  onChange: (e) => setProp("src", e.target.value)
709
726
  }
710
727
  )
711
728
  ] }) : null,
712
- /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Background" }),
729
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("background"), children: "Background" }),
713
730
  /* @__PURE__ */ jsxRuntime.jsx(
714
731
  "input",
715
732
  {
733
+ id: fieldId("background"),
734
+ "data-cms-field": "background",
716
735
  style: input,
717
736
  placeholder: "#ffffff | gradient | url(\u2026)",
718
737
  value: s.background ?? "",
719
738
  onChange: (e) => setStyle({ background: e.target.value })
720
739
  }
721
740
  ),
722
- /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Text color" }),
741
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("color"), children: "Text color" }),
723
742
  /* @__PURE__ */ jsxRuntime.jsx(
724
743
  "input",
725
744
  {
745
+ id: fieldId("color"),
746
+ "data-cms-field": "color",
726
747
  style: input,
727
748
  placeholder: "#0f172a",
728
749
  value: s.color ?? "",
729
750
  onChange: (e) => setStyle({ color: e.target.value })
730
751
  }
731
752
  ),
732
- /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Font size (px)" }),
753
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("fontSize"), children: "Font size (px)" }),
733
754
  /* @__PURE__ */ jsxRuntime.jsx(
734
755
  "input",
735
756
  {
757
+ id: fieldId("fontSize"),
758
+ "data-cms-field": "fontSize",
736
759
  type: "number",
737
760
  style: input,
738
761
  value: s.fontSize?.value ?? "",
739
762
  onChange: (e) => setStyle({ fontSize: { value: Number(e.target.value) || 0, unit: "px" } })
740
763
  }
741
764
  ),
742
- /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Padding (px)" }),
765
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("padding"), children: "Padding (px)" }),
743
766
  /* @__PURE__ */ jsxRuntime.jsx(
744
767
  "input",
745
768
  {
769
+ id: fieldId("padding"),
770
+ "data-cms-field": "padding",
746
771
  type: "number",
747
772
  style: input,
748
773
  value: s.padding?.value ?? "",
749
774
  onChange: (e) => setStyle({ padding: { value: Number(e.target.value) || 0, unit: "px" } })
750
775
  }
751
776
  ),
752
- /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, children: "Opacity" }),
777
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: label, htmlFor: fieldId("opacity"), children: "Opacity" }),
753
778
  /* @__PURE__ */ jsxRuntime.jsx(
754
779
  "input",
755
780
  {
781
+ id: fieldId("opacity"),
782
+ "data-cms-field": "opacity",
756
783
  type: "number",
757
784
  step: "0.1",
758
785
  min: "0",
@@ -764,8 +791,82 @@ function Inspector({ doc, selection, apply }) {
764
791
  )
765
792
  ] });
766
793
  }
794
+
795
+ // src/editor/insert.ts
796
+ var CONTAINER_TYPES = /* @__PURE__ */ new Set([
797
+ "section",
798
+ "frame",
799
+ "stack",
800
+ "grid",
801
+ "shape",
802
+ "card",
803
+ "device",
804
+ "repeater"
805
+ ]);
806
+ var ADD_DEFAULTS = {
807
+ text: { type: "text", props: { content: "New text" }, style: { color: "inherit" } },
808
+ heading: { type: "heading", props: { content: "New heading" }, style: { fontSize: { value: 28, unit: "px" }, fontWeight: 700 } },
809
+ button: { type: "button", props: { label: "Button", href: "#", variant: "primary" }, style: {} },
810
+ link: { type: "link", props: { content: "link text", href: "#" }, style: { color: "#7c3aed" } },
811
+ image: { type: "image", props: { src: "", alt: "" }, style: {} },
812
+ box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } },
813
+ stack: { type: "stack", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "stack" },
814
+ grid: { type: "grid", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "grid" },
815
+ card: { type: "card", props: {}, style: { padding: { value: 16, unit: "px" }, radius: { value: 12, unit: "px" }, border: "1px solid #e2e8f0" } },
816
+ callout: { type: "callout", props: { content: "Heads up \u2014 this is a callout.", variant: "info" }, style: {} },
817
+ divider: { type: "divider", props: {}, style: {} },
818
+ code: { type: "code", props: { content: "npm install @particle-academy/react-fancy", lang: "bash" }, style: {} },
819
+ richtext: { type: "richtext", props: { html: "<p>Rich <strong>text</strong> with <em>inline</em> formatting.</p>" }, style: {} },
820
+ repeater: { type: "repeater", props: { items: "" }, style: { gap: { value: 12, unit: "px" } }, layout: "stack" }
821
+ };
822
+ var ADD_MENU = [
823
+ { kind: "heading", label: "Heading" },
824
+ { kind: "text", label: "Text" },
825
+ { kind: "button", label: "Button" },
826
+ { kind: "link", label: "Link" },
827
+ { kind: "image", label: "Image" },
828
+ { kind: "card", label: "Card" },
829
+ { kind: "callout", label: "Callout" },
830
+ { kind: "stack", label: "Stack" },
831
+ { kind: "grid", label: "Grid" },
832
+ { kind: "box", label: "Box" },
833
+ { kind: "divider", label: "Divider" },
834
+ { kind: "code", label: "Code" },
835
+ { kind: "richtext", label: "Rich text" },
836
+ { kind: "repeater", label: "Repeater" }
837
+ ];
838
+ function resolveInsertParent(doc, selectedId) {
839
+ const selected = selectedId ? doc.nodes[selectedId] : null;
840
+ if (selected) {
841
+ return CONTAINER_TYPES.has(selected.type) ? selected.id : selected.parent;
842
+ }
843
+ return doc.sections[doc.sections.length - 1] ?? null;
844
+ }
845
+ function buildInsertOp(doc, kind, selectedId, targetId) {
846
+ const parent = resolveInsertParent(doc, selectedId);
847
+ const siblings = childrenOf(doc, parent);
848
+ const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
849
+ const id = `n${doc.seq + 1}-${Math.floor(performance.now())}`;
850
+ const def = ADD_DEFAULTS[kind];
851
+ return {
852
+ id,
853
+ op: {
854
+ t: "insert_node",
855
+ node: {
856
+ id,
857
+ type: def.type,
858
+ parent,
859
+ order,
860
+ layout: def.layout,
861
+ props: { ...def.props },
862
+ style: { base: { ...def.style } }
863
+ }
864
+ }
865
+ };
866
+ }
767
867
  function Editor({ defaultValue, onChange, registry, data }) {
768
- const ed = useEditor(defaultValue);
868
+ const initial = react.useMemo(() => defaultValue ?? emptyDoc("untitled"), [defaultValue]);
869
+ const ed = useEditor(initial);
769
870
  const first = react.useRef(true);
770
871
  const onChangeRef = useLatestRef(onChange);
771
872
  react.useEffect(() => {
@@ -827,15 +928,90 @@ function Toolbar({ ed }) {
827
928
  },
828
929
  children: [
829
930
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: ed.canUndo ? 1 : 0.4 }, disabled: !ed.canUndo, onClick: ed.undo, children: "Undo" }),
830
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: ed.canRedo ? 1 : 0.4 }, disabled: !ed.canRedo, onClick: ed.redo, children: "Redo" })
931
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...btn, opacity: ed.canRedo ? 1 : 0.4 }, disabled: !ed.canRedo, onClick: ed.redo, children: "Redo" }),
932
+ /* @__PURE__ */ jsxRuntime.jsx(AddElementMenu, { ed, btn })
831
933
  ]
832
934
  }
833
935
  );
834
936
  }
835
- var DARK_VARS = "color-scheme:dark;--fcms-bg:#0b0f19;--fcms-fg:#e5e7eb;--fcms-muted:#94a3b8;--fcms-border:#27272a;--fcms-canvas:#0f141f;--fcms-input-bg:#111827;--fcms-row-fg:#cbd5e1;--fcms-sel-bg:color-mix(in oklab, #8b5cf6 24%, transparent);--fcms-sel-fg:#ddd6fe;";
836
- var LIGHT_VARS = "color-scheme:light;--fcms-bg:#ffffff;--fcms-fg:#0f172a;--fcms-muted:#64748b;--fcms-border:#e2e8f0;--fcms-canvas:#f8fafc;--fcms-input-bg:#ffffff;--fcms-row-fg:#334155;--fcms-sel-bg:#ede9fe;--fcms-sel-fg:#5b21b6;";
937
+ function AddElementMenu({ ed, btn }) {
938
+ const [open, setOpen] = react.useState(false);
939
+ react.useEffect(() => {
940
+ if (!open) return;
941
+ const close = () => setOpen(false);
942
+ const onKey = (e) => e.key === "Escape" && setOpen(false);
943
+ window.addEventListener("click", close);
944
+ window.addEventListener("keydown", onKey);
945
+ return () => {
946
+ window.removeEventListener("click", close);
947
+ window.removeEventListener("keydown", onKey);
948
+ };
949
+ }, [open]);
950
+ const add = (kind) => {
951
+ const { op, id } = buildInsertOp(ed.state.doc, kind, ed.state.selection);
952
+ ed.apply(op);
953
+ ed.select(id);
954
+ setOpen(false);
955
+ };
956
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", marginLeft: "auto" }, onClick: (e) => e.stopPropagation(), children: [
957
+ /* @__PURE__ */ jsxRuntime.jsx(
958
+ "button",
959
+ {
960
+ type: "button",
961
+ style: { ...btn, borderColor: "var(--fcms-accent, #7c3aed)" },
962
+ "aria-haspopup": "menu",
963
+ "aria-expanded": open,
964
+ "data-cms-add-trigger": "",
965
+ onClick: () => setOpen((v) => !v),
966
+ children: "+ Add"
967
+ }
968
+ ),
969
+ open && /* @__PURE__ */ jsxRuntime.jsx(
970
+ "div",
971
+ {
972
+ role: "menu",
973
+ "data-cms-add-menu": "",
974
+ style: {
975
+ position: "absolute",
976
+ top: "calc(100% + 4px)",
977
+ left: 0,
978
+ zIndex: 20,
979
+ minWidth: 150,
980
+ maxHeight: 280,
981
+ overflowY: "auto",
982
+ padding: 4,
983
+ borderRadius: 8,
984
+ border: "1px solid var(--fcms-border)",
985
+ background: "var(--fcms-bg)",
986
+ boxShadow: "0 12px 32px rgba(0,0,0,.28)"
987
+ },
988
+ children: ADD_MENU.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
989
+ "button",
990
+ {
991
+ type: "button",
992
+ role: "menuitem",
993
+ "data-cms-add": item.kind,
994
+ style: {
995
+ ...btn,
996
+ display: "block",
997
+ width: "100%",
998
+ textAlign: "left",
999
+ border: 0,
1000
+ background: "transparent"
1001
+ },
1002
+ onClick: () => add(item.kind),
1003
+ children: item.label
1004
+ },
1005
+ item.kind
1006
+ ))
1007
+ }
1008
+ )
1009
+ ] });
1010
+ }
1011
+ var DARK_VARS = "color-scheme:dark;--fcms-bg:#0b0f19;--fcms-fg:#e5e7eb;--fcms-muted:#94a3b8;--fcms-border:#27272a;--fcms-canvas:#0f141f;--fcms-input-bg:#111827;--fcms-row-fg:#cbd5e1;--fcms-sel-bg:color-mix(in oklab, #8b5cf6 24%, transparent);--fcms-sel-fg:#ddd6fe;--fcms-drop:#ec4899;--fcms-danger:#fca5a5;";
1012
+ var LIGHT_VARS = "color-scheme:light;--fcms-bg:#ffffff;--fcms-fg:#0f172a;--fcms-muted:#64748b;--fcms-border:#e2e8f0;--fcms-canvas:#f8fafc;--fcms-input-bg:#ffffff;--fcms-row-fg:#334155;--fcms-sel-bg:#ede9fe;--fcms-sel-fg:#5b21b6;--fcms-drop:#db2777;--fcms-danger:#dc2626;";
837
1013
  var CHROME_CSS = `.fancy-cms-editor{--fcms-accent:#8b5cf6;${LIGHT_VARS}}@media (prefers-color-scheme:dark){.fancy-cms-editor{${DARK_VARS}}}:where(.light,[data-theme="light"]) .fancy-cms-editor{${LIGHT_VARS}}:where(.dark,[data-theme="dark"]) .fancy-cms-editor{${DARK_VARS}}`;
838
- var CONTAINER_TYPES = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape", "card", "device"]);
1014
+ var CONTAINER_TYPES2 = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape", "card", "device"]);
839
1015
  function NodeInspector({
840
1016
  node,
841
1017
  transform,
@@ -860,11 +1036,11 @@ function NodeInspector({
860
1036
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, opacity: 0.5, fontFamily: "ui-monospace, monospace", overflow: "hidden", textOverflow: "ellipsis" }, children: node.id })
861
1037
  ] }),
862
1038
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1 } }),
863
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...iconBtn, color: "#fca5a5" }, title: "Delete element", onClick: onRemove, children: "\u{1F5D1}" }),
1039
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...iconBtn, color: "var(--fcms-danger, #fca5a5)" }, title: "Delete element", onClick: onRemove, children: "\u{1F5D1}" }),
864
1040
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: iconBtn, title: "Close", onClick: onClose, children: "\u2715" })
865
1041
  ] }),
866
1042
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: body, children: [
867
- "content" in node.props || node.type === "richtext" ? /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: 11, color: "var(--fg-3, #94a3b8)", margin: "0 0 12px", lineHeight: 1.45, paddingBottom: 12, borderBottom: "1px solid #1e293b" }, children: [
1043
+ "content" in node.props || node.type === "richtext" ? /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: 11, color: "var(--fcms-muted, #94a3b8)", margin: "0 0 12px", lineHeight: 1.45, paddingBottom: 12, borderBottom: "1px solid var(--fcms-border, #1e293b)" }, children: [
868
1044
  "\u270E Edit the text ",
869
1045
  /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "directly on the page" }),
870
1046
  " \u2014 click in and type. This panel is for layout, style, and bindings."
@@ -889,7 +1065,7 @@ function NodeInspector({
889
1065
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: { ...miniBtn, alignSelf: "end" }, onClick: () => onTransform({}), title: "Clear transform", children: "Reset" })
890
1066
  ] })
891
1067
  ] }),
892
- !CONTAINER_TYPES.has(node.type) ? /* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Text", children: [
1068
+ !CONTAINER_TYPES2.has(node.type) ? /* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Text", children: [
893
1069
  /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
894
1070
  /* @__PURE__ */ jsxRuntime.jsx(Color, { label: "Color", value: base.color ?? "#0f172a", onChange: (v) => onStyle({ color: v }) }),
895
1071
  /* @__PURE__ */ jsxRuntime.jsx(Num, { label: "Size", value: base.fontSize?.value, placeholder: "16", onChange: (v) => onStyle({ fontSize: v == null ? void 0 : len(v) }) })
@@ -926,7 +1102,7 @@ function NodeInspector({
926
1102
  ] }),
927
1103
  /* @__PURE__ */ jsxRuntime.jsx(Row, { children: /* @__PURE__ */ jsxRuntime.jsx(Txt, { label: "Border", value: base.border ?? "", placeholder: "1px solid #e2e8f0", onChange: (v) => onStyle({ border: v || void 0 }) }) })
928
1104
  ] }),
929
- CONTAINER_TYPES.has(node.type) ? /* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Layout", children: [
1105
+ CONTAINER_TYPES2.has(node.type) ? /* @__PURE__ */ jsxRuntime.jsxs(Group, { label: "Layout", children: [
930
1106
  /* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
931
1107
  /* @__PURE__ */ jsxRuntime.jsx(
932
1108
  Sel,
@@ -964,7 +1140,7 @@ function NodeInspector({
964
1140
  ] });
965
1141
  }
966
1142
  function Group({ label: label2, children }) {
967
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8, paddingBottom: 12, marginBottom: 12, borderBottom: "1px solid #1e293b" }, children: [
1143
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8, paddingBottom: 12, marginBottom: 12, borderBottom: "1px solid var(--fcms-border, #1e293b)" }, children: [
968
1144
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, letterSpacing: "0.06em", textTransform: "uppercase", opacity: 0.5 }, children: label2 }),
969
1145
  children
970
1146
  ] });
@@ -980,7 +1156,7 @@ function PropField({ name, value, onChange }) {
980
1156
  type: "button",
981
1157
  title: bound ? "Unbind (use a literal value)" : "Bind to data (e.g. item.name)",
982
1158
  onClick: () => onChange(bound ? "" : { $bind: "" }),
983
- style: { background: "none", border: "none", cursor: "pointer", padding: 0, fontSize: 11, color: bound ? "#a78bfa" : "#64748b" },
1159
+ style: { background: "none", border: "none", cursor: "pointer", padding: 0, fontSize: 11, color: bound ? "var(--fcms-accent, #a78bfa)" : "var(--fcms-muted, #64748b)" },
984
1160
  children: "\u{1F517}"
985
1161
  }
986
1162
  );
@@ -991,7 +1167,7 @@ function PropField({ name, value, onChange }) {
991
1167
  " ",
992
1168
  link
993
1169
  ] }),
994
- /* @__PURE__ */ jsxRuntime.jsx("input", { style: { ...input2, borderColor: "#7c3aed" }, placeholder: "data path \xB7 e.g. item.name", value: value.$bind, onChange: (e) => onChange({ $bind: e.target.value }) })
1170
+ /* @__PURE__ */ jsxRuntime.jsx("input", { style: { ...input2, borderColor: "var(--fcms-accent, #7c3aed)" }, placeholder: "data path \xB7 e.g. item.name", value: value.$bind, onChange: (e) => onChange({ $bind: e.target.value }) })
995
1171
  ] });
996
1172
  }
997
1173
  if (typeof value === "boolean") {
@@ -1078,7 +1254,7 @@ var panel = {
1078
1254
  fontFamily: "system-ui, sans-serif",
1079
1255
  overflow: "hidden"
1080
1256
  };
1081
- var headRow = { display: "flex", alignItems: "center", gap: 8, padding: "10px 12px", borderBottom: "1px solid #1e293b" };
1257
+ var headRow = { display: "flex", alignItems: "center", gap: 8, padding: "10px 12px", borderBottom: "1px solid var(--fcms-border, #1e293b)" };
1082
1258
  var body = { padding: 12, overflowY: "auto" };
1083
1259
  var field = { display: "flex", flexDirection: "column", gap: 4, flex: 1, minWidth: 0 };
1084
1260
  var fieldLabel = { fontSize: 10, opacity: 0.6 };
@@ -1309,7 +1485,7 @@ function EditablePage({
1309
1485
  const addNode = react.useCallback(
1310
1486
  (kind) => {
1311
1487
  const doc2 = ed.state.doc;
1312
- const parent = selNode ? CONTAINER_TYPES2.has(selNode.type) ? selNode.id : selNode.parent : doc2.sections[doc2.sections.length - 1] ?? null;
1488
+ const parent = selNode ? CONTAINER_TYPES.has(selNode.type) ? selNode.id : selNode.parent : doc2.sections[doc2.sections.length - 1] ?? null;
1313
1489
  const siblings = childrenOf(doc2, parent);
1314
1490
  const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
1315
1491
  const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
@@ -1323,7 +1499,7 @@ function EditablePage({
1323
1499
  (kind, targetId) => {
1324
1500
  const doc2 = ed.state.doc;
1325
1501
  const target = targetId ? doc2.nodes[targetId] : null;
1326
- const parent = target ? CONTAINER_TYPES2.has(target.type) ? target.id : target.parent : doc2.sections[doc2.sections.length - 1] ?? null;
1502
+ const parent = target ? CONTAINER_TYPES.has(target.type) ? target.id : target.parent : doc2.sections[doc2.sections.length - 1] ?? null;
1327
1503
  const siblings = childrenOf(doc2, parent);
1328
1504
  const order = keyBetween(siblings.length ? siblings[siblings.length - 1].order : null, null);
1329
1505
  const id = `n${doc2.seq + 1}-${Math.floor(performance.now())}`;
@@ -1356,7 +1532,7 @@ function EditablePage({
1356
1532
  const node = ed.state.doc.nodes[id];
1357
1533
  const r = el.getBoundingClientRect();
1358
1534
  const rect = { x: r.left, y: r.top, w: r.width, h: r.height };
1359
- if (node && CONTAINER_TYPES2.has(node.type)) return { id, edge: "inside", rect };
1535
+ if (node && CONTAINER_TYPES.has(node.type)) return { id, edge: "inside", rect };
1360
1536
  return { id, edge: clientY < r.top + r.height / 2 ? "before" : "after", rect };
1361
1537
  };
1362
1538
  const onPageDragOver = (e) => {
@@ -1412,7 +1588,7 @@ function EditablePage({
1412
1588
  if (id) clip.current = snapshotSubtree(ed.state.doc, id);
1413
1589
  };
1414
1590
  const paste = (target) => {
1415
- if (clip.current) runOps(pasteOps(ed.state.doc, clip.current, target, CONTAINER_TYPES2));
1591
+ if (clip.current) runOps(pasteOps(ed.state.doc, clip.current, target, CONTAINER_TYPES));
1416
1592
  };
1417
1593
  const startEditText = (id) => {
1418
1594
  if (!id) return;
@@ -1484,7 +1660,7 @@ function EditablePage({
1484
1660
  {
1485
1661
  node: selNode ?? null,
1486
1662
  hasClipboard: Boolean(clip.current),
1487
- isContainer: selNode ? CONTAINER_TYPES2.has(selNode.type) : false,
1663
+ isContainer: selNode ? CONTAINER_TYPES.has(selNode.type) : false,
1488
1664
  onEditText: () => startEditText(selection),
1489
1665
  onAdd: addNode,
1490
1666
  onDuplicate: () => duplicate(selection),
@@ -1547,7 +1723,7 @@ function DropIndicator({
1547
1723
  }) {
1548
1724
  const r = hint.rect;
1549
1725
  if (!r) return null;
1550
- const color = "#ec4899";
1726
+ const color = "var(--fcms-drop, #ec4899)";
1551
1727
  if (hint.edge === "inside") {
1552
1728
  return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "fixed", left: r.x, top: r.y, width: r.w, height: r.h, outline: `2px dashed ${color}`, outlineOffset: -2, borderRadius: 6, pointerEvents: "none", zIndex: Z2 + 4 } });
1553
1729
  }
@@ -1566,9 +1742,9 @@ function ElementPalette({ open, onDragChange }) {
1566
1742
  width: 216,
1567
1743
  transform: open ? "translateX(0)" : "translateX(100%)",
1568
1744
  transition: "transform 0.18s ease",
1569
- background: "#0b1220",
1570
- color: "#e2e8f0",
1571
- borderLeft: "1px solid #1e293b",
1745
+ background: "var(--fcms-bg, #0b1220)",
1746
+ color: "var(--fcms-fg, #e2e8f0)",
1747
+ borderLeft: "1px solid var(--fcms-border, #1e293b)",
1572
1748
  borderTopLeftRadius: 12,
1573
1749
  borderBottomLeftRadius: 12,
1574
1750
  boxShadow: "-18px 0 48px -20px rgba(0,0,0,0.6)",
@@ -1598,8 +1774,8 @@ function ElementPalette({ open, onDragChange }) {
1598
1774
  gap: 4,
1599
1775
  height: 56,
1600
1776
  borderRadius: 8,
1601
- background: "#0f172a",
1602
- border: "1px solid #1e293b",
1777
+ background: "var(--fcms-canvas, #0f172a)",
1778
+ border: "1px solid var(--fcms-border, #1e293b)",
1603
1779
  cursor: "grab",
1604
1780
  fontSize: 12,
1605
1781
  userSelect: "none"
@@ -1616,23 +1792,6 @@ function ElementPalette({ open, onDragChange }) {
1616
1792
  }
1617
1793
  );
1618
1794
  }
1619
- var CONTAINER_TYPES2 = /* @__PURE__ */ new Set(["section", "frame", "stack", "grid", "shape", "card", "device", "repeater"]);
1620
- var ADD_DEFAULTS = {
1621
- text: { type: "text", props: { content: "New text" }, style: { color: "inherit" } },
1622
- heading: { type: "heading", props: { content: "New heading" }, style: { fontSize: { value: 28, unit: "px" }, fontWeight: 700 } },
1623
- button: { type: "button", props: { label: "Button", href: "#", variant: "primary" }, style: {} },
1624
- link: { type: "link", props: { content: "link text", href: "#" }, style: { color: "#7c3aed" } },
1625
- image: { type: "image", props: { src: "", alt: "" }, style: {} },
1626
- box: { type: "frame", props: {}, style: { padding: { value: 16, unit: "px" } } },
1627
- stack: { type: "stack", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "stack" },
1628
- grid: { type: "grid", props: {}, style: { gap: { value: 12, unit: "px" } }, layout: "grid" },
1629
- card: { type: "card", props: {}, style: { padding: { value: 16, unit: "px" }, radius: { value: 12, unit: "px" }, border: "1px solid #e2e8f0" } },
1630
- callout: { type: "callout", props: { content: "Heads up \u2014 this is a callout.", variant: "info" }, style: {} },
1631
- divider: { type: "divider", props: {}, style: {} },
1632
- code: { type: "code", props: { content: "npm install @particle-academy/react-fancy", lang: "bash" }, style: {} },
1633
- richtext: { type: "richtext", props: { html: "<p>Rich <strong>text</strong> with <em>inline</em> formatting.</p>" }, style: {} },
1634
- repeater: { type: "repeater", props: { items: "" }, style: { gap: { value: 12, unit: "px" } }, layout: "stack" }
1635
- };
1636
1795
  var GRIPS = [
1637
1796
  { key: "nw", north: true, west: true, cursor: "nwse-resize" },
1638
1797
  { key: "n", north: true, cursor: "ns-resize" },
@@ -1743,7 +1902,7 @@ function SelectionOverlay({
1743
1902
  top: box.y - 1,
1744
1903
  width: box.w + 2,
1745
1904
  height: box.h + 2,
1746
- outline: "2px solid #8b5cf6",
1905
+ outline: "2px solid var(--fcms-accent, #8b5cf6)",
1747
1906
  borderRadius: 4,
1748
1907
  pointerEvents: "none",
1749
1908
  zIndex: Z2
@@ -1753,7 +1912,7 @@ function SelectionOverlay({
1753
1912
  width: 12,
1754
1913
  height: 12,
1755
1914
  background: "#fff",
1756
- border: "2px solid #8b5cf6",
1915
+ border: "2px solid var(--fcms-accent, #8b5cf6)",
1757
1916
  borderRadius: 3,
1758
1917
  zIndex: Z2 + 1,
1759
1918
  touchAction: "none",
@@ -1830,22 +1989,6 @@ function FloatingToolbar({
1830
1989
  ] })
1831
1990
  ] });
1832
1991
  }
1833
- var ADD_MENU = [
1834
- { kind: "heading", label: "Heading" },
1835
- { kind: "text", label: "Text" },
1836
- { kind: "button", label: "Button" },
1837
- { kind: "link", label: "Link" },
1838
- { kind: "image", label: "Image" },
1839
- { kind: "card", label: "Card" },
1840
- { kind: "callout", label: "Callout" },
1841
- { kind: "stack", label: "Stack" },
1842
- { kind: "grid", label: "Grid" },
1843
- { kind: "box", label: "Box" },
1844
- { kind: "divider", label: "Divider" },
1845
- { kind: "code", label: "Code" },
1846
- { kind: "richtext", label: "Rich text" },
1847
- { kind: "repeater", label: "Repeater" }
1848
- ];
1849
1992
  function EditBar({
1850
1993
  editing,
1851
1994
  onToggle,