worldorbit 2.5.11 → 2.5.13

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 (39) hide show
  1. package/dist/unpkg/worldorbit-core.min.js +5 -5
  2. package/dist/unpkg/worldorbit-markdown.min.js +20 -20
  3. package/dist/unpkg/worldorbit-viewer.min.js +29 -29
  4. package/dist/unpkg/worldorbit.js +38 -15
  5. package/dist/unpkg/worldorbit.min.js +36 -36
  6. package/package.json +1 -1
  7. package/packages/core/dist/atlas-edit.d.ts +11 -0
  8. package/packages/core/dist/atlas-edit.js +210 -0
  9. package/packages/core/dist/diagnostics.d.ts +10 -0
  10. package/packages/core/dist/diagnostics.js +109 -0
  11. package/packages/core/dist/draft-parse.d.ts +3 -0
  12. package/packages/core/dist/draft-parse.js +642 -0
  13. package/packages/core/dist/draft.d.ts +15 -0
  14. package/packages/core/dist/draft.js +343 -0
  15. package/packages/core/dist/errors.d.ts +7 -0
  16. package/packages/core/dist/errors.js +16 -0
  17. package/packages/core/dist/format.d.ts +4 -0
  18. package/packages/core/dist/format.js +364 -0
  19. package/packages/core/dist/index.d.ts +28 -0
  20. package/packages/core/dist/index.js +44 -0
  21. package/packages/core/dist/load.d.ts +4 -0
  22. package/packages/core/dist/load.js +130 -0
  23. package/packages/core/dist/markdown.d.ts +2 -0
  24. package/packages/core/dist/markdown.js +37 -0
  25. package/packages/core/dist/normalize.d.ts +2 -0
  26. package/packages/core/dist/normalize.js +304 -0
  27. package/packages/core/dist/parse.d.ts +2 -0
  28. package/packages/core/dist/parse.js +133 -0
  29. package/packages/core/dist/scene.d.ts +3 -0
  30. package/packages/core/dist/scene.js +1512 -0
  31. package/packages/core/dist/schema.d.ts +8 -0
  32. package/packages/core/dist/schema.js +298 -0
  33. package/packages/core/dist/tokenize.d.ts +4 -0
  34. package/packages/core/dist/tokenize.js +68 -0
  35. package/packages/core/dist/types.d.ts +382 -0
  36. package/packages/core/dist/types.js +1 -0
  37. package/packages/core/dist/validate.d.ts +2 -0
  38. package/packages/core/dist/validate.js +56 -0
  39. package/packages/editor/dist/editor.js +493 -121
@@ -1,7 +1,7 @@
1
1
  import { cloneAtlasDocument, createEmptyAtlasDocument, formatDocument, getAtlasDocumentNode, loadWorldOrbitSourceWithDiagnostics, materializeAtlasDocument, removeAtlasDocumentNode, resolveAtlasDiagnostics, rotatePoint, upgradeDocumentToV2, validateAtlasDocumentWithDiagnostics, } from "@worldorbit/core";
2
2
  import { renderWorldOrbitBlock } from "@worldorbit/markdown";
3
3
  import { createInteractiveViewer, } from "@worldorbit/viewer";
4
- import { invertViewerPoint } from "@worldorbit/viewer/viewer-state";
4
+ import { getViewerVisibleBounds, invertViewerPoint } from "@worldorbit/viewer/viewer-state";
5
5
  const STYLE_ID = "worldorbit-editor-style";
6
6
  const SOURCE_INPUT_DEBOUNCE_MS = 120;
7
7
  const PREVIEW_BATCH_DELAY_MS = 16;
@@ -61,6 +61,96 @@ const OBJECT_UNIT_FIELDS = [
61
61
  "cycle",
62
62
  ];
63
63
  const OBJECT_NUMBER_FIELDS = ["albedo"];
64
+ const FIELD_HELP = {
65
+ "defaults-view": {
66
+ description: "Sets the default camera projection for the atlas.",
67
+ references: ["Topdown = map-like", "Isometric = angled overview"],
68
+ },
69
+ "defaults-scale": {
70
+ description: "Chooses the overall spacing/style preset used by the renderer.",
71
+ references: ["diagram = tighter", "presentation = roomier"],
72
+ },
73
+ "defaults-units": {
74
+ description: "Stores a document-wide note about the unit style you want to use.",
75
+ references: ["Example: metric", "Example: lore-standard"],
76
+ },
77
+ "viewpoint-projection": {
78
+ description: "Overrides the projection for this saved viewpoint.",
79
+ references: ["Topdown = flat orbital map", "Isometric = angled scene"],
80
+ },
81
+ "viewpoint-zoom": {
82
+ description: "Controls how closely this viewpoint frames the system.",
83
+ references: ["1 = scene fit", "2+ = close-up"],
84
+ },
85
+ "viewpoint-rotation": {
86
+ description: "Rotates the saved camera angle in degrees.",
87
+ references: ["90deg = quarter turn", "180deg = flip"],
88
+ },
89
+ "placement-target": {
90
+ description: "Names the body or reference this object is attached to.",
91
+ references: ["orbit Primary", "surface Homeworld", "at Naar:L4"],
92
+ },
93
+ "placement-free": {
94
+ description: "Stores a free-placement offset or a descriptive label for loose placement.",
95
+ references: ["8au = far from the star", '"outer system" = descriptive note'],
96
+ },
97
+ "placement-distance": {
98
+ description: "Mean orbit distance from the target body.",
99
+ references: ["1 au = Earth-Sun distance", "384400km = Earth-Moon distance"],
100
+ },
101
+ "placement-semiMajor": {
102
+ description: "Semi-major axis of an orbit, used for elliptical orbits.",
103
+ references: ["1 au = Earth-Sun distance", "Use this instead of distance when orbit shape matters"],
104
+ },
105
+ "placement-eccentricity": {
106
+ description: "Controls how stretched the orbit is. Lower is rounder.",
107
+ references: ["0 = circular orbit", "0.1 = mildly elliptical"],
108
+ },
109
+ "placement-period": {
110
+ description: "How long one orbit takes.",
111
+ references: ["1 y = one Earth year", "27.3d = Moon around Earth"],
112
+ },
113
+ "placement-angle": {
114
+ description: "Rotates the orbit ellipse within the scene.",
115
+ references: ["0deg = default orientation", "90deg = quarter turn"],
116
+ },
117
+ "placement-inclination": {
118
+ description: "Tilts the orbit relative to the main orbital plane.",
119
+ references: ["0deg = same plane", "5deg = slight tilt"],
120
+ },
121
+ "placement-phase": {
122
+ description: "Starting position of the object along its orbit.",
123
+ references: ["0deg = start position", "180deg = opposite side"],
124
+ },
125
+ "prop-radius": {
126
+ description: "Visual body size or real-world-inspired radius value.",
127
+ references: ["1re = Earth radius", "1sol = Sun radius"],
128
+ },
129
+ "prop-mass": {
130
+ description: "Optional mass value for the body.",
131
+ references: ["1me = Earth mass", "1sol = Sun mass"],
132
+ },
133
+ "prop-gravity": {
134
+ description: "Surface gravity or a custom gravity marker.",
135
+ references: ["1g = Earth-like gravity", "0.16g = Moon-like gravity"],
136
+ },
137
+ "prop-temperature": {
138
+ description: "Typical temperature or narrative temperature marker.",
139
+ references: ["288K = Earth-like average", "1200K = very hot world"],
140
+ },
141
+ "prop-atmosphere": {
142
+ description: "Short atmosphere descriptor for the object.",
143
+ references: ['"nitrogen-oxygen"', '"methane haze"'],
144
+ },
145
+ "prop-inner": {
146
+ description: "Inner edge for a belt, ring, or broad phenomenon.",
147
+ references: ["120000km = ring inner edge", "2au = belt starts here"],
148
+ },
149
+ "prop-outer": {
150
+ description: "Outer edge for a belt, ring, or broad phenomenon.",
151
+ references: ["190000km = ring outer edge", "3au = belt ends here"],
152
+ },
153
+ };
64
154
  export function createWorldOrbitEditor(container, options = {}) {
65
155
  ensureBrowserEnvironment(container);
66
156
  installEditorStyles();
@@ -83,6 +173,7 @@ export function createWorldOrbitEditor(container, options = {}) {
83
173
  let previewTimer = null;
84
174
  let lastPreviewSvg = "";
85
175
  let lastPreviewMarkup = "";
176
+ const inspectorSectionState = new Map();
86
177
  const showTextPane = options.showTextPane ?? true;
87
178
  const showInspector = options.showInspector ?? true;
88
179
  const showPreview = options.showPreview ?? true;
@@ -605,6 +696,7 @@ export function createWorldOrbitEditor(container, options = {}) {
605
696
  if (!inspector) {
606
697
  return;
607
698
  }
699
+ captureInspectorSectionState(inspector, inspectorSectionState);
608
700
  const formState = {
609
701
  selection: selection ? { path: { ...selection } } : null,
610
702
  system: atlasDocument.system,
@@ -619,26 +711,32 @@ export function createWorldOrbitEditor(container, options = {}) {
619
711
  switch (selection.kind) {
620
712
  case "system":
621
713
  inspector.innerHTML = diagnosticSummary + renderSystemInspector(formState);
714
+ applyInspectorSectionState(inspector, inspectorSectionState);
622
715
  decorateInspectorDiagnostics(selection, diagnostics);
623
716
  return;
624
717
  case "defaults":
625
718
  inspector.innerHTML = diagnosticSummary + renderDefaultsInspector(formState);
719
+ applyInspectorSectionState(inspector, inspectorSectionState);
626
720
  decorateInspectorDiagnostics(selection, diagnostics);
627
721
  return;
628
722
  case "metadata":
629
723
  inspector.innerHTML = diagnosticSummary + renderMetadataInspector(formState, selection.key ?? "");
724
+ applyInspectorSectionState(inspector, inspectorSectionState);
630
725
  decorateInspectorDiagnostics(selection, diagnostics);
631
726
  return;
632
727
  case "viewpoint":
633
728
  inspector.innerHTML = diagnosticSummary + renderViewpointInspector(formState, selection.id ?? "");
729
+ applyInspectorSectionState(inspector, inspectorSectionState);
634
730
  decorateInspectorDiagnostics(selection, diagnostics);
635
731
  return;
636
732
  case "annotation":
637
733
  inspector.innerHTML = diagnosticSummary + renderAnnotationInspector(formState, selection.id ?? "");
734
+ applyInspectorSectionState(inspector, inspectorSectionState);
638
735
  decorateInspectorDiagnostics(selection, diagnostics);
639
736
  return;
640
737
  case "object":
641
738
  inspector.innerHTML = diagnosticSummary + renderObjectInspector(formState, selection.id ?? "");
739
+ applyInspectorSectionState(inspector, inspectorSectionState);
642
740
  decorateInspectorDiagnostics(selection, diagnostics);
643
741
  return;
644
742
  }
@@ -907,12 +1005,16 @@ export function createWorldOrbitEditor(container, options = {}) {
907
1005
  return;
908
1006
  }
909
1007
  }
1008
+ const details = viewer?.getObjectDetails(objectId) ?? null;
910
1009
  dragState = {
911
1010
  kind,
912
1011
  objectId,
913
1012
  pointerId: event.pointerId,
914
1013
  startedFrom: createHistoryEntry(),
915
1014
  changed: false,
1015
+ orbitRadiusContext: kind === "orbit-radius" && details
1016
+ ? createOrbitRadiusDragContext(atlasDocument, viewer.getScene(), details)
1017
+ : null,
916
1018
  };
917
1019
  handle.setPointerCapture?.(event.pointerId);
918
1020
  event.preventDefault();
@@ -938,7 +1040,7 @@ export function createWorldOrbitEditor(container, options = {}) {
938
1040
  break;
939
1041
  case "orbit-radius":
940
1042
  if (details.object.placement?.mode === "orbit" && details.orbit) {
941
- nextDocument = updateOrbitRadius(atlasDocument, dragState.objectId, details, pointer);
1043
+ nextDocument = updateOrbitRadius(atlasDocument, dragState.objectId, details, pointer, dragState.orbitRadiusContext ?? null);
942
1044
  }
943
1045
  break;
944
1046
  case "at-reference":
@@ -978,6 +1080,7 @@ export function createWorldOrbitEditor(container, options = {}) {
978
1080
  if (!dragState || dragState.pointerId !== event.pointerId) {
979
1081
  return;
980
1082
  }
1083
+ const completedDrag = dragState;
981
1084
  if (!dragState.changed) {
982
1085
  dragState = null;
983
1086
  return;
@@ -988,6 +1091,9 @@ export function createWorldOrbitEditor(container, options = {}) {
988
1091
  sourceText = canonicalSource;
989
1092
  dragState = null;
990
1093
  renderAll();
1094
+ if (completedDrag.kind === "orbit-radius") {
1095
+ maybeFitOrbitResizeInView(completedDrag.objectId);
1096
+ }
991
1097
  updateDirtyState();
992
1098
  emitSnapshot();
993
1099
  }
@@ -1214,6 +1320,24 @@ export function createWorldOrbitEditor(container, options = {}) {
1214
1320
  lastPreviewMarkup = nextMarkup;
1215
1321
  }
1216
1322
  }
1323
+ function maybeFitOrbitResizeInView(objectId) {
1324
+ if (!viewer) {
1325
+ return;
1326
+ }
1327
+ const details = viewer.getObjectDetails(objectId);
1328
+ if (!details) {
1329
+ return;
1330
+ }
1331
+ const visibleBounds = getViewerVisibleBounds(viewer.getScene(), viewer.getState());
1332
+ const margin = 36 / Math.max(viewer.getState().scale, 0.001);
1333
+ const point = details.renderObject;
1334
+ if (point.x < visibleBounds.minX + margin ||
1335
+ point.x > visibleBounds.maxX - margin ||
1336
+ point.y < visibleBounds.minY + margin ||
1337
+ point.y > visibleBounds.maxY - margin) {
1338
+ viewer.fitToSystem();
1339
+ }
1340
+ }
1217
1341
  function shouldIgnoreShortcutEvent(event) {
1218
1342
  const target = event.target;
1219
1343
  if (!target) {
@@ -1320,56 +1444,117 @@ function resolveInitialEditorState(options) {
1320
1444
  };
1321
1445
  }
1322
1446
  function buildEditorMarkup() {
1447
+ const previewOpen = shouldPreviewSectionBeOpenByDefault();
1323
1448
  return `<section class="wo-editor-shell">
1324
1449
  <div class="wo-editor-toolbar" data-editor-toolbar></div>
1325
1450
  <div class="wo-editor-status" data-editor-status role="status" aria-live="polite"></div>
1326
1451
  <div class="wo-editor-main">
1327
1452
  <aside class="wo-editor-sidebar" data-editor-pane="sidebar">
1328
- <div class="wo-editor-panel">
1329
- <h2>Atlas</h2>
1330
- <div class="wo-editor-outline" data-editor-outline></div>
1331
- </div>
1332
- <div class="wo-editor-panel">
1333
- <h2>Diagnostics</h2>
1334
- <div class="wo-editor-diagnostics" data-editor-diagnostics></div>
1335
- </div>
1453
+ <div class="wo-editor-panel">${renderPanelSection("Atlas", "atlas", `<div class="wo-editor-outline" data-editor-outline></div>`)}</div>
1454
+ <div class="wo-editor-panel">${renderPanelSection("Diagnostics", "diagnostics", `<div class="wo-editor-diagnostics" data-editor-diagnostics></div>`)}</div>
1336
1455
  </aside>
1337
1456
  <div class="wo-editor-stage-panel">
1338
1457
  <div class="wo-editor-stage-shell" data-editor-stage-shell>
1339
1458
  <div class="wo-editor-stage" data-editor-stage></div>
1340
1459
  <div class="wo-editor-overlay" data-editor-overlay></div>
1341
1460
  </div>
1342
- <div class="wo-editor-preview" data-editor-pane="preview">
1343
- <div class="wo-editor-panel">
1344
- <h2>Static SVG</h2>
1345
- <div class="wo-editor-preview-visual" data-editor-preview-visual></div>
1346
- </div>
1347
- <div class="wo-editor-panel">
1348
- <h2>Embed Markup</h2>
1349
- <pre class="wo-editor-preview-markup" data-editor-preview-markup></pre>
1350
- </div>
1461
+ <div class="wo-editor-panel" data-editor-pane="preview">
1462
+ ${renderPanelSection("Preview", "preview", `<div class="wo-editor-preview">
1463
+ <div class="wo-editor-preview-card">
1464
+ <h3>Static SVG</h3>
1465
+ <div class="wo-editor-preview-visual" data-editor-preview-visual></div>
1466
+ </div>
1467
+ <div class="wo-editor-preview-card">
1468
+ <h3>Embed Markup</h3>
1469
+ <pre class="wo-editor-preview-markup" data-editor-preview-markup></pre>
1470
+ </div>
1471
+ </div>`, previewOpen)}
1351
1472
  </div>
1352
1473
  </div>
1353
- <aside class="wo-editor-panel wo-editor-inspector" data-editor-pane="inspector" data-editor-inspector></aside>
1474
+ <aside class="wo-editor-panel wo-editor-inspector" data-editor-pane="inspector">
1475
+ ${renderPanelSection("Inspector", "inspector", `<div data-editor-inspector></div>`)}
1476
+ </aside>
1354
1477
  </div>
1355
1478
  <div class="wo-editor-panel wo-editor-text-panel" data-editor-pane="text">
1356
- <h2>Source</h2>
1357
- <textarea
1358
- class="wo-editor-source"
1359
- data-editor-source
1360
- spellcheck="false"
1361
- aria-describedby="worldorbit-editor-source-diagnostics"
1362
- ></textarea>
1363
- <div
1364
- class="wo-editor-source-diagnostics"
1365
- id="worldorbit-editor-source-diagnostics"
1366
- data-editor-source-diagnostics
1367
- aria-live="polite"
1368
- ></div>
1479
+ ${renderPanelSection("Source", "source", `<textarea
1480
+ class="wo-editor-source"
1481
+ data-editor-source
1482
+ spellcheck="false"
1483
+ aria-describedby="worldorbit-editor-source-diagnostics"
1484
+ ></textarea>
1485
+ <div
1486
+ class="wo-editor-source-diagnostics"
1487
+ id="worldorbit-editor-source-diagnostics"
1488
+ data-editor-source-diagnostics
1489
+ aria-live="polite"
1490
+ ></div>`)}
1369
1491
  </div>
1370
1492
  <div class="wo-editor-live-region" data-editor-live aria-live="polite" aria-atomic="true"></div>
1371
1493
  </section>`;
1372
1494
  }
1495
+ function renderPanelSection(title, sectionId, content, open = true) {
1496
+ return `<details class="wo-editor-panel-section" data-editor-panel-section="${escapeHtml(sectionId)}"${open ? " open" : ""}>
1497
+ <summary><span>${escapeHtml(title)}</span></summary>
1498
+ <div class="wo-editor-panel-section-body">${content}</div>
1499
+ </details>`;
1500
+ }
1501
+ function renderInspectorSection(formId, sectionId, title, content, open = false) {
1502
+ const stateKey = inspectorSectionStateKey(formId, sectionId);
1503
+ 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" : ""}>
1504
+ <summary><span>${escapeHtml(title)}</span></summary>
1505
+ <div class="wo-editor-inspector-section-body">${content}</div>
1506
+ </details>`;
1507
+ }
1508
+ function renderFieldLabel(label, name) {
1509
+ return `<span class="wo-editor-field-label">${escapeHtml(label)}${renderFieldHelp(name)}</span>`;
1510
+ }
1511
+ function renderFieldHelp(name) {
1512
+ const help = FIELD_HELP[name];
1513
+ if (!help) {
1514
+ return "";
1515
+ }
1516
+ return `<details class="wo-editor-field-help">
1517
+ <summary aria-label="Explain ${escapeAttribute(name)}">?</summary>
1518
+ <div class="wo-editor-field-help-card">
1519
+ <p>${escapeHtml(help.description)}</p>
1520
+ ${(help.references?.length ?? 0) > 0
1521
+ ? `<div class="wo-editor-field-help-chips">${(help.references ?? [])
1522
+ .map((entry) => `<span>${escapeHtml(entry)}</span>`)
1523
+ .join("")}</div>`
1524
+ : ""}
1525
+ </div>
1526
+ </details>`;
1527
+ }
1528
+ function captureInspectorSectionState(container, state) {
1529
+ for (const section of container.querySelectorAll("[data-editor-section-state]")) {
1530
+ const key = section.dataset.editorSectionState;
1531
+ if (!key) {
1532
+ continue;
1533
+ }
1534
+ state.set(key, section.open);
1535
+ }
1536
+ }
1537
+ function applyInspectorSectionState(container, state) {
1538
+ for (const section of container.querySelectorAll("[data-editor-section-state]")) {
1539
+ const key = section.dataset.editorSectionState;
1540
+ if (!key || !state.has(key)) {
1541
+ continue;
1542
+ }
1543
+ section.open = state.get(key) === true;
1544
+ }
1545
+ }
1546
+ function inspectorSectionStateKey(formId, sectionId) {
1547
+ return `${formId}:${sectionId}`;
1548
+ }
1549
+ function shouldPreviewSectionBeOpenByDefault() {
1550
+ if (typeof window === "undefined") {
1551
+ return true;
1552
+ }
1553
+ if (typeof window.matchMedia === "function") {
1554
+ return !window.matchMedia("(max-width: 1280px)").matches;
1555
+ }
1556
+ return window.innerWidth > 1280;
1557
+ }
1373
1558
  function renderOutlineButton(path, label, activeKey, diagnosticBuckets) {
1374
1559
  const key = selectionKey(path);
1375
1560
  const bucket = key ? diagnosticBuckets.get(key) : null;
@@ -1381,36 +1566,36 @@ function renderOutlineButton(path, label, activeKey, diagnosticBuckets) {
1381
1566
  function renderSystemInspector(formState) {
1382
1567
  return `<form class="wo-editor-form" data-editor-form="system">
1383
1568
  <h2>System</h2>
1384
- ${renderTextField("System ID", "system-id", formState.system?.id ?? "")}
1385
- ${renderTextField("Title", "system-title", formState.system?.title ?? "")}
1569
+ ${renderInspectorSection("system", "basics", "Basics", `${renderTextField("System ID", "system-id", formState.system?.id ?? "")}
1570
+ ${renderTextField("Title", "system-title", formState.system?.title ?? "")}`, true)}
1386
1571
  </form>`;
1387
1572
  }
1388
1573
  function renderDefaultsInspector(formState) {
1389
1574
  const defaults = formState.system?.defaults;
1390
1575
  return `<form class="wo-editor-form" data-editor-form="defaults">
1391
1576
  <h2>Defaults</h2>
1392
- ${renderSelectField("Projection", "defaults-view", [
1577
+ ${renderInspectorSection("defaults", "basics", "Basics", `${renderSelectField("Projection", "defaults-view", [
1393
1578
  ["topdown", "Topdown"],
1394
1579
  ["isometric", "Isometric"],
1395
1580
  ], defaults?.view ?? "topdown")}
1396
- ${renderTextField("Scale preset", "defaults-scale", defaults?.scale ?? "")}
1397
- ${renderTextField("Units", "defaults-units", defaults?.units ?? "")}
1398
- ${renderSelectField("Render preset", "defaults-preset", [
1581
+ ${renderTextField("Scale preset", "defaults-scale", defaults?.scale ?? "")}
1582
+ ${renderTextField("Units", "defaults-units", defaults?.units ?? "")}
1583
+ ${renderSelectField("Render preset", "defaults-preset", [
1399
1584
  ["", "Document default"],
1400
1585
  ["diagram", "Diagram"],
1401
1586
  ["presentation", "Presentation"],
1402
1587
  ["atlas-card", "Atlas Card"],
1403
1588
  ["markdown", "Markdown"],
1404
1589
  ], defaults?.preset ?? "")}
1405
- ${renderTextField("Theme", "defaults-theme", defaults?.theme ?? "")}
1590
+ ${renderTextField("Theme", "defaults-theme", defaults?.theme ?? "")}`, true)}
1406
1591
  </form>`;
1407
1592
  }
1408
1593
  function renderMetadataInspector(formState, key) {
1409
1594
  const value = formState.system?.atlasMetadata[key] ?? "";
1410
1595
  return `<form class="wo-editor-form" data-editor-form="metadata">
1411
1596
  <h2>Metadata</h2>
1412
- ${renderTextField("Key", "metadata-key", key)}
1413
- ${renderTextAreaField("Value", "metadata-value", value)}
1597
+ ${renderInspectorSection("metadata", "entry", "Entry", `${renderTextField("Key", "metadata-key", key)}
1598
+ ${renderTextAreaField("Value", "metadata-value", value)}`, true)}
1414
1599
  </form>`;
1415
1600
  }
1416
1601
  function renderViewpointInspector(formState, id) {
@@ -1420,38 +1605,38 @@ function renderViewpointInspector(formState, id) {
1420
1605
  }
1421
1606
  return `<form class="wo-editor-form" data-editor-form="viewpoint">
1422
1607
  <h2>Viewpoint</h2>
1423
- ${renderTextField("ID", "viewpoint-id", viewpoint.id)}
1424
- ${renderTextField("Label", "viewpoint-label", viewpoint.label)}
1425
- ${renderTextAreaField("Summary", "viewpoint-summary", viewpoint.summary)}
1426
- ${renderTextField("Focus object", "viewpoint-focus", viewpoint.focusObjectId ?? "")}
1427
- ${renderTextField("Selected object", "viewpoint-select", viewpoint.selectedObjectId ?? "")}
1428
- ${renderSelectField("Projection", "viewpoint-projection", [
1608
+ ${renderInspectorSection("viewpoint", "basics", "Basics", `${renderTextField("ID", "viewpoint-id", viewpoint.id)}
1609
+ ${renderTextField("Label", "viewpoint-label", viewpoint.label)}
1610
+ ${renderTextAreaField("Summary", "viewpoint-summary", viewpoint.summary)}
1611
+ ${renderTextField("Focus object", "viewpoint-focus", viewpoint.focusObjectId ?? "")}
1612
+ ${renderTextField("Selected object", "viewpoint-select", viewpoint.selectedObjectId ?? "")}
1613
+ ${renderSelectField("Projection", "viewpoint-projection", [
1429
1614
  ["topdown", "Topdown"],
1430
1615
  ["isometric", "Isometric"],
1431
1616
  ], viewpoint.projection)}
1432
- ${renderSelectField("Preset", "viewpoint-preset", [
1617
+ ${renderSelectField("Preset", "viewpoint-preset", [
1433
1618
  ["", "Document default"],
1434
1619
  ["diagram", "Diagram"],
1435
1620
  ["presentation", "Presentation"],
1436
1621
  ["atlas-card", "Atlas Card"],
1437
1622
  ["markdown", "Markdown"],
1438
1623
  ], viewpoint.preset ?? "")}
1439
- ${renderTextField("Zoom", "viewpoint-zoom", viewpoint.zoom === null ? "" : String(viewpoint.zoom))}
1440
- ${renderTextField("Rotation", "viewpoint-rotation", String(viewpoint.rotationDeg))}
1441
- <fieldset class="wo-editor-fieldset">
1442
- <legend>Layers</legend>
1443
- ${renderCheckboxField("Background", "layer-background", viewpoint.layers.background !== false)}
1444
- ${renderCheckboxField("Guides", "layer-guides", viewpoint.layers.guides !== false)}
1445
- ${renderCheckboxField("Orbits back", "layer-orbits-back", viewpoint.layers["orbits-back"] !== false)}
1446
- ${renderCheckboxField("Orbits front", "layer-orbits-front", viewpoint.layers["orbits-front"] !== false)}
1447
- ${renderCheckboxField("Objects", "layer-objects", viewpoint.layers.objects !== false)}
1448
- ${renderCheckboxField("Labels", "layer-labels", viewpoint.layers.labels !== false)}
1449
- ${renderCheckboxField("Metadata", "layer-metadata", viewpoint.layers.metadata !== false)}
1450
- </fieldset>
1451
- ${renderTextField("Filter query", "filter-query", viewpoint.filter?.query ?? "")}
1452
- ${renderTextField("Filter object types", "filter-object-types", viewpoint.filter?.objectTypes.join(" ") ?? "")}
1453
- ${renderTextField("Filter tags", "filter-tags", viewpoint.filter?.tags.join(" ") ?? "")}
1454
- ${renderTextField("Filter groups", "filter-groups", viewpoint.filter?.groupIds.join(" ") ?? "")}
1624
+ ${renderTextField("Zoom", "viewpoint-zoom", viewpoint.zoom === null ? "" : String(viewpoint.zoom))}
1625
+ ${renderTextField("Rotation", "viewpoint-rotation", String(viewpoint.rotationDeg))}`, true)}
1626
+ ${renderInspectorSection("viewpoint", "layers", "Layers", `<fieldset class="wo-editor-fieldset">
1627
+ <legend>Layers</legend>
1628
+ ${renderCheckboxField("Background", "layer-background", viewpoint.layers.background !== false)}
1629
+ ${renderCheckboxField("Guides", "layer-guides", viewpoint.layers.guides !== false)}
1630
+ ${renderCheckboxField("Orbits back", "layer-orbits-back", viewpoint.layers["orbits-back"] !== false)}
1631
+ ${renderCheckboxField("Orbits front", "layer-orbits-front", viewpoint.layers["orbits-front"] !== false)}
1632
+ ${renderCheckboxField("Objects", "layer-objects", viewpoint.layers.objects !== false)}
1633
+ ${renderCheckboxField("Labels", "layer-labels", viewpoint.layers.labels !== false)}
1634
+ ${renderCheckboxField("Metadata", "layer-metadata", viewpoint.layers.metadata !== false)}
1635
+ </fieldset>`)}
1636
+ ${renderInspectorSection("viewpoint", "filter", "Filter", `${renderTextField("Filter query", "filter-query", viewpoint.filter?.query ?? "")}
1637
+ ${renderTextField("Filter object types", "filter-object-types", viewpoint.filter?.objectTypes.join(" ") ?? "")}
1638
+ ${renderTextField("Filter tags", "filter-tags", viewpoint.filter?.tags.join(" ") ?? "")}
1639
+ ${renderTextField("Filter groups", "filter-groups", viewpoint.filter?.groupIds.join(" ") ?? "")}`)}
1455
1640
  </form>`;
1456
1641
  }
1457
1642
  function renderAnnotationInspector(formState, id) {
@@ -1461,12 +1646,12 @@ function renderAnnotationInspector(formState, id) {
1461
1646
  }
1462
1647
  return `<form class="wo-editor-form" data-editor-form="annotation">
1463
1648
  <h2>Annotation</h2>
1464
- ${renderTextField("ID", "annotation-id", annotation.id)}
1465
- ${renderTextField("Label", "annotation-label", annotation.label)}
1466
- ${renderTextField("Target object", "annotation-target", annotation.targetObjectId ?? "")}
1467
- ${renderTextField("Source object", "annotation-source", annotation.sourceObjectId ?? "")}
1468
- ${renderTextAreaField("Body", "annotation-body", annotation.body)}
1469
- ${renderTextField("Tags", "annotation-tags", annotation.tags.join(" "))}
1649
+ ${renderInspectorSection("annotation", "entry", "Entry", `${renderTextField("ID", "annotation-id", annotation.id)}
1650
+ ${renderTextField("Label", "annotation-label", annotation.label)}
1651
+ ${renderTextField("Target object", "annotation-target", annotation.targetObjectId ?? "")}
1652
+ ${renderTextField("Source object", "annotation-source", annotation.sourceObjectId ?? "")}
1653
+ ${renderTextAreaField("Body", "annotation-body", annotation.body)}
1654
+ ${renderTextField("Tags", "annotation-tags", annotation.tags.join(" "))}`, true)}
1470
1655
  </form>`;
1471
1656
  }
1472
1657
  function renderObjectInspector(formState, id) {
@@ -1485,62 +1670,62 @@ function renderObjectInspector(formState, id) {
1485
1670
  : "";
1486
1671
  return `<form class="wo-editor-form" data-editor-form="object">
1487
1672
  <h2>Object</h2>
1488
- ${renderTextField("ID", "object-id", object.id)}
1489
- ${renderSelectField("Type", "object-type", OBJECT_TYPES.map((type) => [type, humanizeIdentifier(type)]), object.type)}
1490
- ${renderSelectField("Placement mode", "placement-mode", [
1673
+ ${renderInspectorSection("object", "identity", "Identity", `${renderTextField("ID", "object-id", object.id)}
1674
+ ${renderSelectField("Type", "object-type", OBJECT_TYPES.map((type) => [type, humanizeIdentifier(type)]), object.type)}`, true)}
1675
+ ${renderInspectorSection("object", "placement", "Placement", `${renderSelectField("Placement mode", "placement-mode", [
1491
1676
  ["", "None"],
1492
1677
  ["orbit", "Orbit"],
1493
1678
  ["at", "At"],
1494
1679
  ["surface", "Surface"],
1495
1680
  ["free", "Free"],
1496
1681
  ], placementMode)}
1497
- ${renderTextField("Placement target", "placement-target", placementTarget)}
1498
- ${renderTextField("Free value", "placement-free", freeValue)}
1499
- ${renderTextField("Distance", "placement-distance", object.placement?.mode === "orbit" && object.placement.distance ? formatUnitValue(object.placement.distance) : "")}
1500
- ${renderTextField("Semi-major", "placement-semiMajor", object.placement?.mode === "orbit" && object.placement.semiMajor ? formatUnitValue(object.placement.semiMajor) : "")}
1501
- ${renderTextField("Eccentricity", "placement-eccentricity", object.placement?.mode === "orbit" && object.placement.eccentricity !== undefined ? String(object.placement.eccentricity) : "")}
1502
- ${renderTextField("Period", "placement-period", object.placement?.mode === "orbit" && object.placement.period ? formatUnitValue(object.placement.period) : "")}
1503
- ${renderTextField("Angle", "placement-angle", object.placement?.mode === "orbit" && object.placement.angle ? formatUnitValue(object.placement.angle) : "")}
1504
- ${renderTextField("Inclination", "placement-inclination", object.placement?.mode === "orbit" && object.placement.inclination ? formatUnitValue(object.placement.inclination) : "")}
1505
- ${renderTextField("Phase", "placement-phase", object.placement?.mode === "orbit" && object.placement.phase ? formatUnitValue(object.placement.phase) : "")}
1506
- <fieldset class="wo-editor-fieldset">
1507
- <legend>Properties</legend>
1508
- ${renderTextField("Kind", "prop-kind", readStringProperty(object.properties.kind))}
1509
- ${renderTextField("Class", "prop-class", readStringProperty(object.properties.class))}
1510
- ${renderTextField("Culture", "prop-culture", readStringProperty(object.properties.culture))}
1511
- ${renderTextField("Tags", "prop-tags", readTagsProperty(object.properties.tags))}
1512
- ${renderTextField("Color", "prop-color", readStringProperty(object.properties.color))}
1513
- ${renderTextField("Image", "prop-image", readStringProperty(object.properties.image))}
1514
- ${renderCheckboxField("Hidden", "prop-hidden", object.properties.hidden === true)}
1515
- ${renderTextField("Radius", "prop-radius", readUnitProperty(object.properties.radius))}
1516
- ${renderTextField("Mass", "prop-mass", readUnitProperty(object.properties.mass))}
1517
- ${renderTextField("Density", "prop-density", readUnitProperty(object.properties.density))}
1518
- ${renderTextField("Gravity", "prop-gravity", readUnitProperty(object.properties.gravity))}
1519
- ${renderTextField("Temperature", "prop-temperature", readUnitProperty(object.properties.temperature))}
1520
- ${renderTextField("Albedo", "prop-albedo", readNumberProperty(object.properties.albedo))}
1521
- ${renderTextField("Atmosphere", "prop-atmosphere", readStringProperty(object.properties.atmosphere))}
1522
- ${renderTextField("Inner", "prop-inner", readUnitProperty(object.properties.inner))}
1523
- ${renderTextField("Outer", "prop-outer", readUnitProperty(object.properties.outer))}
1524
- ${renderTextField("On", "prop-on", readStringProperty(object.properties.on))}
1525
- ${renderTextField("Source", "prop-source", readStringProperty(object.properties.source))}
1526
- ${renderTextField("Cycle", "prop-cycle", readUnitProperty(object.properties.cycle))}
1527
- </fieldset>
1528
- ${renderTextAreaField("Description", "info-description", object.info.description ?? "")}
1682
+ ${renderTextField("Placement target", "placement-target", placementTarget)}
1683
+ ${renderTextField("Free value", "placement-free", freeValue)}
1684
+ ${renderTextField("Distance", "placement-distance", object.placement?.mode === "orbit" && object.placement.distance ? formatUnitValue(object.placement.distance) : "")}
1685
+ ${renderTextField("Semi-major", "placement-semiMajor", object.placement?.mode === "orbit" && object.placement.semiMajor ? formatUnitValue(object.placement.semiMajor) : "")}
1686
+ ${renderTextField("Eccentricity", "placement-eccentricity", object.placement?.mode === "orbit" && object.placement.eccentricity !== undefined ? String(object.placement.eccentricity) : "")}
1687
+ ${renderTextField("Period", "placement-period", object.placement?.mode === "orbit" && object.placement.period ? formatUnitValue(object.placement.period) : "")}
1688
+ ${renderTextField("Angle", "placement-angle", object.placement?.mode === "orbit" && object.placement.angle ? formatUnitValue(object.placement.angle) : "")}
1689
+ ${renderTextField("Inclination", "placement-inclination", object.placement?.mode === "orbit" && object.placement.inclination ? formatUnitValue(object.placement.inclination) : "")}
1690
+ ${renderTextField("Phase", "placement-phase", object.placement?.mode === "orbit" && object.placement.phase ? formatUnitValue(object.placement.phase) : "")}`, true)}
1691
+ ${renderInspectorSection("object", "properties", "Properties", `<fieldset class="wo-editor-fieldset">
1692
+ <legend>Properties</legend>
1693
+ ${renderTextField("Kind", "prop-kind", readStringProperty(object.properties.kind))}
1694
+ ${renderTextField("Class", "prop-class", readStringProperty(object.properties.class))}
1695
+ ${renderTextField("Culture", "prop-culture", readStringProperty(object.properties.culture))}
1696
+ ${renderTextField("Tags", "prop-tags", readTagsProperty(object.properties.tags))}
1697
+ ${renderTextField("Color", "prop-color", readStringProperty(object.properties.color))}
1698
+ ${renderTextField("Image", "prop-image", readStringProperty(object.properties.image))}
1699
+ ${renderCheckboxField("Hidden", "prop-hidden", object.properties.hidden === true)}
1700
+ ${renderTextField("Radius", "prop-radius", readUnitProperty(object.properties.radius))}
1701
+ ${renderTextField("Mass", "prop-mass", readUnitProperty(object.properties.mass))}
1702
+ ${renderTextField("Density", "prop-density", readUnitProperty(object.properties.density))}
1703
+ ${renderTextField("Gravity", "prop-gravity", readUnitProperty(object.properties.gravity))}
1704
+ ${renderTextField("Temperature", "prop-temperature", readUnitProperty(object.properties.temperature))}
1705
+ ${renderTextField("Albedo", "prop-albedo", readNumberProperty(object.properties.albedo))}
1706
+ ${renderTextField("Atmosphere", "prop-atmosphere", readStringProperty(object.properties.atmosphere))}
1707
+ ${renderTextField("Inner", "prop-inner", readUnitProperty(object.properties.inner))}
1708
+ ${renderTextField("Outer", "prop-outer", readUnitProperty(object.properties.outer))}
1709
+ ${renderTextField("On", "prop-on", readStringProperty(object.properties.on))}
1710
+ ${renderTextField("Source", "prop-source", readStringProperty(object.properties.source))}
1711
+ ${renderTextField("Cycle", "prop-cycle", readUnitProperty(object.properties.cycle))}
1712
+ </fieldset>`)}
1713
+ ${renderInspectorSection("object", "info", "Info", `${renderTextAreaField("Description", "info-description", object.info.description ?? "")}`)}
1529
1714
  </form>`;
1530
1715
  }
1531
1716
  function renderTextField(label, name, value) {
1532
- return `<label class="wo-editor-field"><span>${escapeHtml(label)}</span><input name="${escapeHtml(name)}" value="${escapeAttribute(value)}" /></label>`;
1717
+ return `<label class="wo-editor-field">${renderFieldLabel(label, name)}<input name="${escapeHtml(name)}" value="${escapeAttribute(value)}" /></label>`;
1533
1718
  }
1534
1719
  function renderTextAreaField(label, name, value) {
1535
- return `<label class="wo-editor-field"><span>${escapeHtml(label)}</span><textarea name="${escapeHtml(name)}">${escapeHtml(value)}</textarea></label>`;
1720
+ return `<label class="wo-editor-field">${renderFieldLabel(label, name)}<textarea name="${escapeHtml(name)}">${escapeHtml(value)}</textarea></label>`;
1536
1721
  }
1537
1722
  function renderSelectField(label, name, options, selectedValue) {
1538
- return `<label class="wo-editor-field"><span>${escapeHtml(label)}</span><select name="${escapeHtml(name)}">${options
1723
+ return `<label class="wo-editor-field">${renderFieldLabel(label, name)}<select name="${escapeHtml(name)}">${options
1539
1724
  .map(([value, optionLabel]) => `<option value="${escapeHtml(value)}"${value === selectedValue ? " selected" : ""}>${escapeHtml(optionLabel)}</option>`)
1540
1725
  .join("")}</select></label>`;
1541
1726
  }
1542
1727
  function renderCheckboxField(label, name, checked) {
1543
- return `<label class="wo-editor-checkbox"><input type="checkbox" name="${escapeHtml(name)}"${checked ? " checked" : ""} /><span>${escapeHtml(label)}</span></label>`;
1728
+ return `<label class="wo-editor-checkbox"><input type="checkbox" name="${escapeHtml(name)}"${checked ? " checked" : ""} />${renderFieldLabel(label, name)}</label>`;
1544
1729
  }
1545
1730
  function createHandleElement(kind, objectId, point, label) {
1546
1731
  const element = document.createElement("button");
@@ -1797,15 +1982,15 @@ function updateOrbitPhase(document, objectId, details, pointer) {
1797
1982
  };
1798
1983
  return next;
1799
1984
  }
1800
- function updateOrbitRadius(document, objectId, details, pointer) {
1985
+ function updateOrbitRadius(document, objectId, details, pointer, dragContext) {
1801
1986
  const orbit = details.orbit;
1802
- if (!orbit || details.object.placement?.mode !== "orbit") {
1987
+ if (!orbit || details.object.placement?.mode !== "orbit" || !dragContext) {
1803
1988
  return document;
1804
1989
  }
1805
1990
  const unrotated = rotatePoint(pointer, { x: orbit.cx, y: orbit.cy }, -orbit.rotationDeg);
1806
- const currentRadius = orbit.kind === "circle" ? orbit.radius ?? 1 : orbit.rx ?? 1;
1807
- const nextRadius = Math.max(Math.abs(unrotated.x - orbit.cx), 24);
1808
- const ratio = nextRadius / Math.max(currentRadius, 1);
1991
+ const nextDisplayedRadius = Math.max(Math.abs(unrotated.x - orbit.cx), 24);
1992
+ const nextBaseRadius = Math.max(nextDisplayedRadius - dragContext.radiusOffsetPx, dragContext.innerPx);
1993
+ const nextMetric = orbitRadiusPxToMetric(nextBaseRadius, dragContext.innerPx, dragContext.stepPx);
1809
1994
  const next = cloneAtlasDocument(document);
1810
1995
  const object = next.objects.find((entry) => entry.id === objectId);
1811
1996
  if (!object || object.placement?.mode !== "orbit") {
@@ -1816,10 +2001,7 @@ function updateOrbitRadius(document, objectId, details, pointer) {
1816
2001
  value: 1,
1817
2002
  unit: "au",
1818
2003
  };
1819
- const scaled = {
1820
- value: roundNumber(currentValue.value * ratio, 3),
1821
- unit: currentValue.unit,
1822
- };
2004
+ const scaled = distanceMetricToUnitValue(Math.max(nextMetric, 0), dragContext.preferredUnit ?? currentValue.unit);
1823
2005
  if (object.placement.semiMajor) {
1824
2006
  object.placement.semiMajor = scaled;
1825
2007
  }
@@ -1855,6 +2037,28 @@ function updateSurfaceTarget(document, objectId, scene, pointer) {
1855
2037
  object.placement.target = target.objectId;
1856
2038
  return next;
1857
2039
  }
2040
+ function createOrbitRadiusDragContext(document, scene, details) {
2041
+ if (details.object.placement?.mode !== "orbit" || !details.orbit || !details.parent) {
2042
+ return null;
2043
+ }
2044
+ const targetId = details.object.placement.target;
2045
+ const siblingCount = document.objects.filter((entry) => entry.placement?.mode === "orbit" &&
2046
+ entry.placement.target === targetId).length;
2047
+ const spacingFactor = layoutPresetSpacingForScene(scene.layoutPreset);
2048
+ const stepPx = (siblingCount > 2 ? 54 : 64) * spacingFactor * scene.scaleModel.orbitDistanceMultiplier;
2049
+ const innerPx = details.parent.radius +
2050
+ 56 * spacingFactor * scene.scaleModel.orbitDistanceMultiplier;
2051
+ const currentValue = details.object.placement.semiMajor ?? details.object.placement.distance ?? null;
2052
+ const currentMetric = unitValueToDistanceMetric(currentValue);
2053
+ const displayedRadius = details.orbit.kind === "circle" ? details.orbit.radius ?? 1 : details.orbit.rx ?? 1;
2054
+ const baseRadius = orbitMetricToRadiusPx(currentMetric ?? 0, innerPx, stepPx);
2055
+ return {
2056
+ innerPx,
2057
+ stepPx,
2058
+ radiusOffsetPx: displayedRadius - baseRadius,
2059
+ preferredUnit: currentValue?.unit ?? null,
2060
+ };
2061
+ }
1858
2062
  function updateFreeDistance(document, objectId, scene, details, pointer) {
1859
2063
  if (details.object.placement?.mode !== "free") {
1860
2064
  return document;
@@ -2278,6 +2482,23 @@ function normalizeFreeDistanceUnit(unit) {
2278
2482
  return null;
2279
2483
  }
2280
2484
  }
2485
+ function unitValueToDistanceMetric(value) {
2486
+ if (!value) {
2487
+ return null;
2488
+ }
2489
+ switch (value.unit) {
2490
+ case "au":
2491
+ return value.value;
2492
+ case "km":
2493
+ return value.value / AU_IN_KM;
2494
+ case "re":
2495
+ return (value.value * EARTH_RADIUS_IN_KM) / AU_IN_KM;
2496
+ case "sol":
2497
+ return (value.value * SOLAR_RADIUS_IN_KM) / AU_IN_KM;
2498
+ default:
2499
+ return value.value;
2500
+ }
2501
+ }
2281
2502
  function distanceMetricToUnitValue(metric, unit) {
2282
2503
  switch (unit) {
2283
2504
  case "km":
@@ -2292,6 +2513,28 @@ function distanceMetricToUnitValue(metric, unit) {
2292
2513
  return { value: roundNumber(metric, 2), unit: null };
2293
2514
  }
2294
2515
  }
2516
+ function orbitMetricToRadiusPx(metric, innerPx, stepPx) {
2517
+ return innerPx + stepPx * log2(Math.max(metric, 0) + 1);
2518
+ }
2519
+ function orbitRadiusPxToMetric(radiusPx, innerPx, stepPx) {
2520
+ if (radiusPx <= innerPx) {
2521
+ return 0;
2522
+ }
2523
+ return Math.pow(2, (radiusPx - innerPx) / Math.max(stepPx, 0.0001)) - 1;
2524
+ }
2525
+ function layoutPresetSpacingForScene(layoutPreset) {
2526
+ switch (layoutPreset) {
2527
+ case "compact":
2528
+ return 0.84;
2529
+ case "presentation":
2530
+ return 1.2;
2531
+ default:
2532
+ return 1;
2533
+ }
2534
+ }
2535
+ function log2(value) {
2536
+ return Math.log(value) / Math.log(2);
2537
+ }
2295
2538
  function escapeHtml(value) {
2296
2539
  return value
2297
2540
  .replaceAll("&", "&amp;")
@@ -2391,6 +2634,62 @@ function installEditorStyles() {
2391
2634
  text-transform: uppercase;
2392
2635
  letter-spacing: 0.08em;
2393
2636
  }
2637
+ .wo-editor-panel h3 {
2638
+ margin: 0 0 12px;
2639
+ color: rgba(237, 246, 255, 0.82);
2640
+ font: 700 12px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
2641
+ text-transform: uppercase;
2642
+ letter-spacing: 0.08em;
2643
+ }
2644
+ .wo-editor-panel-section,
2645
+ .wo-editor-inspector-section {
2646
+ min-width: 0;
2647
+ }
2648
+ .wo-editor-panel-section > summary,
2649
+ .wo-editor-inspector-section > summary {
2650
+ display: flex;
2651
+ align-items: center;
2652
+ justify-content: space-between;
2653
+ gap: 10px;
2654
+ cursor: pointer;
2655
+ list-style: none;
2656
+ color: #edf6ff;
2657
+ font: 700 14px/1.2 "Segoe UI Variable Display", "Segoe UI", sans-serif;
2658
+ text-transform: uppercase;
2659
+ letter-spacing: 0.08em;
2660
+ }
2661
+ .wo-editor-inspector-section > summary {
2662
+ font: 700 12px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
2663
+ color: rgba(237, 246, 255, 0.9);
2664
+ }
2665
+ .wo-editor-panel-section > summary::-webkit-details-marker,
2666
+ .wo-editor-inspector-section > summary::-webkit-details-marker,
2667
+ .wo-editor-field-help > summary::-webkit-details-marker {
2668
+ display: none;
2669
+ }
2670
+ .wo-editor-panel-section > summary::after,
2671
+ .wo-editor-inspector-section > summary::after {
2672
+ content: "▾";
2673
+ color: rgba(240, 180, 100, 0.86);
2674
+ font-size: 12px;
2675
+ transition: transform 0.18s ease;
2676
+ }
2677
+ .wo-editor-panel-section:not([open]) > summary::after,
2678
+ .wo-editor-inspector-section:not([open]) > summary::after {
2679
+ transform: rotate(-90deg);
2680
+ }
2681
+ .wo-editor-panel-section-body {
2682
+ display: grid;
2683
+ gap: 16px;
2684
+ margin-top: 14px;
2685
+ min-width: 0;
2686
+ }
2687
+ .wo-editor-inspector-section-body {
2688
+ display: grid;
2689
+ gap: 12px;
2690
+ margin-top: 12px;
2691
+ min-width: 0;
2692
+ }
2394
2693
  .wo-editor[data-wo-show-inspector="false"] [data-editor-pane="inspector"] { display: none; }
2395
2694
  .wo-editor[data-wo-show-text-pane="false"] [data-editor-pane="text"] { display: none; }
2396
2695
  .wo-editor[data-wo-show-preview="false"] [data-editor-pane="preview"] { display: none; }
@@ -2519,8 +2818,23 @@ function installEditorStyles() {
2519
2818
  margin-bottom: 14px;
2520
2819
  }
2521
2820
  .wo-editor-form { display: grid; gap: 12px; min-width: 0; }
2821
+ .wo-editor-inspector-section {
2822
+ border: 1px solid rgba(255,255,255,0.08);
2823
+ border-radius: 18px;
2824
+ padding: 14px;
2825
+ background: rgba(255,255,255,0.02);
2826
+ }
2522
2827
  .wo-editor-field { display: grid; gap: 6px; }
2523
- .wo-editor-field span, .wo-editor-fieldset legend {
2828
+ .wo-editor-field-label,
2829
+ .wo-editor-fieldset legend {
2830
+ display: flex;
2831
+ align-items: center;
2832
+ justify-content: space-between;
2833
+ gap: 10px;
2834
+ }
2835
+ .wo-editor-field-label,
2836
+ .wo-editor-checkbox .wo-editor-field-label,
2837
+ .wo-editor-fieldset legend {
2524
2838
  color: rgba(237,246,255,0.72);
2525
2839
  font: 600 11px/1.2 "Segoe UI Variable", "Segoe UI", sans-serif;
2526
2840
  text-transform: uppercase;
@@ -2573,11 +2887,69 @@ function installEditorStyles() {
2573
2887
  .wo-editor-checkbox {
2574
2888
  display: flex;
2575
2889
  gap: 10px;
2576
- align-items: center;
2890
+ align-items: flex-start;
2577
2891
  color: #edf6ff;
2578
2892
  font: 500 13px/1.4 "Segoe UI Variable", "Segoe UI", sans-serif;
2579
2893
  }
2894
+ .wo-editor-checkbox input {
2895
+ margin-top: 2px;
2896
+ }
2897
+ .wo-editor-field-help {
2898
+ position: relative;
2899
+ flex: 0 0 auto;
2900
+ }
2901
+ .wo-editor-field-help > summary {
2902
+ display: inline-flex;
2903
+ align-items: center;
2904
+ justify-content: center;
2905
+ width: 20px;
2906
+ height: 20px;
2907
+ border-radius: 999px;
2908
+ border: 1px solid rgba(240, 180, 100, 0.28);
2909
+ background: rgba(240, 180, 100, 0.12);
2910
+ color: #ffdda9;
2911
+ cursor: pointer;
2912
+ font: 700 11px/1 "Segoe UI Variable", "Segoe UI", sans-serif;
2913
+ list-style: none;
2914
+ }
2915
+ .wo-editor-field-help-card {
2916
+ display: grid;
2917
+ gap: 8px;
2918
+ margin-top: 8px;
2919
+ padding: 10px 12px;
2920
+ border-radius: 14px;
2921
+ border: 1px solid rgba(240, 180, 100, 0.2);
2922
+ background: rgba(12, 26, 39, 0.92);
2923
+ color: rgba(237, 246, 255, 0.86);
2924
+ text-transform: none;
2925
+ letter-spacing: normal;
2926
+ font: 500 12px/1.45 "Segoe UI Variable", "Segoe UI", sans-serif;
2927
+ }
2928
+ .wo-editor-field-help-card p {
2929
+ margin: 0;
2930
+ }
2931
+ .wo-editor-field-help-chips {
2932
+ display: flex;
2933
+ flex-wrap: wrap;
2934
+ gap: 6px;
2935
+ }
2936
+ .wo-editor-field-help-chips span {
2937
+ border-radius: 999px;
2938
+ padding: 4px 8px;
2939
+ background: rgba(255,255,255,0.06);
2940
+ color: #edf6ff;
2941
+ font: 600 11px/1.3 "Segoe UI Variable", "Segoe UI", sans-serif;
2942
+ text-transform: none;
2943
+ letter-spacing: normal;
2944
+ }
2580
2945
  .wo-editor-preview { grid-template-columns: repeat(2, minmax(0, 1fr)); }
2946
+ .wo-editor-preview-card {
2947
+ border-radius: 18px;
2948
+ border: 1px solid rgba(255,255,255,0.08);
2949
+ background: rgba(255,255,255,0.03);
2950
+ padding: 14px;
2951
+ min-width: 0;
2952
+ }
2581
2953
  .wo-editor-preview-visual {
2582
2954
  min-height: 240px;
2583
2955
  overflow: auto;