@johnmorrisdotca/wakusei-planets 0.3.0 → 0.4.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ "Put it in your page" is a configurator.
6
+
7
+ - **Set up a body, copy the code.** The demo's last section follows what is set up above it (body, look, sky and date, moons, turning) and a size (160, 240, 320 or 480), and writes working code for plain HTML from jsDelivr, an ES module, React, Vue, Angular, Svelte and an iframe of the embed page, on tabs that answer the arrow keys, beside a picture made from the same choices, with a Copy button and the credits that go with the picture. The choices are kept in the address (`?embed=react&size=240`).
8
+ - **`embedCode(options, kind, settings)` and `embedCredit(options)`** make the same text for a page of your own (`EMBED_KINDS`: `html`, `module`, `react`, `vue`, `angular`, `svelte`, `iframe`). Pattern: senshu-jerseys' `embedCode` and its tabbed "Put it in your page". Every snippet is tested (`test/embed.test.ts`): the React component is built and rendered, the Vue and Svelte components compiled, the Angular template parsed, the module checked as TypeScript, the iframe's address read the way the embed page reads it, and every body's entry point exists in the package.
9
+ - A guide, "Put it in your page", in English and Japanese.
10
+ - **kazoku-ui 0.11.0**: the view controls' Centre button (it appears once the body is dragged away), a pan that keeps a quarter of the body in sight (told the body's size with `object-size`), and a drag that keeps the touched point under the pointer (`grab-radius`). The 0.3.1 stopgap that made Fit re-centre is gone.
11
+
12
+ ## 0.3.1
13
+
14
+ The Earth is a body like the others, and "To scale" is a comparison.
15
+
16
+ - **The Earth draws in every look.** In the realistic and painted looks the big view was empty and the row showed chikyu-earth's line globe, because the Earth was handed to chikyu-earth's own live element (0.7.0 here, a second WebGL context, no still). It is now drawn by this package like every other body, from NASA's Blue Marble picture (`earth-512`, `-1024` and `-2048`; the package grows by 0.08 MB), through the page's one WebGL context, with a still in the row. Both looks use the picture (an honest shaded Earth cannot be worked out from numbers), with a rim of air. Only the line look is chikyu-earth's globe. The Earth is in `/all`. chikyu-earth is now `^0.10.0` (it was `^0.7.0`).
17
+ - **"To scale" is a lineup** (section "To scale" in the demo): every body at the same scale in the same size of picture, a magnified picture joined by a line for each body too small to see, Saturn's rings whole, the Sun as the curved edge it is, the Earth at its true size. The tiles in the row are never scaled any more (the Sun was a flat square, Mercury a dot). New attribute `span-km`: the half-width of a picture in kilometres, the same in every picture of a lineup.
18
+ - **Every moon has a tile and a name** for `body` (`?body=io`), in the demo under its planet, with chips on each planet's and moon's view for its moons, its planet and its sister moons.
19
+ - **The body cannot be dragged out of the view** (a quarter of it stays in), and Fit centres it and returns it to its first angle.
20
+ - The README's pictures are absolute addresses pinned to the release's tag (npm's page showed a different picture from GitHub's), and a test holds it (`pnpm readme:images` pins them after a version bump).
21
+
3
22
  ## 0.3.0
4
23
 
5
24
  Painted moons, Saturn's rings from Cassini, and a drag that goes the right way up.
package/NOTICE.md CHANGED
@@ -15,7 +15,7 @@ The tests compare the Galilean moons and four planets with astronomy-engine (MIT
15
15
 
16
16
  ## The Earth
17
17
 
18
- The Earth is drawn by chikyu-earth (MIT, by John Morris; https://github.com/johnmorrisdotca/chikyu-earth), an optional peer dependency: its own NOTICE.md has the terms of its outlines and its NASA pictures.
18
+ The Earth's line drawing is chikyu-earth's own globe (MIT, by John Morris; https://github.com/johnmorrisdotca/chikyu-earth), an optional peer dependency: its own NOTICE.md has the terms of its outlines. The Earth's realistic and painted looks are drawn here, like every other body, from NASA's Blue Marble picture (below).
19
19
 
20
20
  ## Surface pictures
21
21
 
@@ -40,6 +40,7 @@ NASA says its content "generally are not subject to copyright in the United Stat
40
40
  | `ganymede-*.webp` | Ganymede | [Ganymede equatorial.jpg](https://commons.wikimedia.org/wiki/File:Ganymede_equatorial.jpg) | Public domain | USGS | USGS Voyager and Galileo colour mosaic (I-2762), the Mercator sheet for 57 degrees north and south, redrawn as an equirectangular map with longitude 0 in the middle (the source does not say; Galileo Regio falls at 140 degrees west); the poles are not on that sheet and fade from its edge into its mean colour |
41
41
  | `callisto-*.webp` | Callisto | [Callisto equatorial.jpg](https://commons.wikimedia.org/wiki/File:Callisto_equatorial.jpg) | Public domain | USGS | USGS Voyager and Galileo mosaic (I-2770), the Mercator sheet for 57 degrees north and south, redrawn as an equirectangular map with longitude 0 in the middle (the source does not say; Valhalla falls at 56 degrees west); the poles fade from its edge into its mean grey |
42
42
  | `titan-*.webp` | Titan | [Titan map April 2011 full.png](https://commons.wikimedia.org/wiki/File:Titan_map_April_2011_full.png) | Public domain | NASA / JPL / Space Science Institute | Cassini ISS 938 nm map (PIA14908), turned so longitude 0 is in the middle (the original is centred on 180). Titan's surface cannot be seen in visible light under its haze, so the picture is a colour for the infrared brightness, with its contrast turned down: it is not a photograph, as with Venus |
43
+ | `earth-*.webp` | Earth | [Blue Marble: Next Generation with topography and bathymetry, September 2004](https://visibleearth.nasa.gov/images/73801) (Visible Earth 73801) | Public domain (NASA) | NASA Earth Observatory (Reto Stockli) | Resized from the 2048 pixel picture chikyu-earth already makes from the 5400 x 2700 original; the realistic and the painted looks both draw it, because an honest shaded Earth cannot be worked out from numbers |
43
44
  | `rhea-*.webp` | Rhea | [Rhea Color Map.jpg](https://commons.wikimedia.org/wiki/File:Rhea_Color_Map.jpg) | Public domain | NASA/JPL-Caltech/Space Science Institute/Lunar and Planetary Institute | Cassini global colour map (PIA18438), turned half way round so longitude 0 is in the middle (the source does not say where it is centred; the dark trailing side is put at 90 degrees east); the colours are false-colour (infrared, green, ultraviolet) and are turned about half way to grey |
44
45
  | `enceladus-*.webp` | Enceladus | [Enceladus Color Map.jpg](https://commons.wikimedia.org/wiki/File:Enceladus_Color_Map.jpg) | Public domain | NASA/JPL-Caltech/Space Science Institute/Lunar and Planetary Institute | Cassini global colour map (PIA18435), turned half way round the same way as Rhea's (the source does not say where it is centred; the fractured trailing terrain is put at 90 degrees east); the colours are false-colour (the blue of the tiger stripes is ultraviolet) and are turned most of the way to grey |
45
46
  | `mimas-*.webp` | Mimas | [Map of Mimas 2010-02 PIA12780.jpg](https://commons.wikimedia.org/wiki/File:Map_of_Mimas_2010-02_PIA12780.jpg) | Public domain | NASA/JPL/Space Science Institute | Cassini ISS map of February 2010 (PIA12780), cropped inside its frame and labels; Herschel is at 110 degrees west as its own scale says |
package/README.md CHANGED
@@ -11,7 +11,9 @@ A clean line drawing, a shaded realistic drawing, or a painted surface map, with
11
11
 
12
12
  <p align="center"><a href="https://johnmorrisdotca.github.io/wakusei-planets/"><strong>Open the demo →</strong></a> · <a href="https://johnmorrisdotca.github.io/wakusei-planets/embed.html">Embed page</a> · <a href="https://johnmorrisdotca.github.io/wakusei-planets/docs/">Docs</a></p>
13
13
 
14
- <p align="center"><img alt="Jupiter and Saturn drawn in the realistic look, and Saturn as a line drawing" src="docs/images/hero-desk.png" width="640"></p>
14
+ <p align="center"><img alt="Jupiter and Saturn drawn in the realistic look, and Saturn as a line drawing" src="https://raw.githubusercontent.com/johnmorrisdotca/wakusei-planets/v0.4.0/docs/images/hero-desk.png" width="640"></p>
15
+
16
+ <p align="center"><img alt="Every body at one scale, the Earth among them: each small body has a magnified picture joined by a line, and the Sun shows only its edge" src="https://raw.githubusercontent.com/johnmorrisdotca/wakusei-planets/v0.4.0/docs/images/lineup-desk.png" width="640"></p>
15
17
 
16
18
  ## One minute
17
19
 
@@ -74,7 +76,7 @@ A bundled app imports it: `import "@johnmorrisdotca/wakusei-planets/element"` (a
74
76
  | `realistic` | A shaded sphere drawn from the body's real colours and features, lit from the Sun's side, with the rings casting shadows. No pictures: the surface is worked out in a shader. | `three`, the body's entry point |
75
77
  | `painted` | A real surface map from public-domain NASA, USGS and JPL imagery. Small pictures first (1.31 MB for all of them in the package), a sharper one fetched when you zoom in. | `three`, the body's entry point |
76
78
 
77
- Every body has its real size relative to the others (`scale-to="jupiter"`), its axial tilt and its rotation. The Sun, Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune and Pluto are drawn, and so are 25 moons (the Moon, Phobos and Deimos, the four Galilean moons, Saturn's seven larger moons, Uranus's five, Proteus, Triton and Nereid, Charon, Nix and Hydra). The Earth is [chikyu-earth](https://github.com/johnmorrisdotca/chikyu-earth)'s own globe, not a second copy: `import "@johnmorrisdotca/wakusei-planets/earth"` (it needs `@johnmorrisdotca/chikyu-earth` installed). The painted looks cover the Sun, the planets from Mercury to Pluto, the Moon, the four Galilean moons, Titan, Rhea, Enceladus and Mimas, and Saturn's rings are Cassini's own picture of them.
79
+ Every body has its real size relative to the others (`scale-to="jupiter"`), its axial tilt and its rotation. The Sun, Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune and Pluto are drawn, and so are 25 moons (the Moon, Phobos and Deimos, the four Galilean moons, Saturn's seven larger moons, Uranus's five, Proteus, Triton and Nereid, Charon, Nix and Hydra). The Earth is drawn like the rest in the realistic and painted looks (NASA's Blue Marble), and its line look is [chikyu-earth](https://github.com/johnmorrisdotca/chikyu-earth)'s own globe: `import "@johnmorrisdotca/wakusei-planets/earth"` (it needs `@johnmorrisdotca/chikyu-earth` 0.10.0 or later installed). The painted looks cover the Sun, the planets from Mercury to Earth and Pluto, the Moon, the four Galilean moons, Titan, Rhea, Enceladus and Mimas, and Saturn's rings are Cassini's own picture of them.
78
80
 
79
81
  ## As the Earth sees it
80
82
 
@@ -92,6 +94,7 @@ Give a body a `time` and it is drawn as the sky shows it then: its pole and rota
92
94
  | An ES module in Vite or webpack, only the bodies you need | [bundlers guide](docs/guides/bundlers.md) |
93
95
  | React, Vue, Svelte, Angular; Next.js, Nuxt, SvelteKit | [frameworks guide](docs/guides/frameworks.md) |
94
96
  | An `<iframe>` of the embed page, no script of your own | [iframe guide](docs/guides/embed-in-an-iframe.md) |
97
+ | Set up a body in the demo and copy the code for HTML, React, Vue, Angular, Svelte or an iframe | [Put it in your page](docs/guides/put-it-in-your-page.md), or `embedCode()` |
95
98
  | TypeScript types, an SVG string on a server, Node | [TypeScript and Node guide](docs/guides/typescript-and-node.md) |
96
99
  | The looks, the entry points, one WebGL context | [looks and bodies](docs/guides/looks-and-bodies.md) |
97
100
  | Moons where they are on a date, time that runs fast | [moons and time](docs/guides/moons-and-time.md) |
@@ -7,6 +7,7 @@ import "./saturn.ts";
7
7
  import "./uranus.ts";
8
8
  import "./neptune.ts";
9
9
  import "./pluto.ts";
10
+ import "./earth-look.ts";
10
11
  import "./moons-earth.ts";
11
12
  import "./moons-mars.ts";
12
13
  import "./moons-jupiter.ts";
@@ -7,6 +7,7 @@ import "./saturn.js";
7
7
  import "./uranus.js";
8
8
  import "./neptune.js";
9
9
  import "./pluto.js";
10
+ import "./earth-look.js";
10
11
  import "./moons-earth.js";
11
12
  import "./moons-mars.js";
12
13
  import "./moons-jupiter.js";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ import { registerLook } from "../scene/look.js";
2
+ // The Earth's realistic and painted looks, drawn like every other body's from the Blue Marble picture, so they share the page's one WebGL context and have stills like the rest.
3
+ registerLook({
4
+ id: "earth",
5
+ surface: `
6
+ vec4 surface(vec3 p) {
7
+ float land = smoothstep(0.52, 0.58, fbm(p * 2.6 + 3.0));
8
+ vec3 sea = vec3(0.05, 0.17, 0.38);
9
+ vec3 ground = mix(vec3(0.22, 0.38, 0.16), vec3(0.62, 0.55, 0.40), smoothstep(0.55, 0.8, fbm(p * 5.0)));
10
+ float ice = smoothstep(radians(66.0), radians(76.0), abs(lat(p)));
11
+ return vec4(mix(mix(sea, ground, land), vec3(0.93, 0.96, 0.98), ice), 0.0);
12
+ }`,
13
+ atmosphere: { colour: [0.32, 0.52, 0.95], strength: 0.5, power: 3 },
14
+ wrap: 0.12,
15
+ limb: 0.12,
16
+ mapped: true,
17
+ painted: { map: "earth", widths: [512, 1024, 2048], far: 2048, relief: 0 },
18
+ });
@@ -1 +1 @@
1
- export {};
1
+ import "./earth-look.ts";
@@ -1,6 +1,7 @@
1
+ import "./earth-look.js";
1
2
  import { delegates } from "../scene/store.js";
2
- // The Earth is drawn by chikyu-earth's own elements, not a second copy of them: the line globe with the countries, and the Earth from space with the real Sun, clouds and night lights.
3
- // chikyu-earth is an optional peer, loaded only when an Earth is drawn.
3
+ // The Earth's line look is chikyu-earth's own globe with the countries, not a second copy (chikyu-earth is an optional peer, loaded only when that line globe is drawn). The realistic and
4
+ // painted looks are drawn here like every other body's (earth-look.ts, which `/all` also loads).
4
5
  const CENTRE = "10,20";
5
6
  delegates.set("earth", {
6
7
  async line({ center, size }) {
@@ -11,13 +12,4 @@ delegates.set("earth", {
11
12
  globe.setAttribute("size", String(size));
12
13
  return globe;
13
14
  },
14
- async live({ center, time, textureBase }) {
15
- await import("@johnmorrisdotca/chikyu-earth/realistic/element");
16
- const earth = document.createElement("chikyu-earth-realistic");
17
- earth.setAttribute("center", center || CENTRE);
18
- earth.setAttribute("time", time);
19
- if (textureBase !== null)
20
- earth.setAttribute("texture-base", textureBase);
21
- return earth;
22
- },
23
15
  });
package/dist/element.d.ts CHANGED
@@ -25,7 +25,7 @@ export type WakuseiState = "line" | "loading" | "still" | "live" | "lost" | "una
25
25
  * console.log(TEXTURE_BASE);
26
26
  * ```
27
27
  */
28
- export declare const TEXTURE_BASE = "https://cdn.jsdelivr.net/npm/@johnmorrisdotca/wakusei-planets@0.3.0/dist/textures/";
28
+ export declare const TEXTURE_BASE = "https://cdn.jsdelivr.net/npm/@johnmorrisdotca/wakusei-planets@0.4.0/dist/textures/";
29
29
  /**
30
30
  * Where the sharpest surface pictures are: the files of this exact version in the repository, on jsDelivr's GitHub address. The npm package holds only the two smaller widths, so a page that
31
31
  * never zooms in never fetches them. Set `texture-far-base` on an element to serve them yourself.
@@ -37,13 +37,13 @@ export declare const TEXTURE_BASE = "https://cdn.jsdelivr.net/npm/@johnmorrisdot
37
37
  * console.log(TEXTURE_FAR_BASE);
38
38
  * ```
39
39
  */
40
- export declare const TEXTURE_FAR_BASE = "https://cdn.jsdelivr.net/gh/johnmorrisdotca/wakusei-planets@v0.3.0/textures/";
40
+ export declare const TEXTURE_FAR_BASE = "https://cdn.jsdelivr.net/gh/johnmorrisdotca/wakusei-planets@v0.4.0/textures/";
41
41
  /**
42
42
  * The `<wakusei-body>` custom element: a body of the solar system, drawn in a look.
43
43
  *
44
44
  * Attributes: `body` (a name, such as `jupiter`), `look` (`line`, `realistic` or `painted`), `size` (pixels across), `time` (`now` or an ISO date: the body as the Earth sees it then; leave it out for a portrait),
45
45
  * `tilt`, `pitch`, `phase` and `meridian` (a portrait's pose, degrees), `turn` (a portrait turns this many degrees a second), `rate` (simulated seconds for each real second), `moons` (draw the planet's moons on their orbits),
46
- * `range` (planet radii the moons picture reaches), `scale-to` (draw to scale with another body), `live` (`always`, `hover` or `off`: when it takes the page's one WebGL context), `texture-base` (where the surface pictures are) and `lang` (`en` or `ja`).
46
+ * `range` (planet radii the moons picture reaches), `scale-to` (draw to scale with another body), `span-km` (draw to scale: the half-width of the picture in kilometres, the same in every picture of a lineup), `live` (`always`, `hover` or `off`: when it takes the page's one WebGL context), `texture-base` (where the surface pictures are) and `lang` (`en` or `ja`).
47
47
  *
48
48
  * @example
49
49
  * ```ts no-run
package/dist/element.js CHANGED
@@ -55,10 +55,18 @@ function extentOf(id, options) {
55
55
  const far = Math.max(1.8, ringReach(f) * 1.15, ...near.map((m) => m.reach * 1.1));
56
56
  return options.range ?? far;
57
57
  }
58
+ if (options.spanKm !== undefined)
59
+ return options.spanKm / f.radiusKm;
58
60
  if (options.scaleTo !== undefined)
59
61
  return (BODIES[options.scaleTo].radiusKm * 1.06) / f.radiusKm;
60
62
  return Math.max(id === "sun" ? 1.55 : 1.06, ringReach(f) * 1.04);
61
63
  }
64
+ /** Keep the body in the picture: at least a quarter of its width stays inside the edge, however far the view is moved. `pan` is in half-widths of the picture. */
65
+ function clampPan(pan, extent, zoom) {
66
+ const half = extent / Math.max(0.05, zoom);
67
+ const limit = 1 + 0.5 / half;
68
+ return Math.max(-limit, Math.min(limit, pan));
69
+ }
62
70
  const TEMPLATE = `<style>
63
71
  :host { display: block; width: var(--wakusei-size, 320px); max-width: 100%; aspect-ratio: 1 / 1; position: relative; contain: layout style; }
64
72
  #stage { position: absolute; inset: 0; }
@@ -69,7 +77,7 @@ const TEMPLATE = `<style>
69
77
  *
70
78
  * Attributes: `body` (a name, such as `jupiter`), `look` (`line`, `realistic` or `painted`), `size` (pixels across), `time` (`now` or an ISO date: the body as the Earth sees it then; leave it out for a portrait),
71
79
  * `tilt`, `pitch`, `phase` and `meridian` (a portrait's pose, degrees), `turn` (a portrait turns this many degrees a second), `rate` (simulated seconds for each real second), `moons` (draw the planet's moons on their orbits),
72
- * `range` (planet radii the moons picture reaches), `scale-to` (draw to scale with another body), `live` (`always`, `hover` or `off`: when it takes the page's one WebGL context), `texture-base` (where the surface pictures are) and `lang` (`en` or `ja`).
80
+ * `range` (planet radii the moons picture reaches), `scale-to` (draw to scale with another body), `span-km` (draw to scale: the half-width of the picture in kilometres, the same in every picture of a lineup), `live` (`always`, `hover` or `off`: when it takes the page's one WebGL context), `texture-base` (where the surface pictures are) and `lang` (`en` or `ja`).
73
81
  *
74
82
  * @example
75
83
  * ```ts no-run
@@ -83,7 +91,7 @@ const TEMPLATE = `<style>
83
91
  * ```
84
92
  */
85
93
  export class WakuseiBody extends HTMLElement {
86
- static observedAttributes = ["body", "look", "size", "time", "tilt", "pitch", "phase", "meridian", "turn", "rate", "moons", "range", "scale-to", "live", "texture-base", "texture-far-base", "lang", "labels"];
94
+ static observedAttributes = ["body", "look", "size", "time", "tilt", "pitch", "phase", "meridian", "turn", "rate", "moons", "range", "scale-to", "span-km", "live", "texture-base", "texture-far-base", "lang", "labels"];
87
95
  #stage;
88
96
  #state = "line";
89
97
  #renderer;
@@ -209,10 +217,6 @@ export class WakuseiBody extends HTMLElement {
209
217
  if (this.look === "line")
210
218
  return false;
211
219
  this.#wantLive = true;
212
- if (delegates.has(this.body)) {
213
- await this.#delegated(delegates.get(this.body));
214
- return true;
215
- }
216
220
  await this.#ensureView();
217
221
  const ok = (await this.#view?.focus()) === true;
218
222
  if (ok) {
@@ -227,10 +231,6 @@ export class WakuseiBody extends HTMLElement {
227
231
  /** Give the WebGL context back: the picture stays as a still. */
228
232
  rest() {
229
233
  this.#wantLive = false;
230
- if (delegates.has(this.body)) {
231
- this.#queue(true);
232
- return;
233
- }
234
234
  this.#stopTick();
235
235
  if (this.#view !== undefined) {
236
236
  this.#view.blur();
@@ -292,7 +292,8 @@ export class WakuseiBody extends HTMLElement {
292
292
  const r = this.#request();
293
293
  const scaleTo = this.getAttribute("scale-to");
294
294
  const range = this.hasAttribute("range") ? num(this.getAttribute("range"), 0) : undefined;
295
- const extent = extentOf(this.body, { ...(scaleTo !== null && scaleTo in BODIES ? { scaleTo } : {}), moons: r.moons, ...(range !== undefined && range > 0 ? { range } : {}) });
295
+ const span = num(this.getAttribute("span-km"), 0);
296
+ const extent = extentOf(this.body, { ...(span > 0 ? { spanKm: span } : {}), ...(scaleTo !== null && scaleTo in BODIES ? { scaleTo } : {}), moons: r.moons, ...(range !== undefined && range > 0 ? { range } : {}) });
296
297
  const moons = r.moons
297
298
  ? frame.moons
298
299
  .filter((m) => m.distance <= extent * 1.05)
@@ -302,7 +303,7 @@ export class WakuseiBody extends HTMLElement {
302
303
  return { id: m.id, x: rotated[0], y: rotated[1], z: rotated[2], ...(orbit !== undefined ? { orbit: tiltBy === 0 ? orbit : this.#tiltedOrbit(m.id, orbit, tiltBy) } : {}) };
303
304
  })
304
305
  : undefined;
305
- return { pose: { pole, meridian, light }, extent, zoom: this.#zoom, panX: this.#panX, panY: this.#panY, ...(moons !== undefined ? { moons } : {}) };
306
+ return { pose: { pole, meridian, light }, extent, zoom: this.#zoom, panX: clampPan(this.#panX, extent, this.#zoom), panY: clampPan(this.#panY, extent, this.#zoom), ...(moons !== undefined ? { moons } : {}) };
306
307
  }
307
308
  #tilted = new Map();
308
309
  #tiltedOrbit(id, orbit, tilt) {
@@ -359,7 +360,7 @@ export class WakuseiBody extends HTMLElement {
359
360
  this.setAttribute("aria-label", this.#label());
360
361
  if (this.hasAttribute("size"))
361
362
  this.style.setProperty("--wakusei-size", `${num(this.getAttribute("size"), 320)}px`);
362
- const delegate = delegates.get(this.body);
363
+ const delegate = this.look === "line" ? delegates.get(this.body) : undefined;
363
364
  if (delegate !== undefined) {
364
365
  await this.#delegated(delegate);
365
366
  return;
@@ -441,12 +442,12 @@ export class WakuseiBody extends HTMLElement {
441
442
  size,
442
443
  pose: scene.pose,
443
444
  ...(r.moons ? { moons: true, ...(frame.time !== undefined ? { time: frame.time } : {}), range: scene.extent, moonsAt: (scene.moons ?? []).map((m) => ({ id: m.id, x: m.x, y: m.y, z: m.z, distance: Math.hypot(m.x, m.y, m.z) })), orbits: new Map((scene.moons ?? []).filter((m) => m.orbit !== undefined).map((m) => [m.id, m.orbit])) } : {}),
444
- ...(scaleTo !== null && scaleTo in BODIES ? { fill: BODIES[this.body].radiusKm / (BODIES[scaleTo].radiusKm * 1.06) } : {}),
445
+ ...(scaleTo !== null || this.hasAttribute("span-km") ? { fill: this.#fill() } : {}),
445
446
  ...(this.getAttribute("lang") === "ja" ? { lang: "ja" } : {}),
446
447
  ...(this.getAttribute("labels") === "off" ? { labels: false } : {}),
447
448
  ...(stroke !== "" ? { stroke } : {}),
448
449
  ...(paper !== "" ? { paper } : {}),
449
- ...(this.#zoom !== 1 ? { zoom: this.#zoom, panX: this.#panX, panY: this.#panY } : {}),
450
+ ...(this.#zoom !== 1 ? { zoom: this.#zoom, panX: scene.panX, panY: scene.panY } : {}),
450
451
  });
451
452
  this.#stage.innerHTML = html;
452
453
  }
@@ -504,30 +505,34 @@ export class WakuseiBody extends HTMLElement {
504
505
  if (this.#visible)
505
506
  this.#still();
506
507
  }
507
- /** A body another package draws (the Earth): its line globe, or its shaded Earth once it is asked to go live. */
508
+ /** A body whose line look another package draws (the Earth's globe with its countries). It is drawn at the body's size in a scale picture, never larger than the picture. */
508
509
  async #delegated(delegate) {
509
510
  if (this.#view !== undefined) {
510
511
  this.#view.dispose();
511
512
  this.#view = undefined;
512
513
  this.#viewKey = "";
513
514
  }
514
- const shaded = this.look !== "line" && (this.#wantLive || (this.#hasPriority() && (this.getAttribute("live") ?? "always") === "always"));
515
- const kind = shaded ? "live" : "line";
516
- const time = this.#start === undefined ? "now" : new Date(this.#start).toISOString();
517
- const base = this.getAttribute("texture-base");
518
515
  const center = this.hasAttribute("meridian") ? `${num(this.getAttribute("meridian"), 10)},20` : "10,20";
519
- const key = `${kind}|${center}|${time}|${base ?? ""}`;
516
+ const size = Math.round(this.clientWidth || 320);
517
+ const fill = this.#fill();
518
+ const key = `line|${center}|${fill}`;
520
519
  if (this.#delegateEl !== undefined && this.#delegateKind === key)
521
520
  return;
522
- const size = Math.round(this.clientWidth || 320);
523
- const el = kind === "live" ? await delegate.live({ center, time, textureBase: base }) : await delegate.line({ center, size });
521
+ const el = await delegate.line({ center, size });
524
522
  this.#delegateEl?.remove();
525
523
  this.#stage.innerHTML = "";
526
- el.style.cssText = "display:block;width:100%;height:100%;aspect-ratio:1";
524
+ el.style.cssText = `display:block;width:${fill * 100}%;height:${fill * 100}%;aspect-ratio:1;position:absolute;left:${(1 - fill) * 50}%;top:${(1 - fill) * 50}%`;
527
525
  this.#stage.append(el);
528
526
  this.#delegateEl = el;
529
527
  this.#delegateKind = key;
530
- this.#set(kind === "live" ? "live" : this.look === "line" ? "line" : "still");
528
+ this.#set("line");
529
+ }
530
+ /** How much of the picture's width the body's disc takes: 1 alone, less in a scale picture (`scale-to`, `span-km`). */
531
+ #fill() {
532
+ const scaleTo = this.getAttribute("scale-to");
533
+ const span = num(this.getAttribute("span-km"), 0);
534
+ const half = span > 0 ? span : scaleTo !== null && scaleTo in BODIES ? BODIES[scaleTo].radiusKm * 1.06 : 0;
535
+ return half > 0 ? Math.max(0.01, Math.min(1, BODIES[this.body].radiusKm / half)) : 1;
531
536
  }
532
537
  /** Make the view's still, once, when it is in sight. */
533
538
  #still() {
@@ -0,0 +1,96 @@
1
+ import { type BodyId, type Look } from "./facts.ts";
2
+ /**
3
+ * The ways `embedCode` writes a body into a page.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { EMBED_KINDS } from "@johnmorrisdotca/wakusei-planets";
8
+ *
9
+ * console.log(EMBED_KINDS.join(", "));
10
+ * ```
11
+ */
12
+ export declare const EMBED_KINDS: readonly ["html", "module", "react", "vue", "angular", "svelte", "iframe"];
13
+ /**
14
+ * One of the ways in `EMBED_KINDS`.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { EMBED_KINDS, type EmbedKind } from "@johnmorrisdotca/wakusei-planets";
19
+ *
20
+ * const kind: EmbedKind = EMBED_KINDS[2];
21
+ * console.log(kind);
22
+ * ```
23
+ */
24
+ export type EmbedKind = (typeof EMBED_KINDS)[number];
25
+ /**
26
+ * What the reader chose: the body, how it is drawn, and whether it is a portrait or the sky at a date.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * import { embedCode, type EmbedOptions } from "@johnmorrisdotca/wakusei-planets";
31
+ *
32
+ * const options: EmbedOptions = { body: "saturn", look: "painted", size: 320, turn: 12 };
33
+ * console.log(embedCode(options, "html"));
34
+ * ```
35
+ */
36
+ export interface EmbedOptions {
37
+ body: BodyId;
38
+ look?: Look;
39
+ /** The date and time as an ISO string, or `now`: the body as the Earth sees it then. Left out, a portrait. */
40
+ time?: string;
41
+ /** A planet with its moons on their orbits (needs `time`). */
42
+ moons?: boolean;
43
+ /** Degrees a second it turns by itself; left out or 0, it is still. */
44
+ turn?: number;
45
+ /** Pixels across. */
46
+ size?: number;
47
+ }
48
+ /**
49
+ * Where the embed code points: the package's version on jsDelivr, and the published demo, where `embed.html` is.
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * import { embedCode } from "@johnmorrisdotca/wakusei-planets";
54
+ *
55
+ * console.log(embedCode({ body: "mars", look: "line" }, "iframe", { site: "https://example.com/wakusei/" }));
56
+ * ```
57
+ */
58
+ export interface EmbedSettings {
59
+ /** The published demo's address, ending in a slash. */
60
+ site?: string;
61
+ /** The package's address on the CDN, with its version. */
62
+ cdn?: string;
63
+ }
64
+ /** The entry point that holds a body's shaded and painted look. */
65
+ export declare function entryOf(id: BodyId): string;
66
+ /**
67
+ * The text that puts a body in a page, for one way of making the page. Each way is built, parsed or run by the package's tests: the plain HTML one with a script tag from jsDelivr, an ES module, a React component,
68
+ * a Vue single-file component, an Angular standalone component, a Svelte component, and an iframe of the demo's `embed.html`.
69
+ *
70
+ * @param options - The body, its look, the date, the moons, the turn and the size.
71
+ * @param kind - The way the page is made; `html` by default.
72
+ * @param settings - The CDN and the demo's address, when they are not the package's own.
73
+ * @returns The text to paste into the page's source.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * import { embedCode } from "@johnmorrisdotca/wakusei-planets";
78
+ *
79
+ * console.log(embedCode({ body: "jupiter", look: "realistic", time: "now", moons: true }, "react"));
80
+ * ```
81
+ */
82
+ export declare function embedCode(options: EmbedOptions, kind?: EmbedKind, settings?: EmbedSettings): string;
83
+ /**
84
+ * The credits that travel with a picture of a body: the line and shaded looks are drawn from numbers, the painted look uses public-domain pictures.
85
+ *
86
+ * @param options - The choices, so only what is used is named.
87
+ * @returns A short line of credit to put near the picture.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * import { embedCredit } from "@johnmorrisdotca/wakusei-planets";
92
+ *
93
+ * console.log(embedCredit({ body: "mars", look: "painted" }));
94
+ * ```
95
+ */
96
+ export declare function embedCredit(options: EmbedOptions): string;
package/dist/embed.js ADDED
@@ -0,0 +1,112 @@
1
+ // The text to put a body in a page, for each way a page is made: a script tag from a CDN, an ES module, React, Vue, Angular, Svelte, or an iframe of `embed.html`. The demo's "Put it in your page"
2
+ // section shows these as the reader changes a choice, and the tests build or parse every one. Pure: no DOM.
3
+ import { BODIES } from "./facts.js";
4
+ import { VERSION } from "./version.js";
5
+ /**
6
+ * The ways `embedCode` writes a body into a page.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { EMBED_KINDS } from "@johnmorrisdotca/wakusei-planets";
11
+ *
12
+ * console.log(EMBED_KINDS.join(", "));
13
+ * ```
14
+ */
15
+ export const EMBED_KINDS = ["html", "module", "react", "vue", "angular", "svelte", "iframe"];
16
+ const SITE = "https://johnmorrisdotca.github.io/wakusei-planets/";
17
+ const CDN = `https://cdn.jsdelivr.net/npm/@johnmorrisdotca/wakusei-planets@${VERSION}`;
18
+ const PACKAGE = "@johnmorrisdotca/wakusei-planets";
19
+ /** The entry point that holds a body's shaded and painted look. */
20
+ export function entryOf(id) {
21
+ const parent = BODIES[id].parent;
22
+ return parent === undefined ? id : `moons-${parent}`;
23
+ }
24
+ /** The attributes of the element for these choices, in the order they are written. */
25
+ function attributesOf(o) {
26
+ const out = [["body", o.body], ["look", o.look ?? "realistic"]];
27
+ if (o.size !== undefined && o.size !== 320)
28
+ out.push(["size", String(o.size)]);
29
+ if (o.time !== undefined)
30
+ out.push(["time", o.time]);
31
+ if (o.moons === true && o.time !== undefined)
32
+ out.push(["moons", ""]);
33
+ if (o.turn !== undefined && o.turn > 0)
34
+ out.push(["turn", String(o.turn)]);
35
+ return out;
36
+ }
37
+ const quote = (v) => `"${v.replace(/&/g, "&amp;").replace(/"/g, "&quot;")}"`;
38
+ const tagAttrs = (o) => attributesOf(o).map(([k, v]) => (v === "" ? k : `${k}=${quote(v)}`)).join(" ");
39
+ const needsShading = (o) => (o.look ?? "realistic") !== "line";
40
+ /**
41
+ * The text that puts a body in a page, for one way of making the page. Each way is built, parsed or run by the package's tests: the plain HTML one with a script tag from jsDelivr, an ES module, a React component,
42
+ * a Vue single-file component, an Angular standalone component, a Svelte component, and an iframe of the demo's `embed.html`.
43
+ *
44
+ * @param options - The body, its look, the date, the moons, the turn and the size.
45
+ * @param kind - The way the page is made; `html` by default.
46
+ * @param settings - The CDN and the demo's address, when they are not the package's own.
47
+ * @returns The text to paste into the page's source.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * import { embedCode } from "@johnmorrisdotca/wakusei-planets";
52
+ *
53
+ * console.log(embedCode({ body: "jupiter", look: "realistic", time: "now", moons: true }, "react"));
54
+ * ```
55
+ */
56
+ export function embedCode(options, kind = "html", settings = {}) {
57
+ const cdn = settings.cdn ?? CDN;
58
+ const site = settings.site ?? SITE;
59
+ const shaded = needsShading(options);
60
+ const entry = entryOf(options.body);
61
+ const size = options.size ?? 320;
62
+ if (kind === "iframe") {
63
+ const query = new URLSearchParams({ body: options.body, look: options.look ?? "realistic" });
64
+ if (options.time !== undefined)
65
+ query.set("time", options.time);
66
+ if (options.moons === true && options.time !== undefined)
67
+ query.set("moons", "1");
68
+ if (options.turn !== undefined && options.turn > 0)
69
+ query.set("turn", String(options.turn));
70
+ return `<iframe\n src=${quote(`${site}embed.html?${query.toString()}`)}\n title=${quote(BODIES[options.body].name.en)}\n width="${size}"\n height="${size}"\n loading="lazy"\n style="border: 0"\n></iframe>`;
71
+ }
72
+ if (kind === "html") {
73
+ const scripts = [`<script type="module" src="${cdn}/element/+esm"></script>`, ...(shaded ? [`<script type="module" src="${cdn}/${entry}/+esm"></script>`] : [])];
74
+ return `${scripts.join("\n")}\n\n<wakusei-body ${tagAttrs(options)}></wakusei-body>`;
75
+ }
76
+ const imports = [`import "${PACKAGE}/element";`, ...(shaded ? [`import "${PACKAGE}/${entry}";`] : [])];
77
+ if (kind === "module") {
78
+ const sets = attributesOf(options).map(([k, v]) => `planet.setAttribute(${JSON.stringify(k)}, ${JSON.stringify(v)});`);
79
+ return `${imports.join("\n")}\n\nconst planet = document.createElement("wakusei-body");\n${sets.join("\n")}\ndocument.querySelector("#planet").append(planet);`;
80
+ }
81
+ const dynamic = [`void import("${PACKAGE}/element");`, ...(shaded ? [`void import("${PACKAGE}/${entry}");`] : [])];
82
+ if (kind === "react") {
83
+ const props = attributesOf(options).map(([k, v]) => (v === "" ? k : `${k}=${quote(v)}`)).join(" ");
84
+ return `import { useEffect } from "react";\n\nexport function Planet() {\n useEffect(() => {\n // The element needs a browser, so it is imported in an effect, never at the top of the file.\n${dynamic.map((l) => ` ${l}`).join("\n")}\n }, []);\n return <wakusei-body ${props} />;\n}`;
85
+ }
86
+ if (kind === "vue") {
87
+ return `<script setup lang="ts">\nimport { onMounted } from "vue";\n\nonMounted(() => {\n${dynamic.map((l) => ` ${l}`).join("\n")}\n});\n</script>\n\n<template>\n <wakusei-body ${tagAttrs(options)}></wakusei-body>\n</template>`;
88
+ }
89
+ if (kind === "angular") {
90
+ return `import { Component, CUSTOM_ELEMENTS_SCHEMA, OnInit } from "@angular/core";\n\n@Component({\n selector: "app-planet",\n standalone: true,\n schemas: [CUSTOM_ELEMENTS_SCHEMA],\n template: \`<wakusei-body ${tagAttrs(options)}></wakusei-body>\`,\n})\nexport class PlanetComponent implements OnInit {\n ngOnInit(): void {\n // The element needs a browser: ngOnInit also runs when a server renders.\n if (typeof window !== "undefined") {\n${dynamic.map((l) => ` ${l}`).join("\n")}\n }\n }\n}`;
91
+ }
92
+ return `<script lang="ts">\n import { onMount } from "svelte";\n\n onMount(() => {\n${dynamic.map((l) => ` ${l}`).join("\n")}\n });\n</script>\n\n<wakusei-body ${tagAttrs(options)}></wakusei-body>`;
93
+ }
94
+ /**
95
+ * The credits that travel with a picture of a body: the line and shaded looks are drawn from numbers, the painted look uses public-domain pictures.
96
+ *
97
+ * @param options - The choices, so only what is used is named.
98
+ * @returns A short line of credit to put near the picture.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * import { embedCredit } from "@johnmorrisdotca/wakusei-planets";
103
+ *
104
+ * console.log(embedCredit({ body: "mars", look: "painted" }));
105
+ * ```
106
+ */
107
+ export function embedCredit(options) {
108
+ const base = "Wakusei Planets by John Morris (MIT). Sizes, tilt and rotation: IAU and NASA fact sheets.";
109
+ const painted = (options.look ?? "realistic") === "painted" ? " Surface pictures: NASA, USGS and JPL imagery in the public domain (details in the package's NOTICE.md)." : "";
110
+ const earth = options.body === "earth" ? " Earth: NASA Earth Observatory, Blue Marble." : "";
111
+ return `${base}${painted}${earth}`;
112
+ }
package/dist/index.d.ts CHANGED
@@ -3,3 +3,4 @@ export { apparent, moonGeocentric, heliocentric, orbitPath, system, type Apparen
3
3
  export { drawnTilt, portrait, type PortraitOptions } from "./pose.ts";
4
4
  export { lineSvg, mix, type LineOptions } from "./line.ts";
5
5
  export { ringStrip, type RingStrip } from "./scene/rings.ts";
6
+ export { embedCode, embedCredit, EMBED_KINDS, type EmbedKind, type EmbedOptions, type EmbedSettings } from "./embed.ts";
package/dist/index.js CHANGED
@@ -3,3 +3,4 @@ export { apparent, moonGeocentric, heliocentric, orbitPath, system } from "./eph
3
3
  export { drawnTilt, portrait } from "./pose.js";
4
4
  export { lineSvg, mix } from "./line.js";
5
5
  export { ringStrip } from "./scene/rings.js";
6
+ export { embedCode, embedCredit, EMBED_KINDS } from "./embed.js";
@@ -52,7 +52,7 @@ export function createBodyItem(options) {
52
52
  key: options.key,
53
53
  async build({ three: T, assets, width, height }) {
54
54
  const look = lookOf(options.id) ?? fallbackLook(facts);
55
- const wantPainted = options.look === "painted" && look.painted !== undefined;
55
+ const wantPainted = (options.look === "painted" || look.mapped === true) && look.painted !== undefined;
56
56
  const geometry = assets.acquire("wk:sphere", () => new T.SphereGeometry(1, 128, 64), (g) => g.dispose());
57
57
  const shader = assets.acquire(`wk:body:${options.id}:r`, () => makeBodyMaterial(T, look, false), (m) => m.dispose());
58
58
  const paintedMaterial = wantPainted ? assets.acquire(`wk:body:${options.id}:p`, () => makeBodyMaterial(T, look, true), (m) => m.dispose()) : undefined;
@@ -74,6 +74,8 @@ export interface BodyLook {
74
74
  /** How much of the night side shows (0 to 1). Default 0.04. */
75
75
  ambient?: number;
76
76
  painted?: PaintedMap;
77
+ /** Draw the painted map in the realistic look too: for a body with no honest drawing from numbers (the Earth's continents). */
78
+ mapped?: boolean;
77
79
  }
78
80
  /**
79
81
  * Teach the package how to draw a body in three.js. Each body's entry point (`@johnmorrisdotca/wakusei-planets/jupiter`) calls this once as it loads; call it yourself to add or replace a look.
@@ -1,18 +1,12 @@
1
1
  import type { BodyId } from "../facts.ts";
2
2
  import type { BodyLook } from "./look.ts";
3
- /** A body drawn by another package's element (the Earth, by chikyu-earth) instead of by this one: the element asks it for what to show. */
3
+ /** A body whose line look another package's element draws (the Earth, by chikyu-earth) instead of this one: the element asks it for what to show. The shaded and painted looks are always this package's. */
4
4
  export interface Delegate {
5
5
  /** The line look: an element for the body, centred on a longitude and latitude (`"lon,lat"`). */
6
6
  line(options: {
7
7
  center: string;
8
8
  size: number;
9
9
  }): Promise<HTMLElement>;
10
- /** The shaded and painted looks, drawn live: an element lit by the Sun at `time` (`"now"` or an ISO date). */
11
- live(options: {
12
- center: string;
13
- time: string;
14
- textureBase: string | null;
15
- }): Promise<HTMLElement>;
16
10
  }
17
11
  export declare const looks: Map<BodyId, BodyLook>, waiting: Set<() => void>, delegates: Map<BodyId, Delegate>;
18
12
  /** Call `wake` whenever a look is registered; returns a function that stops it. */
Binary file
Binary file
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.3.0";
1
+ export declare const VERSION = "0.4.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.3.0";
1
+ export const VERSION = "0.4.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnmorrisdotca/wakusei-planets",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Wakusei Planets 惑星 — The Sun, planets and moons for the web: each as a clean line drawing, a shaded realistic drawing or a painted surface map, with real sizes, axial tilt and rotation as data, and positions of the moons for any date and time. SVG for the lines, three.js (optional) for the rest.",
5
5
  "keywords": [
6
6
  "planets",
@@ -168,6 +168,7 @@
168
168
  "check": "pnpm lint && pnpm typecheck && pnpm test && pnpm build && node scripts/check-package.ts",
169
169
  "prepublishOnly": "pnpm check",
170
170
  "family:readme": "node scripts/family-readme.mjs",
171
+ "readme:images": "node scripts/readme-images.mjs",
171
172
  "site": "pnpm build && node scripts/site.ts",
172
173
  "dev": "pnpm site && node scripts/dev.ts",
173
174
  "test:demo": "pnpm site && node scripts/docs-site.mjs build && playwright test --project=chromium-desk --project=chromium-phone --project=webkit-desk --project=webkit-phone",
@@ -186,7 +187,7 @@
186
187
  "@angular/core": "^22.2.4",
187
188
  "@angular/platform-browser": "^22.2.4",
188
189
  "@eslint/js": "^9.0.0",
189
- "@johnmorrisdotca/chikyu-earth": "^0.7.0",
190
+ "@johnmorrisdotca/chikyu-earth": "^0.10.0",
190
191
  "@playwright/test": "1.64.0",
191
192
  "@sveltejs/vite-plugin-svelte": "^7.3.1",
192
193
  "@types/node": "^24.0.0",
@@ -216,11 +217,11 @@
216
217
  },
217
218
  "packageManager": "pnpm@10.28.0",
218
219
  "dependencies": {
219
- "@johnmorrisdotca/kazoku-ui": "^0.9.1"
220
+ "@johnmorrisdotca/kazoku-ui": "^0.11.0"
220
221
  },
221
222
  "peerDependencies": {
222
- "three": ">=0.160.0",
223
- "@johnmorrisdotca/chikyu-earth": ">=0.7.0"
223
+ "@johnmorrisdotca/chikyu-earth": ">=0.10.0",
224
+ "three": ">=0.160.0"
224
225
  },
225
226
  "peerDependenciesMeta": {
226
227
  "three": {