worldorbit 2.5.1-5.2

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 (117) hide show
  1. package/LICENSE.md +5 -0
  2. package/README.md +468 -0
  3. package/dist/browser/core/dist/index.js +4009 -0
  4. package/dist/browser/markdown/dist/index.js +3951 -0
  5. package/dist/browser/viewer/dist/index.js +5981 -0
  6. package/dist/constants.d.ts +8 -0
  7. package/dist/constants.js +84 -0
  8. package/dist/errors.d.ts +7 -0
  9. package/dist/errors.js +16 -0
  10. package/dist/index.d.ts +18 -0
  11. package/dist/index.js +25 -0
  12. package/dist/normalize.d.ts +2 -0
  13. package/dist/normalize.js +243 -0
  14. package/dist/parse.d.ts +2 -0
  15. package/dist/parse.js +126 -0
  16. package/dist/render.d.ts +6 -0
  17. package/dist/render.js +683 -0
  18. package/dist/tokenize.d.ts +4 -0
  19. package/dist/tokenize.js +68 -0
  20. package/dist/types.d.ts +208 -0
  21. package/dist/types.js +1 -0
  22. package/dist/unpkg/core/dist/index.js +4081 -0
  23. package/dist/unpkg/markdown/dist/index.js +3979 -0
  24. package/dist/unpkg/viewer/dist/index.js +6038 -0
  25. package/dist/unpkg/worldorbit-core.min.js +12 -0
  26. package/dist/unpkg/worldorbit-markdown.min.js +95 -0
  27. package/dist/unpkg/worldorbit-viewer.min.js +251 -0
  28. package/dist/unpkg/worldorbit.d.ts +2 -0
  29. package/dist/unpkg/worldorbit.esm.js +2 -0
  30. package/dist/unpkg/worldorbit.js +8524 -0
  31. package/dist/unpkg/worldorbit.min.js +255 -0
  32. package/dist/validate.d.ts +2 -0
  33. package/dist/validate.js +31 -0
  34. package/dist/viewer-state.d.ts +16 -0
  35. package/dist/viewer-state.js +130 -0
  36. package/dist/viewer.d.ts +2 -0
  37. package/dist/viewer.js +434 -0
  38. package/package.json +68 -0
  39. package/packages/core/README.md +17 -0
  40. package/packages/core/dist/atlas-edit.d.ts +11 -0
  41. package/packages/core/dist/atlas-edit.js +273 -0
  42. package/packages/core/dist/atlas-utils.d.ts +22 -0
  43. package/packages/core/dist/atlas-utils.js +189 -0
  44. package/packages/core/dist/atlas-validate.d.ts +2 -0
  45. package/packages/core/dist/atlas-validate.js +285 -0
  46. package/packages/core/dist/diagnostics.d.ts +10 -0
  47. package/packages/core/dist/diagnostics.js +109 -0
  48. package/packages/core/dist/draft-parse.d.ts +3 -0
  49. package/packages/core/dist/draft-parse.js +1275 -0
  50. package/packages/core/dist/draft.d.ts +18 -0
  51. package/packages/core/dist/draft.js +387 -0
  52. package/packages/core/dist/errors.d.ts +7 -0
  53. package/packages/core/dist/errors.js +16 -0
  54. package/packages/core/dist/format.d.ts +4 -0
  55. package/packages/core/dist/format.js +520 -0
  56. package/packages/core/dist/index.d.ts +28 -0
  57. package/packages/core/dist/index.js +44 -0
  58. package/packages/core/dist/load.d.ts +4 -0
  59. package/packages/core/dist/load.js +175 -0
  60. package/packages/core/dist/markdown.d.ts +2 -0
  61. package/packages/core/dist/markdown.js +37 -0
  62. package/packages/core/dist/normalize.d.ts +2 -0
  63. package/packages/core/dist/normalize.js +311 -0
  64. package/packages/core/dist/parse.d.ts +2 -0
  65. package/packages/core/dist/parse.js +133 -0
  66. package/packages/core/dist/scene.d.ts +3 -0
  67. package/packages/core/dist/scene.js +1565 -0
  68. package/packages/core/dist/schema.d.ts +8 -0
  69. package/packages/core/dist/schema.js +298 -0
  70. package/packages/core/dist/tokenize.d.ts +4 -0
  71. package/packages/core/dist/tokenize.js +68 -0
  72. package/packages/core/dist/types.d.ts +476 -0
  73. package/packages/core/dist/types.js +1 -0
  74. package/packages/core/dist/validate.d.ts +2 -0
  75. package/packages/core/dist/validate.js +56 -0
  76. package/packages/editor/dist/editor.d.ts +2 -0
  77. package/packages/editor/dist/editor.js +3042 -0
  78. package/packages/editor/dist/index.d.ts +2 -0
  79. package/packages/editor/dist/index.js +1 -0
  80. package/packages/editor/dist/types.d.ts +53 -0
  81. package/packages/editor/dist/types.js +1 -0
  82. package/packages/markdown/README.md +9 -0
  83. package/packages/markdown/dist/html.d.ts +3 -0
  84. package/packages/markdown/dist/html.js +57 -0
  85. package/packages/markdown/dist/index.d.ts +4 -0
  86. package/packages/markdown/dist/index.js +3 -0
  87. package/packages/markdown/dist/rehype.d.ts +10 -0
  88. package/packages/markdown/dist/rehype.js +49 -0
  89. package/packages/markdown/dist/remark.d.ts +9 -0
  90. package/packages/markdown/dist/remark.js +28 -0
  91. package/packages/markdown/dist/types.d.ts +11 -0
  92. package/packages/markdown/dist/types.js +1 -0
  93. package/packages/viewer/README.md +13 -0
  94. package/packages/viewer/dist/atlas-state.d.ts +12 -0
  95. package/packages/viewer/dist/atlas-state.js +257 -0
  96. package/packages/viewer/dist/atlas-viewer.d.ts +2 -0
  97. package/packages/viewer/dist/atlas-viewer.js +482 -0
  98. package/packages/viewer/dist/custom-element.d.ts +1 -0
  99. package/packages/viewer/dist/custom-element.js +64 -0
  100. package/packages/viewer/dist/embed.d.ts +20 -0
  101. package/packages/viewer/dist/embed.js +138 -0
  102. package/packages/viewer/dist/index.d.ts +9 -0
  103. package/packages/viewer/dist/index.js +8 -0
  104. package/packages/viewer/dist/minimap.d.ts +3 -0
  105. package/packages/viewer/dist/minimap.js +63 -0
  106. package/packages/viewer/dist/render.d.ts +6 -0
  107. package/packages/viewer/dist/render.js +641 -0
  108. package/packages/viewer/dist/theme.d.ts +4 -0
  109. package/packages/viewer/dist/theme.js +102 -0
  110. package/packages/viewer/dist/tooltip.d.ts +3 -0
  111. package/packages/viewer/dist/tooltip.js +189 -0
  112. package/packages/viewer/dist/types.d.ts +263 -0
  113. package/packages/viewer/dist/types.js +1 -0
  114. package/packages/viewer/dist/viewer-state.d.ts +19 -0
  115. package/packages/viewer/dist/viewer-state.js +162 -0
  116. package/packages/viewer/dist/viewer.d.ts +2 -0
  117. package/packages/viewer/dist/viewer.js +1175 -0
@@ -0,0 +1,641 @@
1
+ import { loadWorldOrbitSource, renderDocumentToScene, } from "@worldorbit/core";
2
+ import { computeVisibleObjectIds } from "./atlas-state.js";
3
+ import { resolveLayers, resolveTheme } from "./theme.js";
4
+ export const WORLD_LAYER_ID = "worldorbit-camera-root";
5
+ export function renderSceneToSvg(scene, options = {}) {
6
+ const theme = resolveTheme(options.theme);
7
+ const presetDefaults = resolveRenderPreset(options.preset ?? scene.renderPreset ?? undefined);
8
+ const layers = resolveLayers({
9
+ ...presetDefaults.layers,
10
+ ...options.layers,
11
+ });
12
+ const subtitle = options.subtitle ?? scene.subtitle;
13
+ const visibleObjectIds = computeVisibleObjectIds(scene, options.filter ?? null);
14
+ const visibleObjects = scene.objects
15
+ .filter((object) => !object.hidden)
16
+ .filter((object) => visibleObjectIds.has(object.objectId))
17
+ .filter((object) => layers.structures || !isStructureLike(object.object))
18
+ .sort((left, right) => left.sortKey - right.sortKey);
19
+ const visibleLabels = scene.labels
20
+ .filter((label) => !label.hidden)
21
+ .filter((label) => visibleObjectIds.has(label.objectId))
22
+ .filter((label) => layers.structures || !isStructureLike(label.object));
23
+ const imageDefinitions = buildImageDefinitions(visibleObjects);
24
+ const orbitMarkup = layers.orbits
25
+ ? renderOrbitLayer(scene, visibleObjectIds, layers.structures)
26
+ : { back: "", front: "" };
27
+ const leaderMarkup = layers.guides
28
+ ? scene.leaders
29
+ .filter((leader) => !leader.hidden)
30
+ .filter((leader) => visibleObjectIds.has(leader.objectId))
31
+ .filter((leader) => layers.structures || !isStructureLike(leader.object))
32
+ .map((leader) => `<line class="wo-leader wo-leader-${leader.mode}" x1="${leader.x1}" y1="${leader.y1}" x2="${leader.x2}" y2="${leader.y2}" data-render-id="${escapeXml(leader.renderId)}" data-group-id="${escapeAttribute(leader.groupId ?? "")}" />`)
33
+ .join("")
34
+ : "";
35
+ const relationMarkup = layers.relations
36
+ ? scene.relations
37
+ .filter((relation) => !relation.hidden)
38
+ .filter((relation) => visibleObjectIds.has(relation.fromObjectId) && visibleObjectIds.has(relation.toObjectId))
39
+ .map((relation) => `<line class="wo-relation" x1="${relation.x1}" y1="${relation.y1}" x2="${relation.x2}" y2="${relation.y2}" data-render-id="${escapeXml(relation.renderId)}" data-relation-id="${escapeAttribute(relation.relationId)}" />`)
40
+ .join("")
41
+ : "";
42
+ const objectMarkup = layers.objects
43
+ ? visibleObjects
44
+ .map((object) => renderSceneObject(object, options.selectedObjectId ?? null, theme))
45
+ .join("")
46
+ : "";
47
+ const labelMarkup = layers.labels
48
+ ? visibleLabels
49
+ .map((label) => renderSceneLabel(scene, label, options.selectedObjectId ?? null))
50
+ .join("")
51
+ : "";
52
+ const metadataMarkup = layers.metadata
53
+ ? `<text class="wo-title" x="56" y="64">${escapeXml(scene.title)}</text>
54
+ <text class="wo-subtitle" x="56" y="88">${escapeXml(subtitle)}</text>
55
+ <text class="wo-meta" x="56" y="${scene.height - 42}">${escapeXml(renderMetadata(scene))}</text>`
56
+ : "";
57
+ const backgroundMarkup = layers.background
58
+ ? `<rect class="wo-bg" x="0" y="0" width="${scene.width}" height="${scene.height}" rx="28" ry="28" />
59
+ <rect class="wo-bg-glow" x="0" y="0" width="${scene.width}" height="${scene.height}" rx="28" ry="28" />
60
+ ${layers.guides ? renderBackdrop(scene.width, scene.height) : ""}`
61
+ : "";
62
+ return `<svg xmlns="http://www.w3.org/2000/svg" data-worldorbit-svg="true" width="${scene.width}" height="${scene.height}" viewBox="0 0 ${scene.width} ${scene.height}" preserveAspectRatio="xMidYMid meet" role="img" aria-labelledby="worldorbit-title worldorbit-desc">
63
+ <title id="worldorbit-title">${escapeXml(scene.title)}</title>
64
+ <desc id="worldorbit-desc">A ${escapeXml(scene.viewMode)} WorldOrbit render with ${visibleObjects.length} visible objects.</desc>
65
+ <defs>
66
+ <linearGradient id="wo-bg" x1="0%" y1="0%" x2="100%" y2="100%">
67
+ <stop offset="0%" stop-color="${theme.backgroundStart}" />
68
+ <stop offset="100%" stop-color="${theme.backgroundEnd}" />
69
+ </linearGradient>
70
+ <radialGradient id="wo-star-glow" cx="50%" cy="50%" r="50%">
71
+ <stop offset="0%" stop-color="${theme.starGlow}" stop-opacity="0.95" />
72
+ <stop offset="100%" stop-color="${theme.starCore}" stop-opacity="0" />
73
+ </radialGradient>
74
+ <radialGradient id="wo-bg-glow" cx="20%" cy="10%" r="90%">
75
+ <stop offset="0%" stop-color="${theme.backgroundGlow}" />
76
+ <stop offset="100%" stop-color="transparent" />
77
+ </radialGradient>
78
+ ${imageDefinitions}
79
+ </defs>
80
+ <style>
81
+ .wo-bg { fill: url(#wo-bg); }
82
+ .wo-bg-glow { fill: url(#wo-bg-glow); }
83
+ .wo-grid { fill: none; stroke: ${theme.guide}; stroke-width: 1; }
84
+ .wo-orbit { fill: none; stroke: ${theme.orbit}; stroke-width: 1.5; }
85
+ .wo-orbit-back { opacity: 0.38; stroke-dasharray: 8 6; }
86
+ .wo-orbit-front { opacity: 0.9; }
87
+ .wo-orbit-band { stroke: ${theme.orbitBand}; stroke-linecap: round; }
88
+ .wo-relation { stroke: ${theme.relation}; stroke-width: 2; stroke-dasharray: 10 6; }
89
+ .wo-leader { stroke: ${theme.leader}; stroke-width: 1.5; stroke-dasharray: 6 5; }
90
+ .wo-label { fill: ${theme.ink}; font-family: ${theme.fontFamily}; font-weight: 600; letter-spacing: 0.02em; }
91
+ .wo-label-secondary { fill: ${theme.muted}; font-family: ${theme.fontFamily}; font-weight: 500; }
92
+ .wo-title { fill: ${theme.ink}; font: 700 24px ${theme.displayFont}; letter-spacing: 0.06em; text-transform: uppercase; }
93
+ .wo-subtitle { fill: ${theme.muted}; font: 500 12px ${theme.fontFamily}; letter-spacing: 0.14em; text-transform: uppercase; }
94
+ .wo-meta { fill: ${theme.muted}; font: 500 11px ${theme.fontFamily}; letter-spacing: 0.06em; }
95
+ .wo-object { cursor: pointer; outline: none; }
96
+ .wo-object:focus-visible .wo-selection-ring,
97
+ .wo-object:hover .wo-selection-ring,
98
+ .wo-object-selected .wo-selection-ring { display: block; }
99
+ .wo-object-selected .wo-selection-ring { stroke: ${theme.selected}; }
100
+ .wo-object-label-selected .wo-label { fill: ${theme.accent}; }
101
+ .wo-object-label-selected .wo-label-secondary { fill: ${theme.selected}; }
102
+ .wo-chain-selected .wo-selection-ring,
103
+ .wo-chain-hover .wo-selection-ring { display: block; }
104
+ .wo-ancestor-selected .wo-selection-ring,
105
+ .wo-ancestor-hover .wo-selection-ring { display: block; opacity: 0.66; }
106
+ .wo-chain-selected .wo-label,
107
+ .wo-chain-hover .wo-label { fill: ${theme.accent}; }
108
+ .wo-ancestor-selected .wo-label,
109
+ .wo-ancestor-hover .wo-label { fill: ${theme.ink}; opacity: 0.82; }
110
+ .wo-orbit-related-selected,
111
+ .wo-orbit-related-hover { stroke: ${theme.accentStrong}; opacity: 1; }
112
+ .wo-selection-ring { display: none; fill: none; stroke-width: 2; stroke-dasharray: 6 5; }
113
+ .wo-object-image { pointer-events: none; }
114
+ </style>
115
+ ${backgroundMarkup}
116
+ ${metadataMarkup}
117
+ <g data-worldorbit-world="true">
118
+ <g data-worldorbit-camera-root="${WORLD_LAYER_ID}" id="${WORLD_LAYER_ID}">
119
+ <g data-worldorbit-world-content="true">
120
+ ${layers.orbits ? `<g data-layer-id="orbits-back">${orbitMarkup.back}</g>` : ""}
121
+ ${layers.guides ? `<g data-layer-id="guides">${leaderMarkup}</g>` : ""}
122
+ ${layers.relations ? `<g data-layer-id="relations">${relationMarkup}</g>` : ""}
123
+ ${layers.objects ? `<g data-layer-id="objects">${objectMarkup}</g>` : ""}
124
+ ${layers.orbits ? `<g data-layer-id="orbits-front">${orbitMarkup.front}</g>` : ""}
125
+ ${layers.labels ? `<g data-layer-id="labels">${labelMarkup}</g>` : ""}
126
+ </g>
127
+ </g>
128
+ </g>
129
+ </svg>`;
130
+ }
131
+ export function renderDocumentToSvg(document, options = {}) {
132
+ return renderSceneToSvg(renderDocumentToScene(document, options), options);
133
+ }
134
+ export function renderSourceToSvg(source, options = {}) {
135
+ const loaded = loadWorldOrbitSource(source);
136
+ return renderDocumentToSvg(loaded.document, resolveSourceRenderOptions(loaded, options));
137
+ }
138
+ function resolveSourceRenderOptions(loaded, options) {
139
+ const atlasDocument = loaded.atlasDocument ?? loaded.draftDocument;
140
+ if (options.preset || !atlasDocument?.system?.defaults.preset) {
141
+ return options;
142
+ }
143
+ return {
144
+ ...options,
145
+ preset: atlasDocument.system.defaults.preset,
146
+ };
147
+ }
148
+ function renderOrbitLayer(scene, visibleObjectIds, includeStructures) {
149
+ const backParts = [];
150
+ const frontParts = [];
151
+ for (const visual of scene.orbitVisuals.filter((entry) => !entry.hidden &&
152
+ visibleObjectIds.has(entry.objectId) &&
153
+ (includeStructures || !isStructureLike(entry.object)))) {
154
+ const strokeWidth = visual.bandThickness ?? (visual.band ? 10 : 1.5);
155
+ const orbitClass = visual.band ? "wo-orbit wo-orbit-band wo-orbit-node" : "wo-orbit wo-orbit-node";
156
+ const dataAttributes = `data-render-id="${escapeXml(visual.renderId)}" data-orbit-object-id="${escapeAttribute(visual.objectId)}" data-parent-id="${escapeAttribute(visual.parentId)}" data-group-id="${escapeAttribute(visual.groupId ?? "")}"`;
157
+ if (visual.backArcPath || visual.frontArcPath) {
158
+ if (visual.backArcPath) {
159
+ backParts.push(`<path class="${orbitClass} wo-orbit-back" d="${visual.backArcPath}" stroke-width="${strokeWidth}" ${dataAttributes} />`);
160
+ }
161
+ if (visual.frontArcPath) {
162
+ frontParts.push(`<path class="${orbitClass} wo-orbit-front" d="${visual.frontArcPath}" stroke-width="${strokeWidth}" ${dataAttributes} />`);
163
+ }
164
+ continue;
165
+ }
166
+ if (visual.kind === "ellipse") {
167
+ const rx = visual.rx ?? visual.radius ?? 0;
168
+ const ry = visual.ry ?? visual.radius ?? 0;
169
+ frontParts.push(`<ellipse class="${orbitClass} wo-orbit-front" cx="${visual.cx}" cy="${visual.cy}" rx="${rx}" ry="${ry}" transform="rotate(${visual.rotationDeg} ${visual.cx} ${visual.cy})" stroke-width="${strokeWidth}" ${dataAttributes} />`);
170
+ continue;
171
+ }
172
+ frontParts.push(`<circle class="${orbitClass} wo-orbit-front" cx="${visual.cx}" cy="${visual.cy}" r="${visual.radius ?? 0}" stroke-width="${strokeWidth}" ${dataAttributes} />`);
173
+ }
174
+ return {
175
+ back: backParts.join(""),
176
+ front: frontParts.join(""),
177
+ };
178
+ }
179
+ function buildImageDefinitions(objects) {
180
+ return objects
181
+ .filter((object) => Boolean(object.imageHref))
182
+ .map((object) => renderImageClipPath(object))
183
+ .join("");
184
+ }
185
+ function renderSceneObject(sceneObject, selectedObjectId, theme) {
186
+ const { object, x, y, radius, visualRadius } = sceneObject;
187
+ const selectionClass = selectedObjectId === sceneObject.objectId ? " wo-object-selected" : "";
188
+ const kindClass = object.properties.kind
189
+ ? ` wo-kind-${String(object.properties.kind).toLowerCase().replace(/[^a-z0-9-]/g, "-")}`
190
+ : "";
191
+ const palette = resolveObjectPalette(sceneObject, theme);
192
+ const imageMarkup = renderObjectImage(sceneObject);
193
+ const outlineMarkup = imageMarkup
194
+ ? renderObjectBody(object, x, y, radius, palette, { outlineOnly: true })
195
+ : "";
196
+ return `<g class="wo-object wo-object-${object.type}${kindClass}${selectionClass}" data-object-id="${escapeXml(sceneObject.objectId)}" data-parent-id="${escapeAttribute(sceneObject.parentId ?? "")}" data-group-id="${escapeAttribute(sceneObject.groupId ?? "")}" data-render-id="${escapeXml(sceneObject.renderId)}" tabindex="0" role="button" aria-label="${escapeXml(`${object.type} ${sceneObject.objectId}`)}">
197
+ <circle class="wo-selection-ring" cx="${x}" cy="${y}" r="${visualRadius + 8}" />
198
+ ${renderAtmosphere(sceneObject, palette)}
199
+ ${renderObjectBody(object, x, y, radius, palette)}
200
+ ${imageMarkup}
201
+ ${outlineMarkup}
202
+ </g>`;
203
+ }
204
+ function renderSceneLabel(scene, label, selectedObjectId) {
205
+ const selectionClass = selectedObjectId === label.objectId ? " wo-object-label-selected" : "";
206
+ const labelScale = scene.scaleModel.labelMultiplier;
207
+ return `<g class="wo-object-label${selectionClass}" data-object-id="${escapeXml(label.objectId)}" data-group-id="${escapeAttribute(label.groupId ?? "")}" data-render-id="${escapeXml(label.renderId)}">
208
+ <text class="wo-label" x="${label.x}" y="${label.y}" text-anchor="${label.textAnchor}" font-size="${14 * labelScale}">${escapeXml(label.label)}</text>
209
+ <text class="wo-label-secondary" x="${label.x}" y="${label.secondaryY}" text-anchor="${label.textAnchor}" font-size="${11 * labelScale}">${escapeXml(label.secondaryLabel)}</text>
210
+ </g>`;
211
+ }
212
+ function renderObjectBody(object, x, y, radius, palette, options = {}) {
213
+ const fill = options.outlineOnly ? "transparent" : palette.fill;
214
+ const tail = palette.tail ?? palette.fill;
215
+ switch (object.type) {
216
+ case "star":
217
+ return options.outlineOnly
218
+ ? `<circle cx="${x}" cy="${y}" r="${radius}" fill="transparent" stroke="${palette.stroke}" stroke-width="2" />`
219
+ : `<circle cx="${x}" cy="${y}" r="${radius * 2.4}" fill="${palette.glow ?? "url(#wo-star-glow)"}" opacity="0.84" />
220
+ <circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" stroke="${palette.stroke}" stroke-width="2" />`;
221
+ case "planet":
222
+ case "moon":
223
+ case "belt":
224
+ case "asteroid":
225
+ case "ring":
226
+ return `<circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" stroke="${palette.stroke}" stroke-width="${options.outlineOnly ? 1.5 : 1.4}" />`;
227
+ case "comet":
228
+ return options.outlineOnly
229
+ ? `<circle cx="${x}" cy="${y}" r="${radius}" fill="transparent" stroke="${palette.stroke}" stroke-width="1.4" />`
230
+ : `<path d="M ${x - radius * 2} ${y + radius * 1.3} Q ${x - radius * 0.7} ${y + radius * 0.3} ${x - radius * 0.45} ${y}" fill="none" stroke="${tail}" stroke-width="${Math.max(2, radius * 0.8)}" stroke-linecap="round" opacity="0.85" />
231
+ <circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
232
+ case "structure":
233
+ return `<polygon points="${diamondPoints(x, y, radius)}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
234
+ case "phenomenon": {
235
+ const kind = String(object.properties.kind ?? "").toLowerCase().replace(/_/g, "-");
236
+ if (options.outlineOnly) {
237
+ if (kind === "black-hole" || kind === "nebula" || kind === "galaxy" || kind === "dwarf-galaxy") {
238
+ return `<circle cx="${x}" cy="${y}" r="${radius}" fill="transparent" stroke="${palette.stroke}" stroke-width="1.4" />`;
239
+ }
240
+ return `<polygon points="${phenomenonPoints(x, y, radius)}" fill="transparent" stroke="${palette.stroke}" stroke-width="1.4" />`;
241
+ }
242
+ if (kind === "black-hole") {
243
+ return `<ellipse cx="${x}" cy="${y}" rx="${radius * 2.4}" ry="${radius * 0.55}" fill="none" stroke="${palette.accentRing ?? palette.stroke}" stroke-width="3.5" />
244
+ <circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" stroke="${palette.stroke}" stroke-width="2" />`;
245
+ }
246
+ if (kind === "galaxy") {
247
+ return `<ellipse cx="${x}" cy="${y}" rx="${radius * 2.6}" ry="${radius}" fill="${palette.halo ?? "none"}" stroke="none" />
248
+ <ellipse cx="${x}" cy="${y}" rx="${radius * 1.5}" ry="${radius * 0.42}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.2" />
249
+ <circle cx="${x}" cy="${y}" r="${radius * 0.28}" fill="${palette.core ?? "#fff"}" stroke="none" />`;
250
+ }
251
+ if (kind === "dwarf-galaxy") {
252
+ return `<ellipse cx="${x}" cy="${y}" rx="${radius * 1.6}" ry="${radius * 0.55}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1" />
253
+ <circle cx="${x}" cy="${y}" r="${radius * 0.25}" fill="${palette.core ?? "#fff"}" stroke="none" />`;
254
+ }
255
+ if (kind === "nebula") {
256
+ return `<circle cx="${x}" cy="${y}" r="${radius * 2.2}" fill="${palette.halo ?? "none"}" stroke="none" />
257
+ <circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1" />`;
258
+ }
259
+ return `<polygon points="${phenomenonPoints(x, y, radius)}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
260
+ }
261
+ }
262
+ }
263
+ function renderAtmosphere(sceneObject, palette) {
264
+ if (!palette.atmosphere) {
265
+ return "";
266
+ }
267
+ const { object, x, y, radius } = sceneObject;
268
+ if (object.type !== "planet" && object.type !== "moon" && object.type !== "asteroid") {
269
+ return "";
270
+ }
271
+ return `<circle cx="${x}" cy="${y}" r="${radius + 4}" fill="none" stroke="${palette.atmosphere}" stroke-width="4" opacity="0.45" />`;
272
+ }
273
+ function renderObjectImage(sceneObject) {
274
+ if (!sceneObject.imageHref) {
275
+ return "";
276
+ }
277
+ const bounds = imageBoundsForObject(sceneObject);
278
+ return `<image class="wo-object-image" x="${bounds.x}" y="${bounds.y}" width="${bounds.width}" height="${bounds.height}" href="${escapeAttribute(sceneObject.imageHref)}" preserveAspectRatio="xMidYMid slice" clip-path="url(#${escapeAttribute(getObjectClipPathId(sceneObject))})" />`;
279
+ }
280
+ function renderImageClipPath(sceneObject) {
281
+ const { x, y, radius } = sceneObject;
282
+ let content = "";
283
+ switch (sceneObject.object.type) {
284
+ case "star":
285
+ case "planet":
286
+ case "moon":
287
+ case "asteroid":
288
+ case "comet":
289
+ content = `<circle cx="${x}" cy="${y}" r="${radius}" />`;
290
+ break;
291
+ case "structure":
292
+ content = `<polygon points="${diamondPoints(x, y, radius)}" />`;
293
+ break;
294
+ case "phenomenon":
295
+ content = `<polygon points="${phenomenonPoints(x, y, radius)}" />`;
296
+ break;
297
+ default:
298
+ return "";
299
+ }
300
+ return `<clipPath id="${escapeAttribute(getObjectClipPathId(sceneObject))}" clipPathUnits="userSpaceOnUse">${content}</clipPath>`;
301
+ }
302
+ function imageBoundsForObject(sceneObject) {
303
+ const { object, x, y, radius } = sceneObject;
304
+ switch (object.type) {
305
+ case "structure":
306
+ return {
307
+ x: x - radius,
308
+ y: y - radius,
309
+ width: radius * 2,
310
+ height: radius * 2,
311
+ };
312
+ case "phenomenon":
313
+ return {
314
+ x: x - radius * 1.2,
315
+ y: y - radius * 1.2,
316
+ width: radius * 2.4,
317
+ height: radius * 2.4,
318
+ };
319
+ default:
320
+ return {
321
+ x: x - radius,
322
+ y: y - radius,
323
+ width: radius * 2,
324
+ height: radius * 2,
325
+ };
326
+ }
327
+ }
328
+ function computeLabelPlacements(scene, objects) {
329
+ const placements = new Map();
330
+ const occupied = [];
331
+ const labelScale = scene.scaleModel.labelMultiplier;
332
+ for (const object of objects) {
333
+ const direction = object.y > scene.height * 0.62 ? -1 : 1;
334
+ const labelHalfWidth = estimateLabelHalfWidth(object, labelScale);
335
+ let labelY = object.y + direction * (object.radius + 18 * labelScale);
336
+ let secondaryY = labelY + direction * (16 * labelScale);
337
+ let rect = labelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
338
+ let attempts = 0;
339
+ while (occupied.some((entry) => rectsOverlap(entry, rect)) && attempts < 10) {
340
+ labelY += direction * 14 * labelScale;
341
+ secondaryY += direction * 14 * labelScale;
342
+ rect = labelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
343
+ attempts += 1;
344
+ }
345
+ occupied.push(rect);
346
+ placements.set(object.objectId, { labelY, secondaryY });
347
+ }
348
+ return placements;
349
+ }
350
+ function defaultLabelPlacement(scene, object) {
351
+ const direction = object.y > scene.height * 0.62 ? -1 : 1;
352
+ const labelScale = scene.scaleModel.labelMultiplier;
353
+ const labelY = object.y + direction * (object.radius + 18 * labelScale);
354
+ return {
355
+ labelY,
356
+ secondaryY: labelY + direction * (16 * labelScale),
357
+ };
358
+ }
359
+ function labelRect(x, labelY, secondaryY, labelHalfWidth, direction) {
360
+ return {
361
+ left: x - labelHalfWidth,
362
+ right: x + labelHalfWidth,
363
+ top: Math.min(labelY, secondaryY) - (direction < 0 ? 18 : 12),
364
+ bottom: Math.max(labelY, secondaryY) + (direction < 0 ? 8 : 12),
365
+ };
366
+ }
367
+ function rectsOverlap(left, right) {
368
+ return !(left.right < right.left ||
369
+ right.right < left.left ||
370
+ left.bottom < right.top ||
371
+ right.bottom < left.top);
372
+ }
373
+ function resolveObjectPalette(sceneObject, theme) {
374
+ const kind = String(sceneObject.object.properties.kind ?? "").toLowerCase().replace(/_/g, "-");
375
+ const base = basePaletteForType(sceneObject.object.type, kind, theme);
376
+ const customFill = sceneObject.fillColor && isColorLike(sceneObject.fillColor)
377
+ ? sceneObject.fillColor
378
+ : base.fill;
379
+ const albedo = numericValue(sceneObject.object.properties.albedo);
380
+ const temperature = numericValue(sceneObject.object.properties.temperature);
381
+ const fill = applyTemperatureAndAlbedo(customFill, temperature, albedo, sceneObject.object.type);
382
+ const stroke = mixColors(fill, "#ffffff", 0.4) ?? base.stroke;
383
+ const atmosphere = atmosphereColor(sceneObject.object.properties.atmosphere);
384
+ const glow = sceneObject.object.type === "star"
385
+ ? rgbaString(mixColors(fill, "#fff2cb", 0.4) ?? fill, 0.82)
386
+ : undefined;
387
+ return {
388
+ fill,
389
+ stroke,
390
+ glow,
391
+ atmosphere,
392
+ tail: sceneObject.object.type === "comet"
393
+ ? rgbaString(mixColors(fill, "#ffffff", 0.5) ?? fill, 0.72)
394
+ : undefined,
395
+ };
396
+ }
397
+ function basePaletteForType(type, kind, theme) {
398
+ switch (type) {
399
+ case "star":
400
+ return {
401
+ fill: theme.starCore,
402
+ stroke: theme.starStroke,
403
+ };
404
+ case "planet":
405
+ return { fill: "#72b7ff", stroke: "#d8efff" };
406
+ case "moon":
407
+ return { fill: "#c7d7ea", stroke: "#f2f8ff" };
408
+ case "belt":
409
+ return { fill: "#d9aa74", stroke: "#f7d5aa" };
410
+ case "asteroid":
411
+ return { fill: "#a7a5b8", stroke: "#ebe5ff" };
412
+ case "comet":
413
+ return { fill: "#9ce7ff", stroke: "#e7fbff" };
414
+ case "ring":
415
+ return { fill: "#e59f7d", stroke: "#fff0d3" };
416
+ case "structure":
417
+ return { fill: theme.accentStrong, stroke: "#fff2ea" };
418
+ case "phenomenon":
419
+ return kindPhenomenonPalette(kind);
420
+ }
421
+ }
422
+ function kindPhenomenonPalette(kind) {
423
+ if (kind === "galaxy") {
424
+ return { fill: "rgba(165,125,255,0.55)", stroke: "rgba(210,185,255,0.75)", halo: "rgba(160,120,255,0.10)", core: "#ede0ff" };
425
+ }
426
+ if (kind === "dwarf-galaxy") {
427
+ return { fill: "rgba(190,165,255,0.45)", stroke: "rgba(220,205,255,0.75)", core: "#ddd0ff" };
428
+ }
429
+ if (kind === "black-hole") {
430
+ return { fill: "#040408", stroke: "#ff6a00", accentRing: "rgba(255,140,20,0.72)" };
431
+ }
432
+ if (kind === "nebula") {
433
+ return { fill: "rgba(105,205,255,0.45)", stroke: "rgba(180,235,255,0.72)", halo: "rgba(100,200,255,0.08)" };
434
+ }
435
+ if (kind === "void") {
436
+ return { fill: "#05080f", stroke: "rgba(130,160,255,0.4)" };
437
+ }
438
+ return { fill: "#78ffd7", stroke: "#e9fff7" };
439
+ }
440
+ function applyTemperatureAndAlbedo(baseColor, temperature, albedo, type) {
441
+ let nextColor = baseColor;
442
+ if (temperature !== null) {
443
+ const tint = temperatureTint(temperature, type);
444
+ nextColor = mixColors(nextColor, tint, type === "star" ? 0.42 : 0.2) ?? nextColor;
445
+ }
446
+ if (albedo !== null) {
447
+ const clamped = Math.min(Math.max(albedo, 0), 1);
448
+ nextColor =
449
+ mixColors(nextColor, clamped >= 0.5 ? "#ffffff" : "#0f1520", Math.abs(clamped - 0.5) * 0.7) ??
450
+ nextColor;
451
+ }
452
+ return nextColor;
453
+ }
454
+ function temperatureTint(value, type) {
455
+ if (type === "star") {
456
+ if (value >= 8_000)
457
+ return "#d7ebff";
458
+ if (value >= 6_000)
459
+ return "#fff3d8";
460
+ if (value >= 4_000)
461
+ return "#ffd39a";
462
+ return "#ff9d76";
463
+ }
464
+ if (value <= 180)
465
+ return "#8fd8ff";
466
+ if (value >= 600)
467
+ return "#ffb56e";
468
+ return "#fff1c4";
469
+ }
470
+ function atmosphereColor(value) {
471
+ if (typeof value !== "string" || !value.trim()) {
472
+ return undefined;
473
+ }
474
+ const normalized = value.trim().toLowerCase();
475
+ if (normalized.includes("methane"))
476
+ return "rgba(134, 236, 255, 0.75)";
477
+ if (normalized.includes("nitrogen") || normalized.includes("oxygen") || normalized.includes("air")) {
478
+ return "rgba(122, 194, 255, 0.75)";
479
+ }
480
+ if (normalized.includes("carbon") || normalized.includes("co2")) {
481
+ return "rgba(255, 198, 138, 0.75)";
482
+ }
483
+ if (normalized.includes("sulfur")) {
484
+ return "rgba(255, 233, 138, 0.75)";
485
+ }
486
+ return "rgba(180, 222, 255, 0.68)";
487
+ }
488
+ function resolveRenderPreset(preset) {
489
+ switch (preset) {
490
+ case "presentation":
491
+ return {
492
+ layers: {
493
+ background: true,
494
+ guides: true,
495
+ orbits: true,
496
+ objects: true,
497
+ labels: true,
498
+ structures: true,
499
+ metadata: true,
500
+ },
501
+ };
502
+ case "atlas-card":
503
+ return {
504
+ layers: {
505
+ background: true,
506
+ guides: false,
507
+ orbits: true,
508
+ objects: true,
509
+ labels: true,
510
+ structures: true,
511
+ metadata: true,
512
+ },
513
+ };
514
+ case "markdown":
515
+ return {
516
+ layers: {
517
+ background: true,
518
+ guides: false,
519
+ orbits: true,
520
+ objects: true,
521
+ labels: true,
522
+ structures: true,
523
+ metadata: false,
524
+ },
525
+ };
526
+ case "diagram":
527
+ default:
528
+ return {
529
+ layers: {
530
+ background: true,
531
+ guides: true,
532
+ orbits: true,
533
+ objects: true,
534
+ labels: true,
535
+ structures: true,
536
+ metadata: true,
537
+ },
538
+ };
539
+ }
540
+ }
541
+ function numericValue(value) {
542
+ if (typeof value === "number") {
543
+ return value;
544
+ }
545
+ if (value && typeof value === "object" && "value" in value) {
546
+ return value.value;
547
+ }
548
+ return null;
549
+ }
550
+ function estimateLabelHalfWidth(object, labelScale) {
551
+ const primaryWidth = object.label.length * 4.8 * labelScale + 18;
552
+ const secondaryWidth = object.secondaryLabel.length * 4.1 * labelScale + 18;
553
+ return Math.max(primaryWidth, secondaryWidth, object.visualRadius + 18);
554
+ }
555
+ function getObjectClipPathId(sceneObject) {
556
+ return `${sceneObject.renderId}-clip`;
557
+ }
558
+ function diamondPoints(x, y, radius) {
559
+ return `${x},${y - radius} ${x + radius},${y} ${x},${y + radius} ${x - radius},${y}`;
560
+ }
561
+ function phenomenonPoints(x, y, radius) {
562
+ return `${x},${y - radius * 1.2} ${x + radius * 0.9},${y - radius * 0.3} ${x + radius * 1.2},${y + radius * 0.8} ${x},${y + radius * 1.2} ${x - radius * 1.1},${y + radius * 0.3} ${x - radius * 0.8},${y - radius * 0.8}`;
563
+ }
564
+ function renderBackdrop(width, height) {
565
+ const verticalLines = Array.from({ length: 10 }, (_, index) => {
566
+ const x = 90 + index * ((width - 180) / 9);
567
+ return `<line class="wo-grid" x1="${x}" y1="120" x2="${x}" y2="${height - 70}" />`;
568
+ }).join("");
569
+ const horizontalLines = Array.from({ length: 6 }, (_, index) => {
570
+ const y = 150 + index * ((height - 240) / 5);
571
+ return `<line class="wo-grid" x1="56" y1="${y}" x2="${width - 56}" y2="${y}" />`;
572
+ }).join("");
573
+ return `${verticalLines}${horizontalLines}`;
574
+ }
575
+ function renderMetadata(scene) {
576
+ return [
577
+ `${scene.layoutPreset} layout`,
578
+ `${scene.viewMode} view`,
579
+ `${scene.renderPreset ?? "custom"} preset`,
580
+ `schema ${scene.metadata.version ?? "1.0"}`,
581
+ ].join(" - ");
582
+ }
583
+ function isStructureLike(object) {
584
+ return object.type === "structure" || object.type === "phenomenon";
585
+ }
586
+ function mixColors(left, right, amount) {
587
+ const leftRgb = parseColor(left);
588
+ const rightRgb = parseColor(right);
589
+ if (!leftRgb || !rightRgb) {
590
+ return undefined;
591
+ }
592
+ const clamped = Math.min(Math.max(amount, 0), 1);
593
+ return rgbToHex({
594
+ r: Math.round(leftRgb.r + (rightRgb.r - leftRgb.r) * clamped),
595
+ g: Math.round(leftRgb.g + (rightRgb.g - leftRgb.g) * clamped),
596
+ b: Math.round(leftRgb.b + (rightRgb.b - leftRgb.b) * clamped),
597
+ });
598
+ }
599
+ function rgbaString(color, alpha) {
600
+ const rgb = parseColor(color);
601
+ if (!rgb) {
602
+ return color;
603
+ }
604
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`;
605
+ }
606
+ function parseColor(value) {
607
+ const hex = value.trim();
608
+ if (/^#[0-9a-f]{6}$/i.test(hex)) {
609
+ return {
610
+ r: Number.parseInt(hex.slice(1, 3), 16),
611
+ g: Number.parseInt(hex.slice(3, 5), 16),
612
+ b: Number.parseInt(hex.slice(5, 7), 16),
613
+ };
614
+ }
615
+ if (/^#[0-9a-f]{3}$/i.test(hex)) {
616
+ return {
617
+ r: Number.parseInt(hex[1] + hex[1], 16),
618
+ g: Number.parseInt(hex[2] + hex[2], 16),
619
+ b: Number.parseInt(hex[3] + hex[3], 16),
620
+ };
621
+ }
622
+ return null;
623
+ }
624
+ function rgbToHex(color) {
625
+ const part = (value) => value.toString(16).padStart(2, "0");
626
+ return `#${part(color.r)}${part(color.g)}${part(color.b)}`;
627
+ }
628
+ function isColorLike(value) {
629
+ return Boolean(value.trim());
630
+ }
631
+ function escapeXml(value) {
632
+ return value
633
+ .replaceAll("&", "&amp;")
634
+ .replaceAll("<", "&lt;")
635
+ .replaceAll(">", "&gt;")
636
+ .replaceAll("\"", "&quot;")
637
+ .replaceAll("'", "&apos;");
638
+ }
639
+ function escapeAttribute(value) {
640
+ return escapeXml(value);
641
+ }
@@ -0,0 +1,4 @@
1
+ import type { ViewerLayerOptions, WorldOrbitTheme, WorldOrbitThemeName } from "./types.js";
2
+ export declare function resolveTheme(theme?: WorldOrbitTheme | WorldOrbitThemeName): WorldOrbitTheme;
3
+ export declare function resolveLayers(layers?: ViewerLayerOptions): Required<ViewerLayerOptions>;
4
+ export declare function getThemePreset(name: WorldOrbitThemeName): WorldOrbitTheme;