worldorbit 3.2.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.md +546 -545
  2. package/dist/browser/core/dist/atlas-edit.js +146 -1
  3. package/dist/browser/core/dist/atlas-validate.js +105 -10
  4. package/dist/browser/core/dist/draft-parse.js +341 -16
  5. package/dist/browser/core/dist/draft.d.ts +2 -1
  6. package/dist/browser/core/dist/draft.js +25 -3
  7. package/dist/browser/core/dist/format.js +86 -4
  8. package/dist/browser/core/dist/index.d.ts +1 -0
  9. package/dist/browser/core/dist/index.js +1 -0
  10. package/dist/browser/core/dist/load.js +7 -2
  11. package/dist/browser/core/dist/normalize.js +1 -0
  12. package/dist/browser/core/dist/scene.js +11 -2
  13. package/dist/browser/core/dist/schema.js +11 -1
  14. package/dist/browser/core/dist/solver.d.ts +26 -0
  15. package/dist/browser/core/dist/solver.js +27 -0
  16. package/dist/browser/core/dist/types.d.ts +57 -3
  17. package/dist/browser/editor/dist/editor.js +844 -719
  18. package/dist/browser/editor/dist/types.d.ts +2 -1
  19. package/dist/browser/obsidian-plugin/dist/diagnostics.d.ts +3 -0
  20. package/dist/browser/obsidian-plugin/dist/diagnostics.js +23 -0
  21. package/dist/browser/obsidian-plugin/dist/examples.d.ts +3 -0
  22. package/dist/browser/obsidian-plugin/dist/examples.js +77 -0
  23. package/dist/browser/obsidian-plugin/dist/index.d.ts +9 -0
  24. package/dist/browser/obsidian-plugin/dist/index.js +8 -0
  25. package/dist/browser/obsidian-plugin/dist/main.d.ts +2 -0
  26. package/dist/browser/obsidian-plugin/dist/main.js +2 -0
  27. package/dist/browser/obsidian-plugin/dist/navigation.d.ts +6 -0
  28. package/dist/browser/obsidian-plugin/dist/navigation.js +44 -0
  29. package/dist/browser/obsidian-plugin/dist/plugin.d.ts +8 -0
  30. package/dist/browser/obsidian-plugin/dist/plugin.js +508 -0
  31. package/dist/browser/obsidian-plugin/dist/positions.d.ts +7 -0
  32. package/dist/browser/obsidian-plugin/dist/positions.js +14 -0
  33. package/dist/browser/obsidian-plugin/dist/settings.d.ts +2 -0
  34. package/dist/browser/obsidian-plugin/dist/settings.js +5 -0
  35. package/dist/browser/obsidian-plugin/dist/theme.d.ts +2 -0
  36. package/dist/browser/obsidian-plugin/dist/theme.js +31 -0
  37. package/dist/browser/obsidian-plugin/dist/types.d.ts +42 -0
  38. package/dist/browser/obsidian-plugin/dist/types.js +1 -0
  39. package/dist/browser/obsidian-plugin/dist/viewer-host.d.ts +14 -0
  40. package/dist/browser/obsidian-plugin/dist/viewer-host.js +110 -0
  41. package/dist/browser/viewer/dist/index.d.ts +1 -0
  42. package/dist/browser/viewer/dist/index.js +1 -0
  43. package/dist/browser/viewer/dist/interactive-2d.d.ts +21 -0
  44. package/dist/browser/viewer/dist/interactive-2d.js +201 -0
  45. package/dist/browser/viewer/dist/minimap.js +9 -7
  46. package/dist/browser/viewer/dist/render.d.ts +1 -1
  47. package/dist/browser/viewer/dist/render.js +25 -20
  48. package/dist/browser/viewer/dist/runtime-3d.js +2 -0
  49. package/dist/browser/viewer/dist/viewer-state.d.ts +1 -1
  50. package/dist/browser/viewer/dist/viewer-state.js +1 -1
  51. package/dist/browser/viewer/dist/viewer.js +7 -3
  52. package/dist/obsidian-plugin/LICENSE +21 -0
  53. package/dist/obsidian-plugin/README.md +141 -0
  54. package/dist/obsidian-plugin/main.js +108 -0
  55. package/dist/obsidian-plugin/manifest.json +9 -0
  56. package/dist/obsidian-plugin/obsidian_scsh_1.png +0 -0
  57. package/dist/obsidian-plugin/obsidian_scsh_2.png +0 -0
  58. package/dist/obsidian-plugin/styles.css +164 -0
  59. package/dist/unpkg/core/dist/atlas-edit.js +146 -1
  60. package/dist/unpkg/core/dist/atlas-validate.js +105 -10
  61. package/dist/unpkg/core/dist/draft-parse.js +341 -16
  62. package/dist/unpkg/core/dist/draft.d.ts +2 -1
  63. package/dist/unpkg/core/dist/draft.js +25 -3
  64. package/dist/unpkg/core/dist/format.js +86 -4
  65. package/dist/unpkg/core/dist/index.d.ts +1 -0
  66. package/dist/unpkg/core/dist/index.js +1 -0
  67. package/dist/unpkg/core/dist/load.js +7 -2
  68. package/dist/unpkg/core/dist/normalize.js +1 -0
  69. package/dist/unpkg/core/dist/scene.js +11 -2
  70. package/dist/unpkg/core/dist/schema.js +11 -1
  71. package/dist/unpkg/core/dist/solver.d.ts +26 -0
  72. package/dist/unpkg/core/dist/solver.js +27 -0
  73. package/dist/unpkg/core/dist/types.d.ts +57 -3
  74. package/dist/unpkg/editor/dist/editor.js +844 -719
  75. package/dist/unpkg/editor/dist/types.d.ts +2 -1
  76. package/dist/unpkg/obsidian-plugin/dist/diagnostics.d.ts +3 -0
  77. package/dist/unpkg/obsidian-plugin/dist/diagnostics.js +23 -0
  78. package/dist/unpkg/obsidian-plugin/dist/examples.d.ts +3 -0
  79. package/dist/unpkg/obsidian-plugin/dist/examples.js +77 -0
  80. package/dist/unpkg/obsidian-plugin/dist/index.d.ts +9 -0
  81. package/dist/unpkg/obsidian-plugin/dist/index.js +8 -0
  82. package/dist/unpkg/obsidian-plugin/dist/main.d.ts +2 -0
  83. package/dist/unpkg/obsidian-plugin/dist/main.js +2 -0
  84. package/dist/unpkg/obsidian-plugin/dist/navigation.d.ts +6 -0
  85. package/dist/unpkg/obsidian-plugin/dist/navigation.js +44 -0
  86. package/dist/unpkg/obsidian-plugin/dist/plugin.d.ts +8 -0
  87. package/dist/unpkg/obsidian-plugin/dist/plugin.js +508 -0
  88. package/dist/unpkg/obsidian-plugin/dist/positions.d.ts +7 -0
  89. package/dist/unpkg/obsidian-plugin/dist/positions.js +14 -0
  90. package/dist/unpkg/obsidian-plugin/dist/settings.d.ts +2 -0
  91. package/dist/unpkg/obsidian-plugin/dist/settings.js +5 -0
  92. package/dist/unpkg/obsidian-plugin/dist/theme.d.ts +2 -0
  93. package/dist/unpkg/obsidian-plugin/dist/theme.js +31 -0
  94. package/dist/unpkg/obsidian-plugin/dist/types.d.ts +42 -0
  95. package/dist/unpkg/obsidian-plugin/dist/types.js +1 -0
  96. package/dist/unpkg/obsidian-plugin/dist/viewer-host.d.ts +14 -0
  97. package/dist/unpkg/obsidian-plugin/dist/viewer-host.js +110 -0
  98. package/dist/unpkg/viewer/dist/index.d.ts +1 -0
  99. package/dist/unpkg/viewer/dist/index.js +1 -0
  100. package/dist/unpkg/viewer/dist/interactive-2d.d.ts +21 -0
  101. package/dist/unpkg/viewer/dist/interactive-2d.js +201 -0
  102. package/dist/unpkg/viewer/dist/minimap.js +9 -7
  103. package/dist/unpkg/viewer/dist/render.d.ts +1 -1
  104. package/dist/unpkg/viewer/dist/render.js +25 -20
  105. package/dist/unpkg/viewer/dist/runtime-3d.js +2 -0
  106. package/dist/unpkg/viewer/dist/viewer-state.d.ts +1 -1
  107. package/dist/unpkg/viewer/dist/viewer-state.js +1 -1
  108. package/dist/unpkg/viewer/dist/viewer.js +7 -3
  109. package/dist/unpkg/worldorbit-core.min.js +10 -10
  110. package/dist/unpkg/worldorbit-editor.min.js +359 -332
  111. package/dist/unpkg/worldorbit-markdown.min.js +28 -28
  112. package/dist/unpkg/worldorbit-viewer.min.js +203 -203
  113. package/dist/unpkg/worldorbit.js +958 -40
  114. package/dist/unpkg/worldorbit.min.js +214 -214
  115. package/package.json +22 -1
  116. package/packages/core/dist/atlas-edit.js +146 -1
  117. package/packages/core/dist/atlas-validate.js +105 -10
  118. package/packages/core/dist/draft-parse.js +341 -16
  119. package/packages/core/dist/draft.d.ts +2 -1
  120. package/packages/core/dist/draft.js +25 -3
  121. package/packages/core/dist/format.js +86 -4
  122. package/packages/core/dist/index.d.ts +1 -0
  123. package/packages/core/dist/index.js +1 -0
  124. package/packages/core/dist/load.js +7 -2
  125. package/packages/core/dist/normalize.js +1 -0
  126. package/packages/core/dist/scene.js +11 -2
  127. package/packages/core/dist/schema.js +11 -1
  128. package/packages/core/dist/solver.d.ts +26 -0
  129. package/packages/core/dist/solver.js +27 -0
  130. package/packages/core/dist/types.d.ts +57 -3
  131. package/packages/editor/dist/editor.js +844 -719
  132. package/packages/editor/dist/types.d.ts +2 -1
  133. package/packages/viewer/dist/index.d.ts +1 -0
  134. package/packages/viewer/dist/index.js +1 -0
  135. package/packages/viewer/dist/interactive-2d.d.ts +21 -0
  136. package/packages/viewer/dist/interactive-2d.js +201 -0
  137. package/packages/viewer/dist/minimap.js +9 -7
  138. package/packages/viewer/dist/render.d.ts +1 -1
  139. package/packages/viewer/dist/render.js +25 -20
  140. package/packages/viewer/dist/runtime-3d.js +2 -0
  141. package/packages/viewer/dist/viewer-state.d.ts +1 -1
  142. package/packages/viewer/dist/viewer-state.js +1 -1
  143. package/packages/viewer/dist/viewer.js +7 -3
@@ -39,6 +39,7 @@ const OBJECT_TYPES = [
39
39
  "ring",
40
40
  "structure",
41
41
  "phenomenon",
42
+ "craft",
42
43
  ];
43
44
  const OBJECT_STRING_FIELDS = [
44
45
  "kind",
@@ -91,11 +92,11 @@ const FIELD_HELP = {
91
92
  references: ["1 = scene fit", "2+ = close-up"],
92
93
  },
93
94
  "viewpoint-rotation": {
94
- description: "Legacy 2D screen rotation. This is separate from the Schema 2.5 camera block.",
95
+ description: "Legacy 2D screen rotation. This is separate from the Schema 3.0 camera block.",
95
96
  references: ["90deg = quarter turn", "Use camera.azimuth for semantic view direction"],
96
97
  },
97
98
  "viewpoint-camera-azimuth": {
98
- description: "Horizontal camera direction in degrees for Schema 2.5 viewpoints.",
99
+ description: "Horizontal camera direction in degrees for Schema 3.0 viewpoints.",
99
100
  references: ["0 = forward/default", "90 = quarter orbit around the scene"],
100
101
  },
101
102
  "viewpoint-camera-elevation": {
@@ -146,10 +147,18 @@ const FIELD_HELP = {
146
147
  description: "Viewpoint IDs that should list this event prominently.",
147
148
  references: ["naar-system", "overview inner-system"],
148
149
  },
150
+ "event-trajectory": {
151
+ description: "Links the event to a declarative trajectory or maneuver plan.",
152
+ references: ["courier-run", "swing-by-window"],
153
+ },
149
154
  "placement-target": {
150
155
  description: "Names the body or reference this object is attached to.",
151
156
  references: ["orbit Primary", "surface Homeworld", "at Naar:L4"],
152
157
  },
158
+ "object-trajectory": {
159
+ description: "Links a craft to a declarative trajectory definition.",
160
+ references: ["courier-run", "transfer-orbit"],
161
+ },
153
162
  "placement-free": {
154
163
  description: "Stores a free-placement offset or a descriptive label for loose placement.",
155
164
  references: ["8au = far from the star", '"outer system" = descriptive note'],
@@ -740,75 +749,75 @@ export function createWorldOrbitEditor(container, options = {}) {
740
749
  function renderToolbar() {
741
750
  const objectType = toolbar.querySelector("[data-editor-add-object-type]")
742
751
  ?.value ?? "planet";
743
- toolbar.innerHTML = `
744
- <div class="wo-editor-toolbar-group">
745
- <select data-editor-add-object-type>
746
- ${OBJECT_TYPES.map((type) => `<option value="${escapeHtml(type)}"${type === objectType ? " selected" : ""}>${escapeHtml(humanizeIdentifier(type))}</option>`).join("")}
747
- </select>
748
- <button type="button" data-editor-action="add-object">Add object</button>
749
- <button type="button" data-editor-action="add-event">Add event</button>
750
- <button type="button" data-editor-action="add-viewpoint">Add viewpoint</button>
751
- <button type="button" data-editor-action="add-annotation">Add annotation</button>
752
- <button type="button" data-editor-action="add-metadata">Add metadata</button>
753
- </div>
754
- <div class="wo-editor-toolbar-group">
755
- <button type="button" data-editor-action="remove"${!selection || selection.kind === "system" || selection.kind === "defaults" ? " disabled" : ""}>Remove</button>
756
- <button type="button" data-editor-action="undo"${history.length === 0 ? " disabled" : ""}>Undo</button>
757
- <button type="button" data-editor-action="redo"${future.length === 0 ? " disabled" : ""}>Redo</button>
758
- <button type="button" data-editor-action="format">Format source</button>
759
- </div>
752
+ toolbar.innerHTML = `
753
+ <div class="wo-editor-toolbar-group">
754
+ <select data-editor-add-object-type>
755
+ ${OBJECT_TYPES.map((type) => `<option value="${escapeHtml(type)}"${type === objectType ? " selected" : ""}>${escapeHtml(humanizeIdentifier(type))}</option>`).join("")}
756
+ </select>
757
+ <button type="button" data-editor-action="add-object">Add object</button>
758
+ <button type="button" data-editor-action="add-event">Add event</button>
759
+ <button type="button" data-editor-action="add-viewpoint">Add viewpoint</button>
760
+ <button type="button" data-editor-action="add-annotation">Add annotation</button>
761
+ <button type="button" data-editor-action="add-metadata">Add metadata</button>
762
+ </div>
763
+ <div class="wo-editor-toolbar-group">
764
+ <button type="button" data-editor-action="remove"${!selection || selection.kind === "system" || selection.kind === "defaults" ? " disabled" : ""}>Remove</button>
765
+ <button type="button" data-editor-action="undo"${history.length === 0 ? " disabled" : ""}>Undo</button>
766
+ <button type="button" data-editor-action="redo"${future.length === 0 ? " disabled" : ""}>Redo</button>
767
+ <button type="button" data-editor-action="format">Format source</button>
768
+ </div>
760
769
  `;
761
770
  }
762
771
  function renderOutline() {
763
772
  const activeKey = selectionKey(selection);
764
773
  const diagnosticBuckets = buildDiagnosticBuckets(diagnostics);
765
774
  const metadataEntries = Object.entries(atlasDocument.system?.atlasMetadata ?? {}).sort(([left], [right]) => left.localeCompare(right));
766
- outline.innerHTML = `
767
- <div class="wo-editor-outline-section">
768
- <h3>Atlas</h3>
769
- ${renderOutlineButton({ kind: "system" }, "System", activeKey, diagnosticBuckets)}
770
- ${renderOutlineButton({ kind: "defaults" }, "Defaults", activeKey, diagnosticBuckets)}
771
- </div>
772
- <div class="wo-editor-outline-section">
773
- <h3>Metadata</h3>
775
+ outline.innerHTML = `
776
+ <div class="wo-editor-outline-section">
777
+ <h3>Atlas</h3>
778
+ ${renderOutlineButton({ kind: "system" }, "System", activeKey, diagnosticBuckets)}
779
+ ${renderOutlineButton({ kind: "defaults" }, "Defaults", activeKey, diagnosticBuckets)}
780
+ </div>
781
+ <div class="wo-editor-outline-section">
782
+ <h3>Metadata</h3>
774
783
  ${metadataEntries.length > 0
775
784
  ? metadataEntries
776
785
  .map(([key]) => renderOutlineButton({ kind: "metadata", key }, key, activeKey, diagnosticBuckets))
777
786
  .join("")
778
- : `<p class="wo-editor-empty">No atlas metadata yet.</p>`}
779
- </div>
780
- <div class="wo-editor-outline-section">
781
- <h3>Viewpoints</h3>
787
+ : `<p class="wo-editor-empty">No atlas metadata yet.</p>`}
788
+ </div>
789
+ <div class="wo-editor-outline-section">
790
+ <h3>Viewpoints</h3>
782
791
  ${(atlasDocument.system?.viewpoints.length ?? 0) > 0
783
792
  ? atlasDocument.system?.viewpoints
784
793
  .map((viewpoint) => renderOutlineButton({ kind: "viewpoint", id: viewpoint.id }, viewpoint.label, activeKey, diagnosticBuckets))
785
794
  .join("")
786
- : `<p class="wo-editor-empty">No viewpoints yet.</p>`}
787
- </div>
788
- <div class="wo-editor-outline-section">
789
- <h3>Annotations</h3>
795
+ : `<p class="wo-editor-empty">No viewpoints yet.</p>`}
796
+ </div>
797
+ <div class="wo-editor-outline-section">
798
+ <h3>Annotations</h3>
790
799
  ${(atlasDocument.system?.annotations.length ?? 0) > 0
791
800
  ? atlasDocument.system?.annotations
792
801
  .map((annotation) => renderOutlineButton({ kind: "annotation", id: annotation.id }, annotation.label, activeKey, diagnosticBuckets))
793
802
  .join("")
794
- : `<p class="wo-editor-empty">No annotations yet.</p>`}
795
- </div>
796
- <div class="wo-editor-outline-section">
797
- <h3>Events</h3>
803
+ : `<p class="wo-editor-empty">No annotations yet.</p>`}
804
+ </div>
805
+ <div class="wo-editor-outline-section">
806
+ <h3>Events</h3>
798
807
  ${atlasDocument.events.length > 0
799
808
  ? atlasDocument.events
800
809
  .map((eventEntry) => renderEventOutlineItems(eventEntry, activeKey, diagnosticBuckets))
801
810
  .join("")
802
- : `<p class="wo-editor-empty">No events yet.</p>`}
803
- </div>
804
- <div class="wo-editor-outline-section">
805
- <h3>Objects</h3>
811
+ : `<p class="wo-editor-empty">No events yet.</p>`}
812
+ </div>
813
+ <div class="wo-editor-outline-section">
814
+ <h3>Objects</h3>
806
815
  ${atlasDocument.objects.length > 0
807
816
  ? atlasDocument.objects
808
817
  .map((object) => renderOutlineButton({ kind: "object", id: object.id }, `${object.id} - ${object.type}`, activeKey, diagnosticBuckets))
809
818
  .join("")
810
- : `<p class="wo-editor-empty">No objects yet.</p>`}
811
- </div>
819
+ : `<p class="wo-editor-empty">No objects yet.</p>`}
820
+ </div>
812
821
  `;
813
822
  }
814
823
  function renderDiagnostics() {
@@ -819,10 +828,10 @@ export function createWorldOrbitEditor(container, options = {}) {
819
828
  .map((entry) => {
820
829
  const pathLabel = entry.path ? describePath(entry.path) : "Source";
821
830
  const location = formatDiagnosticLocation(entry);
822
- return `<article class="wo-editor-diagnostic wo-editor-diagnostic-${escapeHtml(entry.diagnostic.severity)}">
823
- <strong>${escapeHtml(entry.diagnostic.severity.toUpperCase())}</strong>
824
- <span>${escapeHtml(pathLabel)}${location ? ` · ${escapeHtml(location)}` : ""}</span>
825
- <p>${escapeHtml(entry.diagnostic.message)}</p>
831
+ return `<article class="wo-editor-diagnostic wo-editor-diagnostic-${escapeHtml(entry.diagnostic.severity)}">
832
+ <strong>${escapeHtml(entry.diagnostic.severity.toUpperCase())}</strong>
833
+ <span>${escapeHtml(pathLabel)}${location ? ` · ${escapeHtml(location)}` : ""}</span>
834
+ <p>${escapeHtml(entry.diagnostic.message)}</p>
826
835
  </article>`;
827
836
  })
828
837
  .join("");
@@ -835,10 +844,10 @@ export function createWorldOrbitEditor(container, options = {}) {
835
844
  : sourceEntries
836
845
  .map((entry) => {
837
846
  const location = formatDiagnosticLocation(entry) ?? "Source";
838
- return `<article class="wo-editor-diagnostic wo-editor-diagnostic-${escapeHtml(entry.diagnostic.severity)}">
839
- <strong>${escapeHtml(entry.diagnostic.severity.toUpperCase())}</strong>
840
- <span>${escapeHtml(location)}</span>
841
- <p>${escapeHtml(entry.diagnostic.message)}</p>
847
+ return `<article class="wo-editor-diagnostic wo-editor-diagnostic-${escapeHtml(entry.diagnostic.severity)}">
848
+ <strong>${escapeHtml(entry.diagnostic.severity.toUpperCase())}</strong>
849
+ <span>${escapeHtml(location)}</span>
850
+ <p>${escapeHtml(entry.diagnostic.message)}</p>
842
851
  </article>`;
843
852
  })
844
853
  .join("");
@@ -983,9 +992,9 @@ export function createWorldOrbitEditor(container, options = {}) {
983
992
  panel.setAttribute("role", "status");
984
993
  panel.setAttribute("aria-live", "polite");
985
994
  panel.innerHTML = placementDiagnostics
986
- .map((entry) => `<article class="wo-editor-overlay-diagnostic wo-editor-overlay-diagnostic-${escapeHtml(entry.diagnostic.severity)}">
987
- <strong>${escapeHtml(entry.diagnostic.severity.toUpperCase())}</strong>
988
- <p>${escapeHtml(entry.diagnostic.message)}</p>
995
+ .map((entry) => `<article class="wo-editor-overlay-diagnostic wo-editor-overlay-diagnostic-${escapeHtml(entry.diagnostic.severity)}">
996
+ <strong>${escapeHtml(entry.diagnostic.severity.toUpperCase())}</strong>
997
+ <p>${escapeHtml(entry.diagnostic.message)}</p>
989
998
  </article>`)
990
999
  .join("");
991
1000
  overlay.append(panel);
@@ -1451,6 +1460,13 @@ export function createWorldOrbitEditor(container, options = {}) {
1451
1460
  color: readOptionalTextInput(form, "event-color"),
1452
1461
  hidden: readCheckbox(form, "event-hidden"),
1453
1462
  };
1463
+ const nextTrajectory = readOptionalTextInput(form, "event-trajectory");
1464
+ if (nextTrajectory) {
1465
+ replacement.trajectory = nextTrajectory;
1466
+ }
1467
+ else {
1468
+ delete replacement.trajectory;
1469
+ }
1454
1470
  nextDocument.events = nextDocument.events
1455
1471
  .filter((entry) => entry.id !== current.id)
1456
1472
  .concat(replacement)
@@ -1535,11 +1551,18 @@ export function createWorldOrbitEditor(container, options = {}) {
1535
1551
  const replacement = {
1536
1552
  ...current,
1537
1553
  id: nextId,
1538
- type: readTextInput(form, "object-type") || current.type,
1554
+ type: (readTextInput(form, "object-type") || current.type),
1539
1555
  properties: { ...current.properties },
1540
1556
  info: { ...current.info },
1541
1557
  placement: buildPlacementFromForm(form, current),
1542
1558
  };
1559
+ const nextTrajectory = readOptionalTextInput(form, "object-trajectory");
1560
+ if (nextTrajectory) {
1561
+ replacement.trajectory = nextTrajectory;
1562
+ }
1563
+ else {
1564
+ delete replacement.trajectory;
1565
+ }
1543
1566
  for (const field of OBJECT_STRING_FIELDS) {
1544
1567
  setOptionalProperty(replacement.properties, field, readOptionalTextInput(form, `prop-${field}`));
1545
1568
  }
@@ -1568,11 +1591,11 @@ export function createWorldOrbitEditor(container, options = {}) {
1568
1591
  const errorCount = diagnostics.filter((entry) => entry.diagnostic.severity === "error").length;
1569
1592
  const warningCount = diagnostics.filter((entry) => entry.diagnostic.severity === "warning").length;
1570
1593
  const selectionLabel = selection ? describePath(selection) : "Nothing selected";
1571
- statusBar.innerHTML = `
1572
- <span class="wo-editor-status-pill${dirty ? " is-dirty" : " is-clean"}">${dirty ? "Unsaved changes" : "Saved"}</span>
1573
- <span class="wo-editor-status-pill">Schema ${escapeHtml(atlasDocument.version)}</span>
1574
- <span class="wo-editor-status-pill${errorCount > 0 ? " is-error" : warningCount > 0 ? " is-warning" : ""}">${errorCount} errors · ${warningCount} warnings</span>
1575
- <span class="wo-editor-status-pill">${escapeHtml(selectionLabel)}</span>
1594
+ statusBar.innerHTML = `
1595
+ <span class="wo-editor-status-pill${dirty ? " is-dirty" : " is-clean"}">${dirty ? "Unsaved changes" : "Saved"}</span>
1596
+ <span class="wo-editor-status-pill">Schema ${escapeHtml(atlasDocument.version)}</span>
1597
+ <span class="wo-editor-status-pill${errorCount > 0 ? " is-error" : warningCount > 0 ? " is-warning" : ""}">${errorCount} errors · ${warningCount} warnings</span>
1598
+ <span class="wo-editor-status-pill">${escapeHtml(selectionLabel)}</span>
1576
1599
  `;
1577
1600
  }
1578
1601
  function updateLiveRegion() {
@@ -1659,15 +1682,15 @@ export function createWorldOrbitEditor(container, options = {}) {
1659
1682
  if (selectionDiagnostics.length === 0) {
1660
1683
  return "";
1661
1684
  }
1662
- return `<div class="wo-editor-inspector-summary">
1685
+ return `<div class="wo-editor-inspector-summary">
1663
1686
  ${selectionDiagnostics
1664
1687
  .slice(0, 4)
1665
- .map((entry) => `<article class="wo-editor-diagnostic wo-editor-diagnostic-${escapeHtml(entry.diagnostic.severity)}">
1666
- <strong>${escapeHtml(entry.diagnostic.severity.toUpperCase())}</strong>
1667
- <span>${escapeHtml(entry.diagnostic.field ?? describePath(currentSelection))}</span>
1668
- <p>${escapeHtml(entry.diagnostic.message)}</p>
1688
+ .map((entry) => `<article class="wo-editor-diagnostic wo-editor-diagnostic-${escapeHtml(entry.diagnostic.severity)}">
1689
+ <strong>${escapeHtml(entry.diagnostic.severity.toUpperCase())}</strong>
1690
+ <span>${escapeHtml(entry.diagnostic.field ?? describePath(currentSelection))}</span>
1691
+ <p>${escapeHtml(entry.diagnostic.message)}</p>
1669
1692
  </article>`)
1670
- .join("")}
1693
+ .join("")}
1671
1694
  </div>`;
1672
1695
  }
1673
1696
  function decorateInspectorDiagnostics(currentSelection, entries) {
@@ -1732,64 +1755,64 @@ function formatAtlasSource(document) {
1732
1755
  }
1733
1756
  function buildEditorMarkup() {
1734
1757
  const previewOpen = shouldPreviewSectionBeOpenByDefault();
1735
- return `<section class="wo-editor-shell">
1736
- <div class="wo-editor-toolbar" data-editor-toolbar></div>
1737
- <div class="wo-editor-status" data-editor-status role="status" aria-live="polite"></div>
1738
- <div class="wo-editor-main">
1739
- <aside class="wo-editor-sidebar" data-editor-pane="sidebar">
1740
- <div class="wo-editor-panel">${renderPanelSection("Atlas", "atlas", `<div class="wo-editor-outline" data-editor-outline></div>`)}</div>
1741
- <div class="wo-editor-panel">${renderPanelSection("Diagnostics", "diagnostics", `<div class="wo-editor-diagnostics" data-editor-diagnostics></div>`)}</div>
1742
- </aside>
1743
- <div class="wo-editor-stage-panel">
1744
- <div class="wo-editor-stage-shell" data-editor-stage-shell>
1745
- <div class="wo-editor-stage" data-editor-stage></div>
1746
- <div class="wo-editor-overlay" data-editor-overlay></div>
1747
- </div>
1748
- <div class="wo-editor-panel" data-editor-pane="preview">
1749
- ${renderPanelSection("Preview", "preview", `<div class="wo-editor-preview">
1750
- <div class="wo-editor-preview-card">
1751
- <h3>Static SVG</h3>
1752
- <div class="wo-editor-preview-visual" data-editor-preview-visual></div>
1753
- </div>
1754
- <div class="wo-editor-preview-card">
1755
- <h3>Embed Markup</h3>
1756
- <pre class="wo-editor-preview-markup" data-editor-preview-markup></pre>
1757
- </div>
1758
- </div>`, previewOpen)}
1759
- </div>
1760
- </div>
1761
- <aside class="wo-editor-panel wo-editor-inspector" data-editor-pane="inspector">
1762
- ${renderPanelSection("Inspector", "inspector", `<div data-editor-inspector></div>`)}
1763
- </aside>
1764
- </div>
1765
- <div class="wo-editor-panel wo-editor-text-panel" data-editor-pane="text">
1766
- ${renderPanelSection("Source", "source", `<textarea
1767
- class="wo-editor-source"
1768
- data-editor-source
1769
- spellcheck="false"
1770
- aria-describedby="worldorbit-editor-source-diagnostics"
1771
- ></textarea>
1772
- <div
1773
- class="wo-editor-source-diagnostics"
1774
- id="worldorbit-editor-source-diagnostics"
1775
- data-editor-source-diagnostics
1776
- aria-live="polite"
1777
- ></div>`)}
1778
- </div>
1779
- <div class="wo-editor-live-region" data-editor-live aria-live="polite" aria-atomic="true"></div>
1758
+ return `<section class="wo-editor-shell">
1759
+ <div class="wo-editor-toolbar" data-editor-toolbar></div>
1760
+ <div class="wo-editor-status" data-editor-status role="status" aria-live="polite"></div>
1761
+ <div class="wo-editor-main">
1762
+ <aside class="wo-editor-sidebar" data-editor-pane="sidebar">
1763
+ <div class="wo-editor-panel">${renderPanelSection("Atlas", "atlas", `<div class="wo-editor-outline" data-editor-outline></div>`)}</div>
1764
+ <div class="wo-editor-panel">${renderPanelSection("Diagnostics", "diagnostics", `<div class="wo-editor-diagnostics" data-editor-diagnostics></div>`)}</div>
1765
+ </aside>
1766
+ <div class="wo-editor-stage-panel">
1767
+ <div class="wo-editor-stage-shell" data-editor-stage-shell>
1768
+ <div class="wo-editor-stage" data-editor-stage></div>
1769
+ <div class="wo-editor-overlay" data-editor-overlay></div>
1770
+ </div>
1771
+ <div class="wo-editor-panel" data-editor-pane="preview">
1772
+ ${renderPanelSection("Preview", "preview", `<div class="wo-editor-preview">
1773
+ <div class="wo-editor-preview-card">
1774
+ <h3>Static SVG</h3>
1775
+ <div class="wo-editor-preview-visual" data-editor-preview-visual></div>
1776
+ </div>
1777
+ <div class="wo-editor-preview-card">
1778
+ <h3>Embed Markup</h3>
1779
+ <pre class="wo-editor-preview-markup" data-editor-preview-markup></pre>
1780
+ </div>
1781
+ </div>`, previewOpen)}
1782
+ </div>
1783
+ </div>
1784
+ <aside class="wo-editor-panel wo-editor-inspector" data-editor-pane="inspector">
1785
+ ${renderPanelSection("Inspector", "inspector", `<div data-editor-inspector></div>`)}
1786
+ </aside>
1787
+ </div>
1788
+ <div class="wo-editor-panel wo-editor-text-panel" data-editor-pane="text">
1789
+ ${renderPanelSection("Source", "source", `<textarea
1790
+ class="wo-editor-source"
1791
+ data-editor-source
1792
+ spellcheck="false"
1793
+ aria-describedby="worldorbit-editor-source-diagnostics"
1794
+ ></textarea>
1795
+ <div
1796
+ class="wo-editor-source-diagnostics"
1797
+ id="worldorbit-editor-source-diagnostics"
1798
+ data-editor-source-diagnostics
1799
+ aria-live="polite"
1800
+ ></div>`)}
1801
+ </div>
1802
+ <div class="wo-editor-live-region" data-editor-live aria-live="polite" aria-atomic="true"></div>
1780
1803
  </section>`;
1781
1804
  }
1782
1805
  function renderPanelSection(title, sectionId, content, open = true) {
1783
- return `<details class="wo-editor-panel-section" data-editor-panel-section="${escapeHtml(sectionId)}"${open ? " open" : ""}>
1784
- <summary><span>${escapeHtml(title)}</span></summary>
1785
- <div class="wo-editor-panel-section-body">${content}</div>
1806
+ return `<details class="wo-editor-panel-section" data-editor-panel-section="${escapeHtml(sectionId)}"${open ? " open" : ""}>
1807
+ <summary><span>${escapeHtml(title)}</span></summary>
1808
+ <div class="wo-editor-panel-section-body">${content}</div>
1786
1809
  </details>`;
1787
1810
  }
1788
1811
  function renderInspectorSection(formId, sectionId, title, content, open = false) {
1789
1812
  const stateKey = inspectorSectionStateKey(formId, sectionId);
1790
- return `<details class="wo-editor-inspector-section" data-editor-form-section="${escapeHtml(sectionId)}" data-editor-form-id="${escapeHtml(formId)}" data-editor-section-state="${escapeHtml(stateKey)}"${open ? " open" : ""}>
1791
- <summary><span>${escapeHtml(title)}</span></summary>
1792
- <div class="wo-editor-inspector-section-body">${content}</div>
1813
+ return `<details class="wo-editor-inspector-section" data-editor-form-section="${escapeHtml(sectionId)}" data-editor-form-id="${escapeHtml(formId)}" data-editor-section-state="${escapeHtml(stateKey)}"${open ? " open" : ""}>
1814
+ <summary><span>${escapeHtml(title)}</span></summary>
1815
+ <div class="wo-editor-inspector-section-body">${content}</div>
1793
1816
  </details>`;
1794
1817
  }
1795
1818
  function renderFieldLabel(label, name) {
@@ -1800,16 +1823,16 @@ function renderFieldHelp(name) {
1800
1823
  if (!help) {
1801
1824
  return "";
1802
1825
  }
1803
- return `<details class="wo-editor-field-help">
1804
- <summary aria-label="Explain ${escapeAttribute(name)}">?</summary>
1805
- <div class="wo-editor-field-help-card">
1806
- <p>${escapeHtml(help.description)}</p>
1826
+ return `<details class="wo-editor-field-help">
1827
+ <summary aria-label="Explain ${escapeAttribute(name)}">?</summary>
1828
+ <div class="wo-editor-field-help-card">
1829
+ <p>${escapeHtml(help.description)}</p>
1807
1830
  ${(help.references?.length ?? 0) > 0
1808
1831
  ? `<div class="wo-editor-field-help-chips">${(help.references ?? [])
1809
1832
  .map((entry) => `<span>${escapeHtml(entry)}</span>`)
1810
1833
  .join("")}</div>`
1811
- : ""}
1812
- </div>
1834
+ : ""}
1835
+ </div>
1813
1836
  </details>`;
1814
1837
  }
1815
1838
  function captureInspectorSectionState(container, state) {
@@ -1851,51 +1874,51 @@ function renderOutlineButton(path, label, activeKey, diagnosticBuckets) {
1851
1874
  return `<button type="button" class="wo-editor-outline-item${key === activeKey ? " is-active" : ""}" data-path-kind="${escapeHtml(path.kind)}"${path.id ? ` data-path-id="${escapeHtml(path.id)}"` : ""}${path.key ? ` data-path-key="${escapeHtml(path.key)}"` : ""}><span>${escapeHtml(label)}</span>${badge}</button>`;
1852
1875
  }
1853
1876
  function renderEventOutlineItems(eventEntry, activeKey, diagnosticBuckets) {
1854
- return `<div class="wo-editor-outline-group">
1855
- ${renderOutlineButton({ kind: "event", id: eventEntry.id }, eventEntry.label || eventEntry.id, activeKey, diagnosticBuckets)}
1877
+ return `<div class="wo-editor-outline-group">
1878
+ ${renderOutlineButton({ kind: "event", id: eventEntry.id }, eventEntry.label || eventEntry.id, activeKey, diagnosticBuckets)}
1856
1879
  ${eventEntry.positions.length > 0
1857
1880
  ? `<div class="wo-editor-outline-children">${[...eventEntry.positions]
1858
1881
  .sort(compareEventPoses)
1859
1882
  .map((pose) => renderOutlineButton({ kind: "event-pose", id: eventEntry.id, key: pose.objectId }, pose.objectId, activeKey, diagnosticBuckets))
1860
1883
  .join("")}</div>`
1861
- : ""}
1884
+ : ""}
1862
1885
  </div>`;
1863
1886
  }
1864
1887
  function renderSystemInspector(formState) {
1865
- return `<form class="wo-editor-form" data-editor-form="system">
1866
- <h2>System</h2>
1867
- ${renderInspectorSection("system", "basics", "Basics", `${renderTextField("System ID", "system-id", formState.system?.id ?? "")}
1868
- ${renderTextField("Title", "system-title", formState.system?.title ?? "")}`, true)}
1888
+ return `<form class="wo-editor-form" data-editor-form="system">
1889
+ <h2>System</h2>
1890
+ ${renderInspectorSection("system", "basics", "Basics", `${renderTextField("System ID", "system-id", formState.system?.id ?? "")}
1891
+ ${renderTextField("Title", "system-title", formState.system?.title ?? "")}`, true)}
1869
1892
  </form>`;
1870
1893
  }
1871
1894
  function renderDefaultsInspector(formState) {
1872
1895
  const defaults = formState.system?.defaults;
1873
- return `<form class="wo-editor-form" data-editor-form="defaults">
1874
- <h2>Defaults</h2>
1896
+ return `<form class="wo-editor-form" data-editor-form="defaults">
1897
+ <h2>Defaults</h2>
1875
1898
  ${renderInspectorSection("defaults", "basics", "Basics", `${renderSelectField("Projection", "defaults-view", [
1876
1899
  ["topdown", "Topdown"],
1877
1900
  ["isometric", "Isometric"],
1878
1901
  ["orthographic", "Orthographic"],
1879
1902
  ["perspective", "Perspective"],
1880
- ], defaults?.view ?? "topdown")}
1881
- ${renderTextField("Scale preset", "defaults-scale", defaults?.scale ?? "")}
1882
- ${renderTextField("Units", "defaults-units", defaults?.units ?? "")}
1903
+ ], defaults?.view ?? "topdown")}
1904
+ ${renderTextField("Scale preset", "defaults-scale", defaults?.scale ?? "")}
1905
+ ${renderTextField("Units", "defaults-units", defaults?.units ?? "")}
1883
1906
  ${renderSelectField("Render preset", "defaults-preset", [
1884
1907
  ["", "Document default"],
1885
1908
  ["diagram", "Diagram"],
1886
1909
  ["presentation", "Presentation"],
1887
1910
  ["atlas-card", "Atlas Card"],
1888
1911
  ["markdown", "Markdown"],
1889
- ], defaults?.preset ?? "")}
1890
- ${renderTextField("Theme", "defaults-theme", defaults?.theme ?? "")}`, true)}
1912
+ ], defaults?.preset ?? "")}
1913
+ ${renderTextField("Theme", "defaults-theme", defaults?.theme ?? "")}`, true)}
1891
1914
  </form>`;
1892
1915
  }
1893
1916
  function renderMetadataInspector(formState, key) {
1894
1917
  const value = formState.system?.atlasMetadata[key] ?? "";
1895
- return `<form class="wo-editor-form" data-editor-form="metadata">
1896
- <h2>Metadata</h2>
1897
- ${renderInspectorSection("metadata", "entry", "Entry", `${renderTextField("Key", "metadata-key", key)}
1898
- ${renderTextAreaField("Value", "metadata-value", value)}`, true)}
1918
+ return `<form class="wo-editor-form" data-editor-form="metadata">
1919
+ <h2>Metadata</h2>
1920
+ ${renderInspectorSection("metadata", "entry", "Entry", `${renderTextField("Key", "metadata-key", key)}
1921
+ ${renderTextAreaField("Value", "metadata-value", value)}`, true)}
1899
1922
  </form>`;
1900
1923
  }
1901
1924
  function renderViewpointInspector(formState, id) {
@@ -1903,49 +1926,49 @@ function renderViewpointInspector(formState, id) {
1903
1926
  if (!viewpoint) {
1904
1927
  return `<p class="wo-editor-empty">Viewpoint not found.</p>`;
1905
1928
  }
1906
- return `<form class="wo-editor-form" data-editor-form="viewpoint">
1907
- <h2>Viewpoint</h2>
1908
- ${renderInspectorSection("viewpoint", "basics", "Basics", `${renderTextField("ID", "viewpoint-id", viewpoint.id)}
1909
- ${renderTextField("Label", "viewpoint-label", viewpoint.label)}
1910
- ${renderTextAreaField("Summary", "viewpoint-summary", viewpoint.summary)}
1911
- ${renderTextField("Focus object", "viewpoint-focus", viewpoint.focusObjectId ?? "")}
1912
- ${renderTextField("Selected object", "viewpoint-select", viewpoint.selectedObjectId ?? "")}
1929
+ return `<form class="wo-editor-form" data-editor-form="viewpoint">
1930
+ <h2>Viewpoint</h2>
1931
+ ${renderInspectorSection("viewpoint", "basics", "Basics", `${renderTextField("ID", "viewpoint-id", viewpoint.id)}
1932
+ ${renderTextField("Label", "viewpoint-label", viewpoint.label)}
1933
+ ${renderTextAreaField("Summary", "viewpoint-summary", viewpoint.summary)}
1934
+ ${renderTextField("Focus object", "viewpoint-focus", viewpoint.focusObjectId ?? "")}
1935
+ ${renderTextField("Selected object", "viewpoint-select", viewpoint.selectedObjectId ?? "")}
1913
1936
  ${renderSelectField("Projection", "viewpoint-projection", [
1914
1937
  ["topdown", "Topdown"],
1915
1938
  ["isometric", "Isometric"],
1916
1939
  ["orthographic", "Orthographic"],
1917
1940
  ["perspective", "Perspective"],
1918
- ], viewpoint.projection)}
1941
+ ], viewpoint.projection)}
1919
1942
  ${renderSelectField("Preset", "viewpoint-preset", [
1920
1943
  ["", "Document default"],
1921
1944
  ["diagram", "Diagram"],
1922
1945
  ["presentation", "Presentation"],
1923
1946
  ["atlas-card", "Atlas Card"],
1924
1947
  ["markdown", "Markdown"],
1925
- ], viewpoint.preset ?? "")}
1926
- ${renderTextField("Zoom", "viewpoint-zoom", viewpoint.zoom === null ? "" : String(viewpoint.zoom))}
1927
- ${renderTextField("Rotation", "viewpoint-rotation", String(viewpoint.rotationDeg))}`, true)}
1928
- ${renderInspectorSection("viewpoint", "camera", "Camera", `${renderTextField("Azimuth", "viewpoint-camera-azimuth", viewpoint.camera?.azimuth === null || viewpoint.camera?.azimuth === undefined ? "" : String(viewpoint.camera.azimuth))}
1929
- ${renderTextField("Elevation", "viewpoint-camera-elevation", viewpoint.camera?.elevation === null || viewpoint.camera?.elevation === undefined ? "" : String(viewpoint.camera.elevation))}
1930
- ${renderTextField("Roll", "viewpoint-camera-roll", viewpoint.camera?.roll === null || viewpoint.camera?.roll === undefined ? "" : String(viewpoint.camera.roll))}
1931
- ${renderTextField("Distance", "viewpoint-camera-distance", viewpoint.camera?.distance === null || viewpoint.camera?.distance === undefined ? "" : String(viewpoint.camera.distance))}
1932
- <p class="wo-editor-inline-note">Rotation stays a 2D screen-rotation hint. The camera block stores Schema 2.5 view direction and framing.</p>`)}
1933
- ${renderInspectorSection("viewpoint", "layers", "Layers", `<fieldset class="wo-editor-fieldset">
1934
- <legend>Layers</legend>
1935
- ${renderCheckboxField("Background", "layer-background", viewpoint.layers.background !== false)}
1936
- ${renderCheckboxField("Guides", "layer-guides", viewpoint.layers.guides !== false)}
1937
- ${renderCheckboxField("Orbits back", "layer-orbits-back", viewpoint.layers["orbits-back"] !== false)}
1938
- ${renderCheckboxField("Orbits front", "layer-orbits-front", viewpoint.layers["orbits-front"] !== false)}
1939
- ${renderCheckboxField("Events", "layer-events", viewpoint.layers.events !== false)}
1940
- ${renderCheckboxField("Objects", "layer-objects", viewpoint.layers.objects !== false)}
1941
- ${renderCheckboxField("Labels", "layer-labels", viewpoint.layers.labels !== false)}
1942
- ${renderCheckboxField("Metadata", "layer-metadata", viewpoint.layers.metadata !== false)}
1943
- </fieldset>`)}
1944
- ${renderInspectorSection("viewpoint", "filter", "Filter", `${renderTextField("Filter query", "filter-query", viewpoint.filter?.query ?? "")}
1945
- ${renderTextField("Filter object types", "filter-object-types", viewpoint.filter?.objectTypes.join(" ") ?? "")}
1946
- ${renderTextField("Filter tags", "filter-tags", viewpoint.filter?.tags.join(" ") ?? "")}
1947
- ${renderTextField("Filter groups", "filter-groups", viewpoint.filter?.groupIds.join(" ") ?? "")}
1948
- ${renderTextField("Events", "viewpoint-events", viewpoint.events.join(" "))}`)}
1948
+ ], viewpoint.preset ?? "")}
1949
+ ${renderTextField("Zoom", "viewpoint-zoom", viewpoint.zoom === null ? "" : String(viewpoint.zoom))}
1950
+ ${renderTextField("Rotation", "viewpoint-rotation", String(viewpoint.rotationDeg))}`, true)}
1951
+ ${renderInspectorSection("viewpoint", "camera", "Camera", `${renderTextField("Azimuth", "viewpoint-camera-azimuth", viewpoint.camera?.azimuth === null || viewpoint.camera?.azimuth === undefined ? "" : String(viewpoint.camera.azimuth))}
1952
+ ${renderTextField("Elevation", "viewpoint-camera-elevation", viewpoint.camera?.elevation === null || viewpoint.camera?.elevation === undefined ? "" : String(viewpoint.camera.elevation))}
1953
+ ${renderTextField("Roll", "viewpoint-camera-roll", viewpoint.camera?.roll === null || viewpoint.camera?.roll === undefined ? "" : String(viewpoint.camera.roll))}
1954
+ ${renderTextField("Distance", "viewpoint-camera-distance", viewpoint.camera?.distance === null || viewpoint.camera?.distance === undefined ? "" : String(viewpoint.camera.distance))}
1955
+ <p class="wo-editor-inline-note">Rotation stays a 2D screen-rotation hint. The camera block stores Schema 3.0 view direction and framing.</p>`)}
1956
+ ${renderInspectorSection("viewpoint", "layers", "Layers", `<fieldset class="wo-editor-fieldset">
1957
+ <legend>Layers</legend>
1958
+ ${renderCheckboxField("Background", "layer-background", viewpoint.layers.background !== false)}
1959
+ ${renderCheckboxField("Guides", "layer-guides", viewpoint.layers.guides !== false)}
1960
+ ${renderCheckboxField("Orbits back", "layer-orbits-back", viewpoint.layers["orbits-back"] !== false)}
1961
+ ${renderCheckboxField("Orbits front", "layer-orbits-front", viewpoint.layers["orbits-front"] !== false)}
1962
+ ${renderCheckboxField("Events", "layer-events", viewpoint.layers.events !== false)}
1963
+ ${renderCheckboxField("Objects", "layer-objects", viewpoint.layers.objects !== false)}
1964
+ ${renderCheckboxField("Labels", "layer-labels", viewpoint.layers.labels !== false)}
1965
+ ${renderCheckboxField("Metadata", "layer-metadata", viewpoint.layers.metadata !== false)}
1966
+ </fieldset>`)}
1967
+ ${renderInspectorSection("viewpoint", "filter", "Filter", `${renderTextField("Filter query", "filter-query", viewpoint.filter?.query ?? "")}
1968
+ ${renderTextField("Filter object types", "filter-object-types", viewpoint.filter?.objectTypes.join(" ") ?? "")}
1969
+ ${renderTextField("Filter tags", "filter-tags", viewpoint.filter?.tags.join(" ") ?? "")}
1970
+ ${renderTextField("Filter groups", "filter-groups", viewpoint.filter?.groupIds.join(" ") ?? "")}
1971
+ ${renderTextField("Events", "viewpoint-events", viewpoint.events.join(" "))}`)}
1949
1972
  </form>`;
1950
1973
  }
1951
1974
  function renderEventInspector(formState, id) {
@@ -1957,30 +1980,32 @@ function renderEventInspector(formState, id) {
1957
1980
  .filter((viewpoint) => viewpoint.events.includes(eventEntry.id))
1958
1981
  .map((viewpoint) => viewpoint.id)
1959
1982
  .join(" ");
1960
- return `<form class="wo-editor-form" data-editor-form="event">
1961
- <h2>Event</h2>
1962
- ${renderInspectorSection("event", "basics", "Basics", `${renderTextField("ID", "event-id", eventEntry.id)}
1963
- ${renderTextField("Kind", "event-kind", eventEntry.kind)}
1964
- ${renderTextField("Label", "event-label", eventEntry.label)}
1965
- ${renderTextAreaField("Summary", "event-summary", eventEntry.summary ?? "")}
1966
- ${renderTextField("Target object", "event-target", eventEntry.targetObjectId ?? "")}
1967
- ${renderTextField("Participants", "event-participants", eventEntry.participantObjectIds.join(" "))}
1968
- ${renderTextField("Timing", "event-timing", eventEntry.timing ?? "")}
1969
- ${renderTextField("Visibility", "event-visibility", eventEntry.visibility ?? "")}
1970
- ${renderTextField("Epoch", "event-epoch", eventEntry.epoch ?? "")}
1971
- ${renderTextField("Reference plane", "event-referencePlane", eventEntry.referencePlane ?? "")}
1972
- ${renderTextField("Tags", "event-tags", eventEntry.tags.join(" "))}
1973
- ${renderTextField("Color", "event-color", eventEntry.color ?? "")}
1974
- ${renderCheckboxField("Hidden", "event-hidden", eventEntry.hidden === true)}`, true)}
1975
- ${renderInspectorSection("event", "viewpoints", "Viewpoints", `${renderTextField("Viewpoints", "event-viewpoints", linkedViewpoints)}`)}
1983
+ return `<form class="wo-editor-form" data-editor-form="event">
1984
+ <h2>Event</h2>
1985
+ ${renderInspectorSection("event", "basics", "Basics", `${renderTextField("ID", "event-id", eventEntry.id)}
1986
+ ${renderTextField("Kind", "event-kind", eventEntry.kind)}
1987
+ ${renderTextField("Label", "event-label", eventEntry.label)}
1988
+ ${renderTextAreaField("Summary", "event-summary", eventEntry.summary ?? "")}
1989
+ ${renderTextField("Target object", "event-target", eventEntry.targetObjectId ?? "")}
1990
+ ${renderTextField("Participants", "event-participants", eventEntry.participantObjectIds.join(" "))}
1991
+ ${renderTextField("Timing", "event-timing", eventEntry.timing ?? "")}
1992
+ ${renderTextField("Visibility", "event-visibility", eventEntry.visibility ?? "")}
1993
+ ${renderTextField("Epoch", "event-epoch", eventEntry.epoch ?? "")}
1994
+ ${renderTextField("Reference plane", "event-referencePlane", eventEntry.referencePlane ?? "")}
1995
+ ${renderTextField("Tags", "event-tags", eventEntry.tags.join(" "))}
1996
+ ${renderTextField("Color", "event-color", eventEntry.color ?? "")}
1997
+ ${renderCheckboxField("Hidden", "event-hidden", eventEntry.hidden === true)}`, true)}
1998
+ ${renderInspectorSection("event", "trajectory", "Trajectory", `${renderTextField("Trajectory", "event-trajectory", eventEntry.trajectory ?? "")}
1999
+ <p class="wo-editor-inline-note">Declarative transfer windows, flybys, and maneuver phases can be attached here. The core remains non-numeric.</p>`)}
2000
+ ${renderInspectorSection("event", "viewpoints", "Viewpoints", `${renderTextField("Viewpoints", "event-viewpoints", linkedViewpoints)}`)}
1976
2001
  ${renderInspectorSection("event", "positions", "Positions", `${eventEntry.positions.length > 0
1977
2002
  ? `<div class="wo-editor-inline-list">${eventEntry.positions
1978
2003
  .map((pose) => renderOutlineButton({ kind: "event-pose", id: eventEntry.id, key: pose.objectId }, pose.objectId, null, new Map()))
1979
2004
  .join("")}</div>`
1980
- : `<p class="wo-editor-empty">No event poses yet.</p>`}
1981
- <div class="wo-editor-inline-actions">
1982
- <button type="button" data-editor-action="add-event-pose" data-editor-event-id="${escapeHtml(eventEntry.id)}">Add pose</button>
1983
- </div>`)}
2005
+ : `<p class="wo-editor-empty">No event poses yet.</p>`}
2006
+ <div class="wo-editor-inline-actions">
2007
+ <button type="button" data-editor-action="add-event-pose" data-editor-event-id="${escapeHtml(eventEntry.id)}">Add pose</button>
2008
+ </div>`)}
1984
2009
  </form>`;
1985
2010
  }
1986
2011
  function renderEventPoseInspector(formState, eventId, objectId) {
@@ -1998,34 +2023,34 @@ function renderEventPoseInspector(formState, eventId, objectId) {
1998
2023
  ? formatUnitValue(pose.placement.distance)
1999
2024
  : pose.placement.descriptor ?? ""
2000
2025
  : "";
2001
- return `<form class="wo-editor-form" data-editor-form="event-pose">
2002
- <h2>Event Pose</h2>
2003
- <p class="wo-editor-inline-note">Event <strong>${escapeHtml(eventEntry.label || eventEntry.id)}</strong></p>
2004
- ${renderInspectorSection("event-pose", "identity", "Identity", `${renderTextField("Object", "pose-object-id", pose.objectId)}
2005
- <div class="wo-editor-inline-actions">
2006
- <button type="button" data-path-kind="event" data-path-id="${escapeHtml(eventEntry.id)}">Select event</button>
2007
- </div>`, true)}
2026
+ return `<form class="wo-editor-form" data-editor-form="event-pose">
2027
+ <h2>Event Pose</h2>
2028
+ <p class="wo-editor-inline-note">Event <strong>${escapeHtml(eventEntry.label || eventEntry.id)}</strong></p>
2029
+ ${renderInspectorSection("event-pose", "identity", "Identity", `${renderTextField("Object", "pose-object-id", pose.objectId)}
2030
+ <div class="wo-editor-inline-actions">
2031
+ <button type="button" data-path-kind="event" data-path-id="${escapeHtml(eventEntry.id)}">Select event</button>
2032
+ </div>`, true)}
2008
2033
  ${renderInspectorSection("event-pose", "placement", "Placement", `${renderSelectField("Placement mode", "placement-mode", [
2009
2034
  ["", "None"],
2010
2035
  ["orbit", "Orbit"],
2011
2036
  ["at", "At"],
2012
2037
  ["surface", "Surface"],
2013
2038
  ["free", "Free"],
2014
- ], placementMode)}
2015
- ${renderTextField("Placement target", "placement-target", placementTarget)}
2016
- ${renderTextField("Free value", "placement-free", freeValue)}
2017
- ${renderTextField("Distance", "placement-distance", pose.placement?.mode === "orbit" && pose.placement.distance ? formatUnitValue(pose.placement.distance) : "")}
2018
- ${renderTextField("Semi-major", "placement-semiMajor", pose.placement?.mode === "orbit" && pose.placement.semiMajor ? formatUnitValue(pose.placement.semiMajor) : "")}
2019
- ${renderTextField("Eccentricity", "placement-eccentricity", pose.placement?.mode === "orbit" && pose.placement.eccentricity !== undefined ? String(pose.placement.eccentricity) : "")}
2020
- ${renderTextField("Period", "placement-period", pose.placement?.mode === "orbit" && pose.placement.period ? formatUnitValue(pose.placement.period) : "")}
2021
- ${renderTextField("Angle", "placement-angle", pose.placement?.mode === "orbit" && pose.placement.angle ? formatUnitValue(pose.placement.angle) : "")}
2022
- ${renderTextField("Inclination", "placement-inclination", pose.placement?.mode === "orbit" && pose.placement.inclination ? formatUnitValue(pose.placement.inclination) : "")}
2023
- ${renderTextField("Phase", "placement-phase", pose.placement?.mode === "orbit" && pose.placement.phase ? formatUnitValue(pose.placement.phase) : "")}
2024
- ${renderTextField("Inner", "prop-inner", pose.inner ? formatUnitValue(pose.inner) : "")}
2025
- ${renderTextField("Outer", "prop-outer", pose.outer ? formatUnitValue(pose.outer) : "")}`, true)}
2026
- ${renderInspectorSection("event-pose", "context", "Context", `${renderTextField("Epoch", "pose-epoch", pose.epoch ?? "")}
2027
- ${renderTextField("Reference plane", "pose-referencePlane", pose.referencePlane ?? "")}
2028
- <p class="wo-editor-inline-note">Falls back to event, then object, then system context when left empty.</p>`)}
2039
+ ], placementMode)}
2040
+ ${renderTextField("Placement target", "placement-target", placementTarget)}
2041
+ ${renderTextField("Free value", "placement-free", freeValue)}
2042
+ ${renderTextField("Distance", "placement-distance", pose.placement?.mode === "orbit" && pose.placement.distance ? formatUnitValue(pose.placement.distance) : "")}
2043
+ ${renderTextField("Semi-major", "placement-semiMajor", pose.placement?.mode === "orbit" && pose.placement.semiMajor ? formatUnitValue(pose.placement.semiMajor) : "")}
2044
+ ${renderTextField("Eccentricity", "placement-eccentricity", pose.placement?.mode === "orbit" && pose.placement.eccentricity !== undefined ? String(pose.placement.eccentricity) : "")}
2045
+ ${renderTextField("Period", "placement-period", pose.placement?.mode === "orbit" && pose.placement.period ? formatUnitValue(pose.placement.period) : "")}
2046
+ ${renderTextField("Angle", "placement-angle", pose.placement?.mode === "orbit" && pose.placement.angle ? formatUnitValue(pose.placement.angle) : "")}
2047
+ ${renderTextField("Inclination", "placement-inclination", pose.placement?.mode === "orbit" && pose.placement.inclination ? formatUnitValue(pose.placement.inclination) : "")}
2048
+ ${renderTextField("Phase", "placement-phase", pose.placement?.mode === "orbit" && pose.placement.phase ? formatUnitValue(pose.placement.phase) : "")}
2049
+ ${renderTextField("Inner", "prop-inner", pose.inner ? formatUnitValue(pose.inner) : "")}
2050
+ ${renderTextField("Outer", "prop-outer", pose.outer ? formatUnitValue(pose.outer) : "")}`, true)}
2051
+ ${renderInspectorSection("event-pose", "context", "Context", `${renderTextField("Epoch", "pose-epoch", pose.epoch ?? "")}
2052
+ ${renderTextField("Reference plane", "pose-referencePlane", pose.referencePlane ?? "")}
2053
+ <p class="wo-editor-inline-note">Falls back to event, then object, then system context when left empty.</p>`)}
2029
2054
  </form>`;
2030
2055
  }
2031
2056
  function renderAnnotationInspector(formState, id) {
@@ -2033,14 +2058,14 @@ function renderAnnotationInspector(formState, id) {
2033
2058
  if (!annotation) {
2034
2059
  return `<p class="wo-editor-empty">Annotation not found.</p>`;
2035
2060
  }
2036
- return `<form class="wo-editor-form" data-editor-form="annotation">
2037
- <h2>Annotation</h2>
2038
- ${renderInspectorSection("annotation", "entry", "Entry", `${renderTextField("ID", "annotation-id", annotation.id)}
2039
- ${renderTextField("Label", "annotation-label", annotation.label)}
2040
- ${renderTextField("Target object", "annotation-target", annotation.targetObjectId ?? "")}
2041
- ${renderTextField("Source object", "annotation-source", annotation.sourceObjectId ?? "")}
2042
- ${renderTextAreaField("Body", "annotation-body", annotation.body)}
2043
- ${renderTextField("Tags", "annotation-tags", annotation.tags.join(" "))}`, true)}
2061
+ return `<form class="wo-editor-form" data-editor-form="annotation">
2062
+ <h2>Annotation</h2>
2063
+ ${renderInspectorSection("annotation", "entry", "Entry", `${renderTextField("ID", "annotation-id", annotation.id)}
2064
+ ${renderTextField("Label", "annotation-label", annotation.label)}
2065
+ ${renderTextField("Target object", "annotation-target", annotation.targetObjectId ?? "")}
2066
+ ${renderTextField("Source object", "annotation-source", annotation.sourceObjectId ?? "")}
2067
+ ${renderTextAreaField("Body", "annotation-body", annotation.body)}
2068
+ ${renderTextField("Tags", "annotation-tags", annotation.tags.join(" "))}`, true)}
2044
2069
  </form>`;
2045
2070
  }
2046
2071
  function renderObjectInspector(formState, id) {
@@ -2057,49 +2082,53 @@ function renderObjectInspector(formState, id) {
2057
2082
  ? formatUnitValue(object.placement.distance)
2058
2083
  : object.placement.descriptor ?? ""
2059
2084
  : "";
2060
- return `<form class="wo-editor-form" data-editor-form="object">
2061
- <h2>Object</h2>
2062
- ${renderInspectorSection("object", "identity", "Identity", `${renderTextField("ID", "object-id", object.id)}
2063
- ${renderSelectField("Type", "object-type", OBJECT_TYPES.map((type) => [type, humanizeIdentifier(type)]), object.type)}`, true)}
2085
+ return `<form class="wo-editor-form" data-editor-form="object">
2086
+ <h2>Object</h2>
2087
+ ${renderInspectorSection("object", "identity", "Identity", `${renderTextField("ID", "object-id", object.id)}
2088
+ ${renderSelectField("Type", "object-type", OBJECT_TYPES.map((type) => [type, humanizeIdentifier(type)]), object.type)}`, true)}
2064
2089
  ${renderInspectorSection("object", "placement", "Placement", `${renderSelectField("Placement mode", "placement-mode", [
2065
2090
  ["", "None"],
2066
2091
  ["orbit", "Orbit"],
2067
2092
  ["at", "At"],
2068
2093
  ["surface", "Surface"],
2069
2094
  ["free", "Free"],
2070
- ], placementMode)}
2071
- ${renderTextField("Placement target", "placement-target", placementTarget)}
2072
- ${renderTextField("Free value", "placement-free", freeValue)}
2073
- ${renderTextField("Distance", "placement-distance", object.placement?.mode === "orbit" && object.placement.distance ? formatUnitValue(object.placement.distance) : "")}
2074
- ${renderTextField("Semi-major", "placement-semiMajor", object.placement?.mode === "orbit" && object.placement.semiMajor ? formatUnitValue(object.placement.semiMajor) : "")}
2075
- ${renderTextField("Eccentricity", "placement-eccentricity", object.placement?.mode === "orbit" && object.placement.eccentricity !== undefined ? String(object.placement.eccentricity) : "")}
2076
- ${renderTextField("Period", "placement-period", object.placement?.mode === "orbit" && object.placement.period ? formatUnitValue(object.placement.period) : "")}
2077
- ${renderTextField("Angle", "placement-angle", object.placement?.mode === "orbit" && object.placement.angle ? formatUnitValue(object.placement.angle) : "")}
2078
- ${renderTextField("Inclination", "placement-inclination", object.placement?.mode === "orbit" && object.placement.inclination ? formatUnitValue(object.placement.inclination) : "")}
2079
- ${renderTextField("Phase", "placement-phase", object.placement?.mode === "orbit" && object.placement.phase ? formatUnitValue(object.placement.phase) : "")}`, true)}
2080
- ${renderInspectorSection("object", "properties", "Properties", `<fieldset class="wo-editor-fieldset">
2081
- <legend>Properties</legend>
2082
- ${renderTextField("Kind", "prop-kind", readStringProperty(object.properties.kind))}
2083
- ${renderTextField("Class", "prop-class", readStringProperty(object.properties.class))}
2084
- ${renderTextField("Culture", "prop-culture", readStringProperty(object.properties.culture))}
2085
- ${renderTextField("Tags", "prop-tags", readTagsProperty(object.properties.tags))}
2086
- ${renderTextField("Color", "prop-color", readStringProperty(object.properties.color))}
2087
- ${renderTextField("Image", "prop-image", readStringProperty(object.properties.image))}
2088
- ${renderCheckboxField("Hidden", "prop-hidden", object.properties.hidden === true)}
2089
- ${renderTextField("Radius", "prop-radius", readUnitProperty(object.properties.radius))}
2090
- ${renderTextField("Mass", "prop-mass", readUnitProperty(object.properties.mass))}
2091
- ${renderTextField("Density", "prop-density", readUnitProperty(object.properties.density))}
2092
- ${renderTextField("Gravity", "prop-gravity", readUnitProperty(object.properties.gravity))}
2093
- ${renderTextField("Temperature", "prop-temperature", readUnitProperty(object.properties.temperature))}
2094
- ${renderTextField("Albedo", "prop-albedo", readNumberProperty(object.properties.albedo))}
2095
- ${renderTextField("Atmosphere", "prop-atmosphere", readStringProperty(object.properties.atmosphere))}
2096
- ${renderTextField("Inner", "prop-inner", readUnitProperty(object.properties.inner))}
2097
- ${renderTextField("Outer", "prop-outer", readUnitProperty(object.properties.outer))}
2098
- ${renderTextField("On", "prop-on", readStringProperty(object.properties.on))}
2099
- ${renderTextField("Source", "prop-source", readStringProperty(object.properties.source))}
2100
- ${renderTextField("Cycle", "prop-cycle", readUnitProperty(object.properties.cycle))}
2101
- </fieldset>`)}
2102
- ${renderInspectorSection("object", "info", "Info", `${renderTextAreaField("Description", "info-description", object.info.description ?? "")}`)}
2095
+ ], placementMode)}
2096
+ ${renderTextField("Placement target", "placement-target", placementTarget)}
2097
+ ${renderTextField("Free value", "placement-free", freeValue)}
2098
+ ${renderTextField("Distance", "placement-distance", object.placement?.mode === "orbit" && object.placement.distance ? formatUnitValue(object.placement.distance) : "")}
2099
+ ${renderTextField("Semi-major", "placement-semiMajor", object.placement?.mode === "orbit" && object.placement.semiMajor ? formatUnitValue(object.placement.semiMajor) : "")}
2100
+ ${renderTextField("Eccentricity", "placement-eccentricity", object.placement?.mode === "orbit" && object.placement.eccentricity !== undefined ? String(object.placement.eccentricity) : "")}
2101
+ ${renderTextField("Period", "placement-period", object.placement?.mode === "orbit" && object.placement.period ? formatUnitValue(object.placement.period) : "")}
2102
+ ${renderTextField("Angle", "placement-angle", object.placement?.mode === "orbit" && object.placement.angle ? formatUnitValue(object.placement.angle) : "")}
2103
+ ${renderTextField("Inclination", "placement-inclination", object.placement?.mode === "orbit" && object.placement.inclination ? formatUnitValue(object.placement.inclination) : "")}
2104
+ ${renderTextField("Phase", "placement-phase", object.placement?.mode === "orbit" && object.placement.phase ? formatUnitValue(object.placement.phase) : "")}`, true)}
2105
+ ${object.type === "craft"
2106
+ ? renderInspectorSection("object", "trajectory", "Trajectory", `${renderTextField("Trajectory", "object-trajectory", object.trajectory ?? "")}
2107
+ <p class="wo-editor-inline-note">Crafts can reference declarative trajectory definitions here. Swing-by and transfer details stay in the DSL, not the editor canvas.</p>`)
2108
+ : ""}
2109
+ ${renderInspectorSection("object", "properties", "Properties", `<fieldset class="wo-editor-fieldset">
2110
+ <legend>Properties</legend>
2111
+ ${renderTextField("Kind", "prop-kind", readStringProperty(object.properties.kind))}
2112
+ ${renderTextField("Class", "prop-class", readStringProperty(object.properties.class))}
2113
+ ${renderTextField("Culture", "prop-culture", readStringProperty(object.properties.culture))}
2114
+ ${renderTextField("Tags", "prop-tags", readTagsProperty(object.properties.tags))}
2115
+ ${renderTextField("Color", "prop-color", readStringProperty(object.properties.color))}
2116
+ ${renderTextField("Image", "prop-image", readStringProperty(object.properties.image))}
2117
+ ${renderCheckboxField("Hidden", "prop-hidden", object.properties.hidden === true)}
2118
+ ${renderTextField("Radius", "prop-radius", readUnitProperty(object.properties.radius))}
2119
+ ${renderTextField("Mass", "prop-mass", readUnitProperty(object.properties.mass))}
2120
+ ${renderTextField("Density", "prop-density", readUnitProperty(object.properties.density))}
2121
+ ${renderTextField("Gravity", "prop-gravity", readUnitProperty(object.properties.gravity))}
2122
+ ${renderTextField("Temperature", "prop-temperature", readUnitProperty(object.properties.temperature))}
2123
+ ${renderTextField("Albedo", "prop-albedo", readNumberProperty(object.properties.albedo))}
2124
+ ${renderTextField("Atmosphere", "prop-atmosphere", readStringProperty(object.properties.atmosphere))}
2125
+ ${renderTextField("Inner", "prop-inner", readUnitProperty(object.properties.inner))}
2126
+ ${renderTextField("Outer", "prop-outer", readUnitProperty(object.properties.outer))}
2127
+ ${renderTextField("On", "prop-on", readStringProperty(object.properties.on))}
2128
+ ${renderTextField("Source", "prop-source", readStringProperty(object.properties.source))}
2129
+ ${renderTextField("Cycle", "prop-cycle", readUnitProperty(object.properties.cycle))}
2130
+ </fieldset>`)}
2131
+ ${renderInspectorSection("object", "info", "Info", `${renderTextAreaField("Description", "info-description", object.info.description ?? "")}`)}
2103
2132
  </form>`;
2104
2133
  }
2105
2134
  function renderTextField(label, name, value) {
@@ -2249,8 +2278,20 @@ function createNewObject(type, id, document) {
2249
2278
  const orbitTarget = document.objects.find((object) => object.type === "star")?.id ??
2250
2279
  document.objects[0]?.id ??
2251
2280
  id;
2281
+ if (type === "craft") {
2282
+ return {
2283
+ type: type,
2284
+ id,
2285
+ properties: {},
2286
+ placement: {
2287
+ mode: "free",
2288
+ distance: { value: 1, unit: "au" },
2289
+ },
2290
+ info: {},
2291
+ };
2292
+ }
2252
2293
  return {
2253
- type,
2294
+ type: type,
2254
2295
  id,
2255
2296
  properties: {},
2256
2297
  placement: type === "structure" || type === "phenomenon"
@@ -2488,7 +2529,7 @@ function updateOrbitRadius(document, path, objectId, scene, details, pointer, dr
2488
2529
  value: 1,
2489
2530
  unit: "au",
2490
2531
  };
2491
- const scaled = distanceMetricToUnitValue(Math.max(nextMetric, 0), dragContext.preferredUnit ?? currentValue.unit);
2532
+ const scaled = distanceMetricToUnitValue(Math.max(nextMetric, 0), resolveOrbitDistanceUnit(document, details, Math.max(nextMetric, 0), dragContext.preferredUnit ?? currentValue.unit));
2492
2533
  applyOrbitDistanceValue(orbitPlacementOwner, scaled);
2493
2534
  const targetDisplayedRadius = nextDisplayedRadius;
2494
2535
  let correctedMetric = nextMetric;
@@ -2513,7 +2554,7 @@ function updateOrbitRadius(document, path, objectId, scene, details, pointer, dr
2513
2554
  }
2514
2555
  const correctionFactor = targetDisplayedRadius / Math.max(renderedRadius, 1);
2515
2556
  correctedMetric *= Math.max(correctionFactor, 1.02);
2516
- applyOrbitDistanceValue(orbitPlacementOwner, distanceMetricToUnitValue(Math.max(correctedMetric, 0), dragContext.preferredUnit ?? currentValue.unit));
2557
+ applyOrbitDistanceValue(orbitPlacementOwner, distanceMetricToUnitValue(Math.max(correctedMetric, 0), resolveOrbitDistanceUnit(next, details, Math.max(correctedMetric, 0), dragContext.preferredUnit ?? currentValue.unit)));
2517
2558
  }
2518
2559
  return next;
2519
2560
  }
@@ -3002,6 +3043,10 @@ function mapDiagnosticFieldToInputNames(selection, field) {
3002
3043
  default:
3003
3044
  return [];
3004
3045
  }
3046
+ case "trajectory":
3047
+ case "trajectory-segment":
3048
+ case "trajectory-maneuver":
3049
+ return [];
3005
3050
  case "object":
3006
3051
  if (field === "id") {
3007
3052
  return ["object-id"];
@@ -3028,6 +3073,8 @@ function mapDiagnosticFieldToInputNames(selection, field) {
3028
3073
  return [`placement-${field}`];
3029
3074
  }
3030
3075
  return [`prop-${field}`];
3076
+ default:
3077
+ return [];
3031
3078
  }
3032
3079
  }
3033
3080
  function buildEmbedMarkup(source, document, viewMode) {
@@ -3051,6 +3098,12 @@ function describePath(path) {
3051
3098
  return `Event: ${path.id ?? ""}`;
3052
3099
  case "event-pose":
3053
3100
  return `Event Pose: ${path.id ?? ""} / ${path.key ?? ""}`;
3101
+ case "trajectory":
3102
+ return `Trajectory: ${path.id ?? ""}`;
3103
+ case "trajectory-segment":
3104
+ return `Trajectory Segment: ${path.id ?? ""} / ${path.key ?? ""}`;
3105
+ case "trajectory-maneuver":
3106
+ return `Trajectory Maneuver: ${path.id ?? ""} / ${path.key ?? ""}`;
3054
3107
  case "object":
3055
3108
  return `Object: ${path.id ?? ""}`;
3056
3109
  case "viewpoint":
@@ -3059,6 +3112,8 @@ function describePath(path) {
3059
3112
  return `Annotation: ${path.id ?? ""}`;
3060
3113
  case "relation":
3061
3114
  return `Relation: ${path.id ?? ""}`;
3115
+ default:
3116
+ return "Selection";
3062
3117
  }
3063
3118
  }
3064
3119
  function selectionKey(path) {
@@ -3197,6 +3252,53 @@ function normalizeFreeDistanceUnit(unit) {
3197
3252
  return null;
3198
3253
  }
3199
3254
  }
3255
+ function resolveOrbitDistanceUnit(document, details, metric, currentUnit) {
3256
+ const normalizedMetric = Math.max(metric, 0);
3257
+ const objectType = details.object.type;
3258
+ const parentType = details.parent ? findObject(document, details.parent.objectId)?.type ?? null : null;
3259
+ const nextPreferredUnit = inferOrbitDistanceUnit(objectType, parentType, normalizedMetric);
3260
+ if (isOrbitDistanceUnitPlausible(normalizedMetric, currentUnit, nextPreferredUnit)) {
3261
+ return currentUnit;
3262
+ }
3263
+ return nextPreferredUnit;
3264
+ }
3265
+ function inferOrbitDistanceUnit(objectType, parentType, metric) {
3266
+ if (objectType === "ring") {
3267
+ return "km";
3268
+ }
3269
+ if (objectType === "moon" ||
3270
+ parentType === "planet" ||
3271
+ parentType === "moon" ||
3272
+ parentType === "asteroid" ||
3273
+ parentType === "comet" ||
3274
+ metric <= 0.02) {
3275
+ return "km";
3276
+ }
3277
+ return "au";
3278
+ }
3279
+ function isOrbitDistanceUnitPlausible(metric, unit, preferredUnit) {
3280
+ if (unit === preferredUnit) {
3281
+ return true;
3282
+ }
3283
+ switch (unit) {
3284
+ case "au":
3285
+ return metric >= 0.03;
3286
+ case "km":
3287
+ return metric <= 0.03;
3288
+ case "re": {
3289
+ const earthRadii = (metric * AU_IN_KM) / EARTH_RADIUS_IN_KM;
3290
+ return earthRadii >= 0.25 && earthRadii <= 400;
3291
+ }
3292
+ case "sol": {
3293
+ const solarRadii = (metric * AU_IN_KM) / SOLAR_RADIUS_IN_KM;
3294
+ return solarRadii >= 0.05 && solarRadii <= 120;
3295
+ }
3296
+ case null:
3297
+ return false;
3298
+ default:
3299
+ return false;
3300
+ }
3301
+ }
3200
3302
  function unitValueToDistanceMetric(value) {
3201
3303
  if (!value) {
3202
3304
  return null;
@@ -3284,460 +3386,483 @@ function installEditorStyles() {
3284
3386
  }
3285
3387
  const style = document.createElement("style");
3286
3388
  style.id = STYLE_ID;
3287
- style.textContent = `
3288
- .wo-editor {
3289
- --wo-editor-sidebar-width: 280px;
3290
- --wo-editor-inspector-width: 360px;
3291
- --wo-editor-source-height: 280px;
3292
- }
3293
- .wo-editor-shell { display: grid; gap: 16px; min-width: 0; }
3294
- .wo-editor-toolbar { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; min-width: 0; }
3295
- .wo-editor-toolbar-group { display: flex; gap: 10px; flex-wrap: wrap; min-width: 0; }
3296
- .wo-editor-toolbar button, .wo-editor-toolbar select {
3297
- border: 1px solid rgba(240, 180, 100, 0.2);
3298
- border-radius: 999px;
3299
- background: rgba(240, 180, 100, 0.08);
3300
- color: #edf6ff;
3301
- font: 600 13px/1.4 "Segoe UI Variable", "Segoe UI", sans-serif;
3302
- padding: 10px 14px;
3303
- }
3304
- .wo-editor button:focus-visible,
3305
- .wo-editor select:focus-visible,
3306
- .wo-editor input:focus-visible,
3307
- .wo-editor textarea:focus-visible {
3308
- outline: 2px solid rgba(255, 214, 138, 0.95);
3309
- outline-offset: 2px;
3310
- }
3311
- .wo-editor-toolbar button:disabled { opacity: 0.45; cursor: not-allowed; }
3312
- .wo-editor-status {
3313
- display: flex;
3314
- flex-wrap: wrap;
3315
- gap: 10px;
3316
- min-width: 0;
3317
- }
3318
- .wo-editor-status-pill {
3319
- border: 1px solid rgba(255,255,255,0.08);
3320
- border-radius: 999px;
3321
- background: rgba(255,255,255,0.04);
3322
- color: #edf6ff;
3323
- font: 600 12px/1.4 "Segoe UI Variable", "Segoe UI", sans-serif;
3324
- padding: 8px 12px;
3325
- }
3326
- .wo-editor-status-pill.is-clean { border-color: rgba(120, 255, 195, 0.22); color: #c7ffe5; }
3327
- .wo-editor-status-pill.is-dirty { border-color: rgba(240, 180, 100, 0.28); color: #ffd58a; }
3328
- .wo-editor-status-pill.is-warning { border-color: rgba(240, 180, 100, 0.28); color: #ffd58a; }
3329
- .wo-editor-status-pill.is-error { border-color: rgba(255, 120, 120, 0.3); color: #ffb2b2; }
3330
- .wo-editor-main {
3331
- display: grid;
3332
- grid-template-columns:
3333
- minmax(220px, var(--wo-editor-sidebar-width))
3334
- minmax(0, 1fr)
3335
- minmax(280px, var(--wo-editor-inspector-width));
3336
- gap: 16px;
3337
- min-width: 0;
3338
- align-items: start;
3339
- }
3340
- .wo-editor-sidebar, .wo-editor-stage-panel, .wo-editor-preview { display: grid; gap: 16px; min-width: 0; }
3341
- .wo-editor-panel {
3342
- border-radius: 24px;
3343
- border: 1px solid rgba(255,255,255,0.08);
3344
- background: rgba(7, 16, 25, 0.72);
3345
- padding: 18px;
3346
- min-width: 0;
3347
- }
3348
- .wo-editor-panel h2 {
3349
- margin: 0 0 14px;
3350
- color: #edf6ff;
3351
- font: 700 14px/1.2 "Segoe UI Variable Display", "Segoe UI", sans-serif;
3352
- text-transform: uppercase;
3353
- letter-spacing: 0.08em;
3354
- }
3355
- .wo-editor-panel h3 {
3356
- margin: 0 0 12px;
3357
- color: rgba(237, 246, 255, 0.82);
3358
- font: 700 12px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
3359
- text-transform: uppercase;
3360
- letter-spacing: 0.08em;
3361
- }
3362
- .wo-editor-panel-section,
3363
- .wo-editor-inspector-section {
3364
- min-width: 0;
3365
- }
3366
- .wo-editor-panel-section > summary,
3367
- .wo-editor-inspector-section > summary {
3368
- display: flex;
3369
- align-items: center;
3370
- justify-content: space-between;
3371
- gap: 10px;
3372
- cursor: pointer;
3373
- list-style: none;
3374
- color: #edf6ff;
3375
- font: 700 14px/1.2 "Segoe UI Variable Display", "Segoe UI", sans-serif;
3376
- text-transform: uppercase;
3377
- letter-spacing: 0.08em;
3378
- }
3379
- .wo-editor-inspector-section > summary {
3380
- font: 700 12px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
3381
- color: rgba(237, 246, 255, 0.9);
3382
- }
3383
- .wo-editor-panel-section > summary::-webkit-details-marker,
3384
- .wo-editor-inspector-section > summary::-webkit-details-marker,
3385
- .wo-editor-field-help > summary::-webkit-details-marker {
3386
- display: none;
3387
- }
3388
- .wo-editor-panel-section > summary::after,
3389
- .wo-editor-inspector-section > summary::after {
3390
- content: "▾";
3391
- color: rgba(240, 180, 100, 0.86);
3392
- font-size: 12px;
3393
- transition: transform 0.18s ease;
3394
- }
3395
- .wo-editor-panel-section:not([open]) > summary::after,
3396
- .wo-editor-inspector-section:not([open]) > summary::after {
3397
- transform: rotate(-90deg);
3398
- }
3399
- .wo-editor-panel-section-body {
3400
- display: grid;
3401
- gap: 16px;
3402
- margin-top: 14px;
3403
- min-width: 0;
3404
- }
3405
- .wo-editor-inspector-section-body {
3406
- display: grid;
3407
- gap: 12px;
3408
- margin-top: 12px;
3409
- min-width: 0;
3410
- }
3411
- .wo-editor[data-wo-show-inspector="false"] [data-editor-pane="inspector"] { display: none; }
3412
- .wo-editor[data-wo-show-text-pane="false"] [data-editor-pane="text"] { display: none; }
3413
- .wo-editor[data-wo-show-preview="false"] [data-editor-pane="preview"] { display: none; }
3414
- .wo-editor-stage-shell {
3415
- position: relative;
3416
- min-width: 0;
3417
- border-radius: 26px;
3418
- overflow: hidden;
3419
- border: 1px solid rgba(255,255,255,0.08);
3420
- background: rgba(8, 17, 28, 0.9);
3421
- }
3422
- .wo-editor-stage { min-height: 620px; min-width: 0; }
3423
- .wo-editor-overlay { position: absolute; inset: 0; pointer-events: none; }
3424
- .wo-editor-overlay > * { pointer-events: auto; }
3425
- .wo-editor-handle {
3426
- position: absolute;
3427
- transform: translate(-50%, -50%);
3428
- border: 0;
3429
- border-radius: 999px;
3430
- background: rgba(255, 214, 138, 0.92);
3431
- color: #071019;
3432
- cursor: grab;
3433
- font: 700 11px/1 "Segoe UI Variable", "Segoe UI", sans-serif;
3434
- padding: 8px 10px;
3435
- box-shadow: 0 10px 24px rgba(0,0,0,0.24);
3436
- }
3437
- .wo-editor-hint {
3438
- position: absolute;
3439
- transform: translate(-50%, -50%);
3440
- padding: 4px 8px;
3441
- border-radius: 999px;
3442
- background: rgba(18, 39, 58, 0.84);
3443
- color: #edf6ff;
3444
- font: 600 11px/1.3 "Segoe UI Variable", "Segoe UI", sans-serif;
3445
- white-space: nowrap;
3446
- box-shadow: 0 6px 18px rgba(0,0,0,0.22);
3447
- }
3448
- .wo-editor-hint.is-subtle { background: rgba(18, 39, 58, 0.64); color: rgba(237, 246, 255, 0.78); }
3449
- .wo-editor-hint-note { left: 16px; top: 16px; transform: none; }
3450
- .wo-editor-overlay-diagnostics {
3451
- position: absolute;
3452
- right: 16px;
3453
- top: 16px;
3454
- display: grid;
3455
- gap: 8px;
3456
- max-width: min(320px, calc(100% - 32px));
3457
- }
3458
- .wo-editor-overlay-diagnostic {
3459
- border-radius: 14px;
3460
- padding: 10px 12px;
3461
- background: rgba(10, 20, 32, 0.92);
3462
- box-shadow: 0 12px 30px rgba(0,0,0,0.28);
3463
- }
3464
- .wo-editor-overlay-diagnostic strong {
3465
- display: block;
3466
- margin-bottom: 4px;
3467
- font: 700 11px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
3468
- letter-spacing: 0.08em;
3469
- text-transform: uppercase;
3470
- }
3471
- .wo-editor-overlay-diagnostic p {
3472
- margin: 0;
3473
- color: #edf6ff;
3474
- font: 500 12px/1.4 "Segoe UI Variable", "Segoe UI", sans-serif;
3475
- }
3476
- .wo-editor-overlay-diagnostic-error { border: 1px solid rgba(255, 120, 120, 0.28); }
3477
- .wo-editor-overlay-diagnostic-warning { border: 1px solid rgba(240, 180, 100, 0.24); }
3478
- .wo-editor-outline { display: grid; gap: 14px; }
3479
- .wo-editor-outline-section { display: grid; gap: 8px; }
3480
- .wo-editor-outline-group { display: grid; gap: 6px; }
3481
- .wo-editor-outline-children {
3482
- display: grid;
3483
- gap: 6px;
3484
- padding-left: 16px;
3485
- }
3486
- .wo-editor-outline-section h3 {
3487
- margin: 0;
3488
- color: rgba(237,246,255,0.68);
3489
- font: 600 11px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
3490
- text-transform: uppercase;
3491
- letter-spacing: 0.08em;
3492
- }
3493
- .wo-editor-outline-item {
3494
- border: 1px solid transparent;
3495
- border-radius: 16px;
3496
- background: rgba(255,255,255,0.04);
3497
- color: #edf6ff;
3498
- cursor: pointer;
3499
- font: 500 13px/1.4 "Segoe UI Variable", "Segoe UI", sans-serif;
3500
- padding: 10px 12px;
3501
- text-align: left;
3502
- display: flex;
3503
- align-items: center;
3504
- justify-content: space-between;
3505
- gap: 10px;
3506
- }
3507
- .wo-editor-outline-item.is-active {
3508
- border-color: rgba(255, 214, 138, 0.34);
3509
- background: rgba(255, 214, 138, 0.12);
3510
- color: #ffdda9;
3511
- }
3512
- .wo-editor-outline-badge {
3513
- min-width: 22px;
3514
- border-radius: 999px;
3515
- background: rgba(240, 180, 100, 0.16);
3516
- color: #ffd58a;
3517
- font: 700 11px/1 "Segoe UI Variable", "Segoe UI", sans-serif;
3518
- padding: 5px 7px;
3519
- text-align: center;
3520
- }
3521
- .wo-editor-outline-badge.is-error {
3522
- background: rgba(255, 120, 120, 0.18);
3523
- color: #ffb2b2;
3524
- }
3525
- .wo-editor-inline-list { display: grid; gap: 8px; }
3526
- .wo-editor-inline-actions {
3527
- display: flex;
3528
- flex-wrap: wrap;
3529
- gap: 10px;
3530
- margin-top: 12px;
3531
- }
3532
- .wo-editor-inline-actions button {
3533
- border: 1px solid rgba(255,255,255,0.14);
3534
- border-radius: 999px;
3535
- background: rgba(255,255,255,0.06);
3536
- color: #edf6ff;
3537
- cursor: pointer;
3538
- font: 600 12px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
3539
- padding: 8px 12px;
3540
- }
3541
- .wo-editor-inline-note {
3542
- margin: 0 0 12px;
3543
- color: rgba(237,246,255,0.72);
3544
- font: 500 12px/1.5 "Segoe UI Variable", "Segoe UI", sans-serif;
3545
- }
3546
- .wo-editor-diagnostics { display: grid; gap: 10px; }
3547
- .wo-editor-diagnostic {
3548
- display: grid;
3549
- gap: 4px;
3550
- border-radius: 16px;
3551
- padding: 12px;
3552
- background: rgba(255,255,255,0.04);
3553
- color: #edf6ff;
3554
- font: 500 12px/1.45 "Segoe UI Variable", "Segoe UI", sans-serif;
3555
- }
3556
- .wo-editor-diagnostic strong { font-size: 11px; letter-spacing: 0.08em; }
3557
- .wo-editor-diagnostic p { margin: 0; color: rgba(237,246,255,0.82); }
3558
- .wo-editor-diagnostic-warning { border: 1px solid rgba(240, 180, 100, 0.22); }
3559
- .wo-editor-diagnostic-error { border: 1px solid rgba(255, 120, 120, 0.24); }
3560
- .wo-editor-inspector-summary {
3561
- display: grid;
3562
- gap: 10px;
3563
- margin-bottom: 14px;
3564
- }
3565
- .wo-editor-form { display: grid; gap: 12px; min-width: 0; }
3566
- .wo-editor-inspector-section {
3567
- border: 1px solid rgba(255,255,255,0.08);
3568
- border-radius: 18px;
3569
- padding: 14px;
3570
- background: rgba(255,255,255,0.02);
3571
- }
3572
- .wo-editor-field { display: grid; gap: 6px; }
3573
- .wo-editor-field-label,
3574
- .wo-editor-fieldset legend {
3575
- display: flex;
3576
- align-items: center;
3577
- justify-content: space-between;
3578
- gap: 10px;
3579
- }
3580
- .wo-editor-field-label,
3581
- .wo-editor-checkbox .wo-editor-field-label,
3582
- .wo-editor-fieldset legend {
3583
- color: rgba(237,246,255,0.72);
3584
- font: 600 11px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
3585
- text-transform: uppercase;
3586
- letter-spacing: 0.08em;
3587
- }
3588
- .wo-editor-field input, .wo-editor-field select, .wo-editor-field textarea, .wo-editor-source {
3589
- width: 100%;
3590
- min-width: 0;
3591
- border: 1px solid rgba(255,255,255,0.08);
3592
- border-radius: 14px;
3593
- background: rgba(12, 26, 39, 0.84);
3594
- color: #edf6ff;
3595
- font: 500 13px/1.5 "Segoe UI Variable", "Segoe UI", sans-serif;
3596
- padding: 10px 12px;
3597
- box-sizing: border-box;
3598
- }
3599
- .wo-editor-field.has-error input,
3600
- .wo-editor-field.has-error select,
3601
- .wo-editor-field.has-error textarea,
3602
- .wo-editor-checkbox.has-error {
3603
- border-color: rgba(255, 120, 120, 0.44);
3604
- }
3605
- .wo-editor-field.has-warning input,
3606
- .wo-editor-field.has-warning select,
3607
- .wo-editor-field.has-warning textarea,
3608
- .wo-editor-checkbox.has-warning {
3609
- border-color: rgba(240, 180, 100, 0.42);
3610
- }
3611
- .wo-editor-field textarea, .wo-editor-source { min-height: 110px; resize: vertical; }
3612
- .wo-editor-source {
3613
- min-height: var(--wo-editor-source-height);
3614
- font-family: "Cascadia Code", "Consolas", monospace;
3615
- white-space: pre;
3616
- overflow: auto;
3617
- }
3618
- .wo-editor-field-note {
3619
- color: rgba(237,246,255,0.72);
3620
- font: 500 12px/1.45 "Segoe UI Variable", "Segoe UI", sans-serif;
3621
- }
3622
- .wo-editor-field-note.is-error { color: #ffb2b2; }
3623
- .wo-editor-field-note.is-warning { color: #ffd58a; }
3624
- .wo-editor-fieldset {
3625
- display: grid;
3626
- gap: 10px;
3627
- border: 1px solid rgba(255,255,255,0.08);
3628
- border-radius: 18px;
3629
- padding: 14px;
3630
- min-width: 0;
3631
- }
3632
- .wo-editor-checkbox {
3633
- display: flex;
3634
- gap: 10px;
3635
- align-items: flex-start;
3636
- color: #edf6ff;
3637
- font: 500 13px/1.4 "Segoe UI Variable", "Segoe UI", sans-serif;
3638
- }
3639
- .wo-editor-checkbox input {
3640
- margin-top: 2px;
3641
- }
3642
- .wo-editor-field-help {
3643
- position: relative;
3644
- flex: 0 0 auto;
3645
- }
3646
- .wo-editor-field-help > summary {
3647
- display: inline-flex;
3648
- align-items: center;
3649
- justify-content: center;
3650
- width: 20px;
3651
- height: 20px;
3652
- border-radius: 999px;
3653
- border: 1px solid rgba(240, 180, 100, 0.28);
3654
- background: rgba(240, 180, 100, 0.12);
3655
- color: #ffdda9;
3656
- cursor: pointer;
3657
- font: 700 11px/1 "Segoe UI Variable", "Segoe UI", sans-serif;
3658
- list-style: none;
3659
- }
3660
- .wo-editor-field-help-card {
3661
- display: grid;
3662
- gap: 8px;
3663
- margin-top: 8px;
3664
- padding: 10px 12px;
3665
- border-radius: 14px;
3666
- border: 1px solid rgba(240, 180, 100, 0.2);
3667
- background: rgba(12, 26, 39, 0.92);
3668
- color: rgba(237, 246, 255, 0.86);
3669
- text-transform: none;
3670
- letter-spacing: normal;
3671
- font: 500 12px/1.45 "Segoe UI Variable", "Segoe UI", sans-serif;
3672
- }
3673
- .wo-editor-field-help-card p {
3674
- margin: 0;
3675
- }
3676
- .wo-editor-field-help-chips {
3677
- display: flex;
3678
- flex-wrap: wrap;
3679
- gap: 6px;
3680
- }
3681
- .wo-editor-field-help-chips span {
3682
- border-radius: 999px;
3683
- padding: 4px 8px;
3684
- background: rgba(255,255,255,0.06);
3685
- color: #edf6ff;
3686
- font: 600 11px/1.3 "Segoe UI Variable", "Segoe UI", sans-serif;
3687
- text-transform: none;
3688
- letter-spacing: normal;
3689
- }
3690
- .wo-editor-preview { grid-template-columns: repeat(2, minmax(0, 1fr)); }
3691
- .wo-editor-preview-card {
3692
- border-radius: 18px;
3693
- border: 1px solid rgba(255,255,255,0.08);
3694
- background: rgba(255,255,255,0.03);
3695
- padding: 14px;
3696
- min-width: 0;
3697
- }
3698
- .wo-editor-preview-visual {
3699
- min-height: 240px;
3700
- overflow: auto;
3701
- }
3702
- .wo-editor-preview-markup {
3703
- margin: 0;
3704
- min-height: 240px;
3705
- white-space: pre-wrap;
3706
- overflow-wrap: anywhere;
3707
- color: #edf6ff;
3708
- font: 12px/1.5 "Cascadia Code", "Consolas", monospace;
3709
- }
3710
- .wo-editor-source-diagnostics {
3711
- display: grid;
3712
- gap: 10px;
3713
- margin-top: 14px;
3714
- }
3715
- .wo-editor-empty {
3716
- margin: 0;
3717
- color: rgba(237,246,255,0.68);
3718
- font: 500 12px/1.5 "Segoe UI Variable", "Segoe UI", sans-serif;
3719
- }
3720
- .wo-editor-live-region {
3721
- position: absolute;
3722
- width: 1px;
3723
- height: 1px;
3724
- padding: 0;
3725
- margin: -1px;
3726
- overflow: hidden;
3727
- clip: rect(0, 0, 0, 0);
3728
- white-space: nowrap;
3729
- border: 0;
3730
- }
3731
- @media (max-width: 1280px) {
3732
- .wo-editor-main { grid-template-columns: minmax(220px, 260px) minmax(0, 1fr); }
3733
- .wo-editor-inspector { grid-column: 1 / -1; }
3734
- .wo-editor-preview { grid-template-columns: 1fr; }
3735
- }
3736
- @media (max-width: 960px) {
3737
- .wo-editor-main { grid-template-columns: 1fr; }
3738
- .wo-editor-stage { min-height: 440px; }
3739
- .wo-editor-status { flex-direction: column; }
3740
- }
3389
+ style.textContent = `
3390
+ .wo-editor {
3391
+ --wo-editor-sidebar-width: 280px;
3392
+ --wo-editor-inspector-width: 360px;
3393
+ --wo-editor-source-height: 280px;
3394
+ }
3395
+ .wo-editor-shell { display: grid; gap: 16px; min-width: 0; }
3396
+ .wo-editor-toolbar { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; min-width: 0; }
3397
+ .wo-editor-toolbar-group { display: flex; gap: 10px; flex-wrap: wrap; min-width: 0; }
3398
+ .wo-editor-toolbar button, .wo-editor-toolbar select {
3399
+ border: 1px solid rgba(240, 180, 100, 0.2);
3400
+ border-radius: 999px;
3401
+ background: rgba(240, 180, 100, 0.08);
3402
+ color: #edf6ff;
3403
+ font: 600 13px/1.4 "Segoe UI Variable", "Segoe UI", sans-serif;
3404
+ padding: 10px 14px;
3405
+ }
3406
+ .wo-editor button:focus-visible,
3407
+ .wo-editor select:focus-visible,
3408
+ .wo-editor input:focus-visible,
3409
+ .wo-editor textarea:focus-visible {
3410
+ outline: 2px solid rgba(255, 214, 138, 0.95);
3411
+ outline-offset: 2px;
3412
+ }
3413
+ .wo-editor-toolbar button:disabled { opacity: 0.45; cursor: not-allowed; }
3414
+ .wo-editor-status {
3415
+ display: flex;
3416
+ flex-wrap: wrap;
3417
+ gap: 10px;
3418
+ min-width: 0;
3419
+ }
3420
+ .wo-editor-status-pill {
3421
+ border: 1px solid rgba(255,255,255,0.08);
3422
+ border-radius: 999px;
3423
+ background: rgba(255,255,255,0.04);
3424
+ color: #edf6ff;
3425
+ font: 600 12px/1.4 "Segoe UI Variable", "Segoe UI", sans-serif;
3426
+ padding: 8px 12px;
3427
+ }
3428
+ .wo-editor-status-pill.is-clean { border-color: rgba(120, 255, 195, 0.22); color: #c7ffe5; }
3429
+ .wo-editor-status-pill.is-dirty { border-color: rgba(240, 180, 100, 0.28); color: #ffd58a; }
3430
+ .wo-editor-status-pill.is-warning { border-color: rgba(240, 180, 100, 0.28); color: #ffd58a; }
3431
+ .wo-editor-status-pill.is-error { border-color: rgba(255, 120, 120, 0.3); color: #ffb2b2; }
3432
+ .wo-editor-main {
3433
+ display: grid;
3434
+ grid-template-columns:
3435
+ minmax(220px, var(--wo-editor-sidebar-width))
3436
+ minmax(0, 1fr)
3437
+ minmax(280px, var(--wo-editor-inspector-width));
3438
+ gap: 16px;
3439
+ min-width: 0;
3440
+ align-items: start;
3441
+ }
3442
+ .wo-editor-sidebar, .wo-editor-stage-panel, .wo-editor-preview { display: grid; gap: 16px; min-width: 0; }
3443
+ .wo-editor-panel {
3444
+ border-radius: 24px;
3445
+ border: 1px solid rgba(255,255,255,0.08);
3446
+ background: rgba(7, 16, 25, 0.72);
3447
+ padding: 18px;
3448
+ min-width: 0;
3449
+ }
3450
+ .wo-editor-panel h2 {
3451
+ margin: 0 0 14px;
3452
+ color: #edf6ff;
3453
+ font: 700 14px/1.2 "Segoe UI Variable Display", "Segoe UI", sans-serif;
3454
+ text-transform: uppercase;
3455
+ letter-spacing: 0.08em;
3456
+ }
3457
+ .wo-editor-panel h3 {
3458
+ margin: 0 0 12px;
3459
+ color: rgba(237, 246, 255, 0.82);
3460
+ font: 700 12px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
3461
+ text-transform: uppercase;
3462
+ letter-spacing: 0.08em;
3463
+ }
3464
+ .wo-editor-panel-section,
3465
+ .wo-editor-inspector-section {
3466
+ min-width: 0;
3467
+ }
3468
+ .wo-editor-panel-section > summary,
3469
+ .wo-editor-inspector-section > summary {
3470
+ display: flex;
3471
+ align-items: center;
3472
+ justify-content: space-between;
3473
+ gap: 10px;
3474
+ cursor: pointer;
3475
+ list-style: none;
3476
+ color: #edf6ff;
3477
+ font: 700 14px/1.2 "Segoe UI Variable Display", "Segoe UI", sans-serif;
3478
+ text-transform: uppercase;
3479
+ letter-spacing: 0.08em;
3480
+ }
3481
+ .wo-editor-inspector-section > summary {
3482
+ font: 700 12px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
3483
+ color: rgba(237, 246, 255, 0.9);
3484
+ }
3485
+ .wo-editor-panel-section > summary::-webkit-details-marker,
3486
+ .wo-editor-inspector-section > summary::-webkit-details-marker,
3487
+ .wo-editor-field-help > summary::-webkit-details-marker {
3488
+ display: none;
3489
+ }
3490
+ .wo-editor-panel-section > summary::after,
3491
+ .wo-editor-inspector-section > summary::after {
3492
+ content: "▾";
3493
+ color: rgba(240, 180, 100, 0.86);
3494
+ font-size: 12px;
3495
+ transition: transform 0.18s ease;
3496
+ }
3497
+ .wo-editor-panel-section:not([open]) > summary::after,
3498
+ .wo-editor-inspector-section:not([open]) > summary::after {
3499
+ transform: rotate(-90deg);
3500
+ }
3501
+ .wo-editor-panel-section-body {
3502
+ display: grid;
3503
+ gap: 16px;
3504
+ margin-top: 14px;
3505
+ min-width: 0;
3506
+ }
3507
+ .wo-editor-inspector-section-body {
3508
+ display: grid;
3509
+ gap: 12px;
3510
+ margin-top: 12px;
3511
+ min-width: 0;
3512
+ }
3513
+ .wo-editor[data-wo-show-inspector="false"] [data-editor-pane="inspector"] { display: none; }
3514
+ .wo-editor[data-wo-show-text-pane="false"] [data-editor-pane="text"] { display: none; }
3515
+ .wo-editor[data-wo-show-preview="false"] [data-editor-pane="preview"] { display: none; }
3516
+ .wo-editor[data-wo-sticky-stage="true"] .wo-editor-stage-panel {
3517
+ position: sticky;
3518
+ top: var(--wo-editor-stage-sticky-top, 12px);
3519
+ align-self: start;
3520
+ max-height: var(--wo-editor-stage-sticky-max-height, calc(100vh - 24px));
3521
+ }
3522
+ .wo-editor[data-wo-sticky-stage="true"] .wo-editor-stage-shell {
3523
+ max-height: var(--wo-editor-stage-sticky-max-height, calc(100vh - 24px));
3524
+ }
3525
+ .wo-editor[data-wo-sticky-stage="true"] .wo-editor-stage {
3526
+ min-height: min(620px, var(--wo-editor-stage-sticky-max-height, calc(100vh - 24px)));
3527
+ }
3528
+ .wo-editor-stage-shell {
3529
+ position: relative;
3530
+ min-width: 0;
3531
+ border-radius: 26px;
3532
+ overflow: hidden;
3533
+ border: 1px solid rgba(255,255,255,0.08);
3534
+ background: rgba(8, 17, 28, 0.9);
3535
+ }
3536
+ .wo-editor-stage { min-height: 620px; min-width: 0; }
3537
+ .wo-editor-overlay { position: absolute; inset: 0; pointer-events: none; }
3538
+ .wo-editor-overlay > * { pointer-events: auto; }
3539
+ .wo-editor-handle {
3540
+ position: absolute;
3541
+ transform: translate(-50%, -50%);
3542
+ border: 0;
3543
+ border-radius: 999px;
3544
+ background: rgba(255, 214, 138, 0.92);
3545
+ color: #071019;
3546
+ cursor: grab;
3547
+ font: 700 11px/1 "Segoe UI Variable", "Segoe UI", sans-serif;
3548
+ padding: 8px 10px;
3549
+ box-shadow: 0 10px 24px rgba(0,0,0,0.24);
3550
+ }
3551
+ .wo-editor-hint {
3552
+ position: absolute;
3553
+ transform: translate(-50%, -50%);
3554
+ padding: 4px 8px;
3555
+ border-radius: 999px;
3556
+ background: rgba(18, 39, 58, 0.84);
3557
+ color: #edf6ff;
3558
+ font: 600 11px/1.3 "Segoe UI Variable", "Segoe UI", sans-serif;
3559
+ white-space: nowrap;
3560
+ box-shadow: 0 6px 18px rgba(0,0,0,0.22);
3561
+ }
3562
+ .wo-editor-hint.is-subtle { background: rgba(18, 39, 58, 0.64); color: rgba(237, 246, 255, 0.78); }
3563
+ .wo-editor-hint-note { left: 16px; top: 16px; transform: none; }
3564
+ .wo-editor-overlay-diagnostics {
3565
+ position: absolute;
3566
+ right: 16px;
3567
+ top: 16px;
3568
+ display: grid;
3569
+ gap: 8px;
3570
+ max-width: min(320px, calc(100% - 32px));
3571
+ }
3572
+ .wo-editor-overlay-diagnostic {
3573
+ border-radius: 14px;
3574
+ padding: 10px 12px;
3575
+ background: rgba(10, 20, 32, 0.92);
3576
+ box-shadow: 0 12px 30px rgba(0,0,0,0.28);
3577
+ }
3578
+ .wo-editor-overlay-diagnostic strong {
3579
+ display: block;
3580
+ margin-bottom: 4px;
3581
+ font: 700 11px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
3582
+ letter-spacing: 0.08em;
3583
+ text-transform: uppercase;
3584
+ }
3585
+ .wo-editor-overlay-diagnostic p {
3586
+ margin: 0;
3587
+ color: #edf6ff;
3588
+ font: 500 12px/1.4 "Segoe UI Variable", "Segoe UI", sans-serif;
3589
+ }
3590
+ .wo-editor-overlay-diagnostic-error { border: 1px solid rgba(255, 120, 120, 0.28); }
3591
+ .wo-editor-overlay-diagnostic-warning { border: 1px solid rgba(240, 180, 100, 0.24); }
3592
+ .wo-editor-outline { display: grid; gap: 14px; }
3593
+ .wo-editor-outline-section { display: grid; gap: 8px; }
3594
+ .wo-editor-outline-group { display: grid; gap: 6px; }
3595
+ .wo-editor-outline-children {
3596
+ display: grid;
3597
+ gap: 6px;
3598
+ padding-left: 16px;
3599
+ }
3600
+ .wo-editor-outline-section h3 {
3601
+ margin: 0;
3602
+ color: rgba(237,246,255,0.68);
3603
+ font: 600 11px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
3604
+ text-transform: uppercase;
3605
+ letter-spacing: 0.08em;
3606
+ }
3607
+ .wo-editor-outline-item {
3608
+ border: 1px solid transparent;
3609
+ border-radius: 16px;
3610
+ background: rgba(255,255,255,0.04);
3611
+ color: #edf6ff;
3612
+ cursor: pointer;
3613
+ font: 500 13px/1.4 "Segoe UI Variable", "Segoe UI", sans-serif;
3614
+ padding: 10px 12px;
3615
+ text-align: left;
3616
+ display: flex;
3617
+ align-items: center;
3618
+ justify-content: space-between;
3619
+ gap: 10px;
3620
+ }
3621
+ .wo-editor-outline-item.is-active {
3622
+ border-color: rgba(255, 214, 138, 0.34);
3623
+ background: rgba(255, 214, 138, 0.12);
3624
+ color: #ffdda9;
3625
+ }
3626
+ .wo-editor-outline-badge {
3627
+ min-width: 22px;
3628
+ border-radius: 999px;
3629
+ background: rgba(240, 180, 100, 0.16);
3630
+ color: #ffd58a;
3631
+ font: 700 11px/1 "Segoe UI Variable", "Segoe UI", sans-serif;
3632
+ padding: 5px 7px;
3633
+ text-align: center;
3634
+ }
3635
+ .wo-editor-outline-badge.is-error {
3636
+ background: rgba(255, 120, 120, 0.18);
3637
+ color: #ffb2b2;
3638
+ }
3639
+ .wo-editor-inline-list { display: grid; gap: 8px; }
3640
+ .wo-editor-inline-actions {
3641
+ display: flex;
3642
+ flex-wrap: wrap;
3643
+ gap: 10px;
3644
+ margin-top: 12px;
3645
+ }
3646
+ .wo-editor-inline-actions button {
3647
+ border: 1px solid rgba(255,255,255,0.14);
3648
+ border-radius: 999px;
3649
+ background: rgba(255,255,255,0.06);
3650
+ color: #edf6ff;
3651
+ cursor: pointer;
3652
+ font: 600 12px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
3653
+ padding: 8px 12px;
3654
+ }
3655
+ .wo-editor-inline-note {
3656
+ margin: 0 0 12px;
3657
+ color: rgba(237,246,255,0.72);
3658
+ font: 500 12px/1.5 "Segoe UI Variable", "Segoe UI", sans-serif;
3659
+ }
3660
+ .wo-editor-diagnostics { display: grid; gap: 10px; }
3661
+ .wo-editor-diagnostic {
3662
+ display: grid;
3663
+ gap: 4px;
3664
+ border-radius: 16px;
3665
+ padding: 12px;
3666
+ background: rgba(255,255,255,0.04);
3667
+ color: #edf6ff;
3668
+ font: 500 12px/1.45 "Segoe UI Variable", "Segoe UI", sans-serif;
3669
+ }
3670
+ .wo-editor-diagnostic strong { font-size: 11px; letter-spacing: 0.08em; }
3671
+ .wo-editor-diagnostic p { margin: 0; color: rgba(237,246,255,0.82); }
3672
+ .wo-editor-diagnostic-warning { border: 1px solid rgba(240, 180, 100, 0.22); }
3673
+ .wo-editor-diagnostic-error { border: 1px solid rgba(255, 120, 120, 0.24); }
3674
+ .wo-editor-inspector-summary {
3675
+ display: grid;
3676
+ gap: 10px;
3677
+ margin-bottom: 14px;
3678
+ }
3679
+ .wo-editor-form { display: grid; gap: 12px; min-width: 0; }
3680
+ .wo-editor-inspector-section {
3681
+ border: 1px solid rgba(255,255,255,0.08);
3682
+ border-radius: 18px;
3683
+ padding: 14px;
3684
+ background: rgba(255,255,255,0.02);
3685
+ }
3686
+ .wo-editor-field { display: grid; gap: 6px; }
3687
+ .wo-editor-field-label,
3688
+ .wo-editor-fieldset legend {
3689
+ display: flex;
3690
+ align-items: center;
3691
+ justify-content: space-between;
3692
+ gap: 10px;
3693
+ }
3694
+ .wo-editor-field-label,
3695
+ .wo-editor-checkbox .wo-editor-field-label,
3696
+ .wo-editor-fieldset legend {
3697
+ color: rgba(237,246,255,0.72);
3698
+ font: 600 11px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
3699
+ text-transform: uppercase;
3700
+ letter-spacing: 0.08em;
3701
+ }
3702
+ .wo-editor-field input, .wo-editor-field select, .wo-editor-field textarea, .wo-editor-source {
3703
+ width: 100%;
3704
+ min-width: 0;
3705
+ border: 1px solid rgba(255,255,255,0.08);
3706
+ border-radius: 14px;
3707
+ background: rgba(12, 26, 39, 0.84);
3708
+ color: #edf6ff;
3709
+ font: 500 13px/1.5 "Segoe UI Variable", "Segoe UI", sans-serif;
3710
+ padding: 10px 12px;
3711
+ box-sizing: border-box;
3712
+ }
3713
+ .wo-editor-field.has-error input,
3714
+ .wo-editor-field.has-error select,
3715
+ .wo-editor-field.has-error textarea,
3716
+ .wo-editor-checkbox.has-error {
3717
+ border-color: rgba(255, 120, 120, 0.44);
3718
+ }
3719
+ .wo-editor-field.has-warning input,
3720
+ .wo-editor-field.has-warning select,
3721
+ .wo-editor-field.has-warning textarea,
3722
+ .wo-editor-checkbox.has-warning {
3723
+ border-color: rgba(240, 180, 100, 0.42);
3724
+ }
3725
+ .wo-editor-field textarea, .wo-editor-source { min-height: 110px; resize: vertical; }
3726
+ .wo-editor-source {
3727
+ min-height: var(--wo-editor-source-height);
3728
+ font-family: "Cascadia Code", "Consolas", monospace;
3729
+ white-space: pre;
3730
+ overflow: auto;
3731
+ }
3732
+ .wo-editor-field-note {
3733
+ color: rgba(237,246,255,0.72);
3734
+ font: 500 12px/1.45 "Segoe UI Variable", "Segoe UI", sans-serif;
3735
+ }
3736
+ .wo-editor-field-note.is-error { color: #ffb2b2; }
3737
+ .wo-editor-field-note.is-warning { color: #ffd58a; }
3738
+ .wo-editor-fieldset {
3739
+ display: grid;
3740
+ gap: 10px;
3741
+ border: 1px solid rgba(255,255,255,0.08);
3742
+ border-radius: 18px;
3743
+ padding: 14px;
3744
+ min-width: 0;
3745
+ }
3746
+ .wo-editor-checkbox {
3747
+ display: flex;
3748
+ gap: 10px;
3749
+ align-items: flex-start;
3750
+ color: #edf6ff;
3751
+ font: 500 13px/1.4 "Segoe UI Variable", "Segoe UI", sans-serif;
3752
+ }
3753
+ .wo-editor-checkbox input {
3754
+ margin-top: 2px;
3755
+ }
3756
+ .wo-editor-field-help {
3757
+ position: relative;
3758
+ flex: 0 0 auto;
3759
+ }
3760
+ .wo-editor-field-help > summary {
3761
+ display: inline-flex;
3762
+ align-items: center;
3763
+ justify-content: center;
3764
+ width: 20px;
3765
+ height: 20px;
3766
+ border-radius: 999px;
3767
+ border: 1px solid rgba(240, 180, 100, 0.28);
3768
+ background: rgba(240, 180, 100, 0.12);
3769
+ color: #ffdda9;
3770
+ cursor: pointer;
3771
+ font: 700 11px/1 "Segoe UI Variable", "Segoe UI", sans-serif;
3772
+ list-style: none;
3773
+ }
3774
+ .wo-editor-field-help-card {
3775
+ display: grid;
3776
+ gap: 8px;
3777
+ margin-top: 8px;
3778
+ padding: 10px 12px;
3779
+ border-radius: 14px;
3780
+ border: 1px solid rgba(240, 180, 100, 0.2);
3781
+ background: rgba(12, 26, 39, 0.92);
3782
+ color: rgba(237, 246, 255, 0.86);
3783
+ text-transform: none;
3784
+ letter-spacing: normal;
3785
+ font: 500 12px/1.45 "Segoe UI Variable", "Segoe UI", sans-serif;
3786
+ }
3787
+ .wo-editor-field-help-card p {
3788
+ margin: 0;
3789
+ }
3790
+ .wo-editor-field-help-chips {
3791
+ display: flex;
3792
+ flex-wrap: wrap;
3793
+ gap: 6px;
3794
+ }
3795
+ .wo-editor-field-help-chips span {
3796
+ border-radius: 999px;
3797
+ padding: 4px 8px;
3798
+ background: rgba(255,255,255,0.06);
3799
+ color: #edf6ff;
3800
+ font: 600 11px/1.3 "Segoe UI Variable", "Segoe UI", sans-serif;
3801
+ text-transform: none;
3802
+ letter-spacing: normal;
3803
+ }
3804
+ .wo-editor-preview { grid-template-columns: repeat(2, minmax(0, 1fr)); }
3805
+ .wo-editor-preview-card {
3806
+ border-radius: 18px;
3807
+ border: 1px solid rgba(255,255,255,0.08);
3808
+ background: rgba(255,255,255,0.03);
3809
+ padding: 14px;
3810
+ min-width: 0;
3811
+ }
3812
+ .wo-editor-preview-visual {
3813
+ min-height: 240px;
3814
+ overflow: auto;
3815
+ }
3816
+ .wo-editor-preview-markup {
3817
+ margin: 0;
3818
+ min-height: 240px;
3819
+ white-space: pre-wrap;
3820
+ overflow-wrap: anywhere;
3821
+ color: #edf6ff;
3822
+ font: 12px/1.5 "Cascadia Code", "Consolas", monospace;
3823
+ }
3824
+ .wo-editor-source-diagnostics {
3825
+ display: grid;
3826
+ gap: 10px;
3827
+ margin-top: 14px;
3828
+ }
3829
+ .wo-editor-empty {
3830
+ margin: 0;
3831
+ color: rgba(237,246,255,0.68);
3832
+ font: 500 12px/1.5 "Segoe UI Variable", "Segoe UI", sans-serif;
3833
+ }
3834
+ .wo-editor-live-region {
3835
+ position: absolute;
3836
+ width: 1px;
3837
+ height: 1px;
3838
+ padding: 0;
3839
+ margin: -1px;
3840
+ overflow: hidden;
3841
+ clip: rect(0, 0, 0, 0);
3842
+ white-space: nowrap;
3843
+ border: 0;
3844
+ }
3845
+ @media (max-width: 1280px) {
3846
+ .wo-editor-main { grid-template-columns: minmax(220px, 260px) minmax(0, 1fr); }
3847
+ .wo-editor-inspector { grid-column: 1 / -1; }
3848
+ .wo-editor-preview { grid-template-columns: 1fr; }
3849
+ }
3850
+ @media (max-width: 960px) {
3851
+ .wo-editor-main { grid-template-columns: 1fr; }
3852
+ .wo-editor[data-wo-sticky-stage="true"] .wo-editor-stage-panel {
3853
+ position: relative;
3854
+ top: auto;
3855
+ max-height: none;
3856
+ }
3857
+ .wo-editor[data-wo-sticky-stage="true"] .wo-editor-stage-shell {
3858
+ max-height: none;
3859
+ }
3860
+ .wo-editor[data-wo-sticky-stage="true"] .wo-editor-stage {
3861
+ min-height: 440px;
3862
+ }
3863
+ .wo-editor-stage { min-height: 440px; }
3864
+ .wo-editor-status { flex-direction: column; }
3865
+ }
3741
3866
  `;
3742
3867
  document.head.append(style);
3743
3868
  }