@johnmorrisdotca/uchusen-rockets 0.7.0 → 0.9.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 +15 -0
- package/README.md +3 -3
- package/dist/element/scene.d.ts +3 -0
- package/dist/element/scene.js +29 -3
- package/dist/embed.d.ts +2 -0
- package/dist/embed.js +3 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.0] - 2026-11-04
|
|
6
|
+
|
|
7
|
+
"Put this rocket on your site" is the family's embed panel (MODULES-379).
|
|
8
|
+
|
|
9
|
+
- **The demo's copy panel and the code builder are kazoku-ui's `<kazoku-embed-panel>`** (kazoku-ui ^0.15.1). Nothing they had is gone, and a test counts it: the six ways (CDN script as the HTML tab, iframe, module, SVG, data address, image link), the note for each (in English and Japanese), Copy (now "Copy code"), Save SVG, Save PNG, Open the embed page, and the credit line, which shows only for a painted rocket whose parts need one.
|
|
10
|
+
- **Added by the panel**: React, Vue, Angular and Svelte tabs (the element, with a painted rocket's credit beside it), a size (240, 320, 480, 640), the drawing beside the code, and Read the docs. The picture is the drawing itself, never the 3D view or an iframe, so it adds no WebGL context and no frame loop.
|
|
11
|
+
- **`EmbedSettings.size`**: the iframe's largest width and the linked picture's width and height (480 by default, so the old text is unchanged).
|
|
12
|
+
- `demo/embed-code.ts` writes the framework tabs; `test/embed-panel.test.ts` holds the six ways to the package's own `embedCode`, the attributes to ones the element reads, the credit to every tab, and compiles the React, Vue, Angular and Svelte code.
|
|
13
|
+
- `scripts/check-standard.mjs` is kazoku-ui 0.15's (the `embed-panel` item); the demo moves to `@johnmorrisdotca/kazoku-ui` ^0.15.1.
|
|
14
|
+
|
|
15
|
+
## [0.8.0] - 2026-11-04
|
|
16
|
+
|
|
17
|
+
- **The wheel zooms the view.** The element's own hands (the ones a page without the family's view controls gets) now zoom with the wheel over the 3D view, as a pinch did; before, the wheel zoomed only once the view had focus. With the pointer anywhere else the page scrolls, and a wheel that starts with the zoom already at its end scrolls the page too, so the page is never trapped. A sideways swipe, or Shift and the wheel, is left to the page. Under the family's view controls (`drive()`) nothing changes: they already zoomed with the wheel (kazoku-ui 0.13).
|
|
18
|
+
- Demo and documentation: the hint under the view, the Element page, the README and the Looks guide (English and Japanese) say that the wheel zooms, not Ctrl or Command with the wheel.
|
|
19
|
+
|
|
5
20
|
## [0.7.0] - 2026-11-03
|
|
6
21
|
|
|
7
22
|
- Demo: the view controls' Auto-turn speeds are the family's speed buttons, 0.5×, 1× and 2× (kazoku-ui 0.14, `^0.14.0`), in place of Slow, Medium and Fast. The reader's choice is the controls' `pace` attribute and `?pace=2` in the address (the address key was `speed`). The base speed stays `speed="5"` degrees a second, which the buttons multiply, and Turn left and Turn right keep their way at any speed. The copied embed code turns at the same speed as the page (`spin` is the seconds a turn: 144, 72 or 36), where it said `slow` whatever the buttons said. Browser tests hold the three buttons, the speed each sets (sign kept), the address and the copied code, and that an old `?speed=fast` opens at 1×.
|
package/README.md
CHANGED
|
@@ -124,7 +124,7 @@ embedCode({ rocket: "starship", variant: "v3", look: "line" }, "element").includ
|
|
|
124
124
|
|
|
125
125
|
Changing `look` keeps the angle. Changing `show` is an animated move, and a cut under reduced motion. Each part alone fills the frame and has its own listed size. Every look works for every variant and every choice of parts. Front is the windward, tiled side; back is the steel side.
|
|
126
126
|
|
|
127
|
-
<p align="center"><img alt="The code builder: a row of makers, rockets, variants, parts, looks and views beside the rocket, and the
|
|
127
|
+
<p align="center"><img alt="The code builder: a row of makers, rockets, variants, parts, looks and views beside the rocket, and under it the embed panel: a size, ten tabs from HTML to Image link, the drawing beside the code, and Copy code, Open the embed page and Read the docs." src="https://raw.githubusercontent.com/johnmorrisdotca/uchusen-rockets/main/docs/images/builder.jpg" width="820"></p>
|
|
128
128
|
|
|
129
129
|
<p align="center"><img alt="The demo on a phone, the rocket on a screen 390 pixels wide." src="https://raw.githubusercontent.com/johnmorrisdotca/uchusen-rockets/main/docs/images/phone.jpg" width="320"></p>
|
|
130
130
|
|
|
@@ -297,7 +297,7 @@ Anything from outside is treated as text: an attribute that is not in its list f
|
|
|
297
297
|
|
|
298
298
|
## The code builder
|
|
299
299
|
|
|
300
|
-
[The code builder](https://johnmorrisdotca.github.io/uchusen-rockets/builder.html) is one screen: choose the maker, the rocket, the variant, the parts, the look, the view and the engines, see the rocket, and copy the code for any of the six ways. A painted rocket's credit is under the code. It is built from `ROCKETS`, `showsOf`, `embedCode` and `paintedCredit`, so it offers every rocket the package has, as it gains them.
|
|
300
|
+
[The code builder](https://johnmorrisdotca.github.io/uchusen-rockets/builder.html) is one screen: choose the maker, the rocket, the variant, the parts, the look, the view and the engines, see the rocket, and copy the code for any of the six ways, or for React, Vue, Angular and Svelte. The demo's "Put this rocket on your site" is the same panel (the family's `<kazoku-embed-panel>`): a size, the drawing beside the code, Save SVG and Save PNG, Open the embed page and Read the docs. A painted rocket's credit is under the code. It is built from `ROCKETS`, `showsOf`, `embedCode` and `paintedCredit`, so it offers every rocket the package has, as it gains them.
|
|
301
301
|
|
|
302
302
|
## Lit engines and flights
|
|
303
303
|
|
|
@@ -379,7 +379,7 @@ variantOf("starship", "2019").facts.diameter.value; // null
|
|
|
379
379
|
|
|
380
380
|
- The element has an accessible name from its `label` attribute, or from the fit report's label (such as "Block 3 (V3)") when there is none. The 3D view's canvas says how to turn it.
|
|
381
381
|
- A reader who asked for reduced motion gets no spin, no flames, no played flight and a cut instead of an animated move. The slider and charts still work.
|
|
382
|
-
- Turning works by drag, touch and the keyboard: the arrow keys turn it, `+` and `-` zoom, Home puts it back.
|
|
382
|
+
- Turning works by drag, touch and the keyboard: the arrow keys turn it, the wheel over the view, a pinch, `+` and `-` zoom, Home puts it back. With the pointer anywhere else the wheel scrolls the page, and so it does when the zoom is already at its end.
|
|
383
383
|
- The demo's controls are at least 44 pixels tall and work with a keyboard.
|
|
384
384
|
|
|
385
385
|
```html
|
package/dist/element/scene.d.ts
CHANGED
|
@@ -154,5 +154,8 @@ export declare class RocketViewer {
|
|
|
154
154
|
private fit;
|
|
155
155
|
private orbit;
|
|
156
156
|
private key;
|
|
157
|
+
/** The wheel over the view zooms it, as does a pinch (Ctrl or Command and the wheel). A wheel that starts with the zoom already at its end, or a sideways swipe, leaves the page to scroll. */
|
|
158
|
+
private onWheel;
|
|
159
|
+
private wheelTaken;
|
|
157
160
|
private zoom;
|
|
158
161
|
}
|
package/dist/element/scene.js
CHANGED
|
@@ -18,6 +18,8 @@ import { GroundKit } from "./ground.js";
|
|
|
18
18
|
import { PartPlumes, PlumeKit } from "./plumes.js";
|
|
19
19
|
const IDLE_STOP_MS = 90_000;
|
|
20
20
|
const MOVE_MS = 1400;
|
|
21
|
+
/** Wheel steps closer together than this belong to one turn of the wheel, so a zoom that reaches its end part-way through does not hand the rest to the page. */
|
|
22
|
+
const WHEEL_GESTURE_MS = 300;
|
|
21
23
|
/** Longest a draw waits for shaders to build: a browser that never says they are ready still draws. */
|
|
22
24
|
const COMPILE_MS = 4000;
|
|
23
25
|
const VIEW_MS = 700;
|
|
@@ -190,8 +192,7 @@ export class RocketViewer {
|
|
|
190
192
|
this.canvas.style.touchAction = "pan-y";
|
|
191
193
|
this.controls.addEventListener("start", () => this.touched());
|
|
192
194
|
this.controls.addEventListener("change", () => this.wake());
|
|
193
|
-
this.canvas.addEventListener("
|
|
194
|
-
this.canvas.addEventListener("blur", () => (this.controls.enableZoom = false));
|
|
195
|
+
this.canvas.addEventListener("wheel", this.onWheel, { passive: false });
|
|
195
196
|
this.canvas.addEventListener("pointerdown", () => this.canvas.focus({ preventScroll: true }));
|
|
196
197
|
this.canvas.addEventListener("dblclick", () => {
|
|
197
198
|
if (!this.driven)
|
|
@@ -205,7 +206,7 @@ export class RocketViewer {
|
|
|
205
206
|
}
|
|
206
207
|
homeAngle = { az: 32, el: 9 };
|
|
207
208
|
setLabel(label) {
|
|
208
|
-
this.canvas.setAttribute("aria-label", `3D view of ${label}. Drag or use the arrow keys to turn it; double-click or Home puts it back.`);
|
|
209
|
+
this.canvas.setAttribute("aria-label", `3D view of ${label}. Drag or use the arrow keys to turn it and the wheel to zoom; double-click or Home puts it back.`);
|
|
209
210
|
}
|
|
210
211
|
/** Loads a rocket and shows it. The camera's angle stays when a rocket is already showing. */
|
|
211
212
|
async load(built, show, look, angle) {
|
|
@@ -954,6 +955,7 @@ export class RocketViewer {
|
|
|
954
955
|
this.disposed = true;
|
|
955
956
|
cancelAnimationFrame(this.frame);
|
|
956
957
|
document.removeEventListener("visibilitychange", this.onVisibility);
|
|
958
|
+
this.canvas.removeEventListener("wheel", this.onWheel);
|
|
957
959
|
this.resize.disconnect();
|
|
958
960
|
this.controls.dispose();
|
|
959
961
|
this.disposeParts();
|
|
@@ -1121,6 +1123,7 @@ export class RocketViewer {
|
|
|
1121
1123
|
this.canvas.dataset.azimuth = angle.az.toFixed(2);
|
|
1122
1124
|
this.canvas.dataset.elevation = angle.el.toFixed(2);
|
|
1123
1125
|
this.canvas.dataset.moving = this.pose ? "true" : "false";
|
|
1126
|
+
this.canvas.dataset.distance = this.camera.position.distanceTo(this.controls.target).toFixed(2);
|
|
1124
1127
|
if (this.driven) {
|
|
1125
1128
|
this.canvas.dataset.zoom = this.driven.zoom.toFixed(2);
|
|
1126
1129
|
this.canvas.dataset.pan = `${Math.round(this.driven.panX)},${Math.round(this.driven.panY)}`;
|
|
@@ -1180,6 +1183,29 @@ export class RocketViewer {
|
|
|
1180
1183
|
move();
|
|
1181
1184
|
this.wake();
|
|
1182
1185
|
}
|
|
1186
|
+
/** The wheel over the view zooms it, as does a pinch (Ctrl or Command and the wheel). A wheel that starts with the zoom already at its end, or a sideways swipe, leaves the page to scroll. */
|
|
1187
|
+
onWheel = (event) => {
|
|
1188
|
+
if (this.driven)
|
|
1189
|
+
return;
|
|
1190
|
+
const pinch = event.ctrlKey || event.metaKey;
|
|
1191
|
+
if (!pinch && (event.shiftKey || Math.abs(event.deltaX) > Math.abs(event.deltaY)))
|
|
1192
|
+
return;
|
|
1193
|
+
const pixels = event.deltaMode === 1 ? event.deltaY * (pinch ? 5 : 33) : event.deltaMode === 2 ? event.deltaY * 800 : event.deltaY;
|
|
1194
|
+
const factor = pinch ? Math.exp(-pixels * 0.01) : MathUtils.clamp(Math.exp(-pixels * 0.002), 0.5, 2);
|
|
1195
|
+
const length = this.camera.position.distanceTo(this.controls.target);
|
|
1196
|
+
const atEnd = factor >= 1 ? length <= this.controls.minDistance * 1.0001 : length >= this.controls.maxDistance * 0.9999;
|
|
1197
|
+
const now = Number.isFinite(event.timeStamp) ? event.timeStamp : 0;
|
|
1198
|
+
if (!pinch && atEnd && !(now >= this.wheelTaken && now - this.wheelTaken < WHEEL_GESTURE_MS))
|
|
1199
|
+
return;
|
|
1200
|
+
event.preventDefault();
|
|
1201
|
+
this.wheelTaken = now;
|
|
1202
|
+
if (atEnd)
|
|
1203
|
+
return;
|
|
1204
|
+
this.touched();
|
|
1205
|
+
this.zoom(1 / factor);
|
|
1206
|
+
this.wake();
|
|
1207
|
+
};
|
|
1208
|
+
wheelTaken = Number.NEGATIVE_INFINITY;
|
|
1183
1209
|
zoom(factor) {
|
|
1184
1210
|
const offset = this.camera.position.clone().sub(this.controls.target);
|
|
1185
1211
|
const length = MathUtils.clamp(offset.length() * factor, this.controls.minDistance, this.controls.maxDistance);
|
package/dist/embed.d.ts
CHANGED
|
@@ -38,6 +38,8 @@ export interface EmbedSettings {
|
|
|
38
38
|
site?: string;
|
|
39
39
|
/** The CDN or package path the element is loaded from. */
|
|
40
40
|
cdn?: string;
|
|
41
|
+
/** Pixels across for the `iframe` (its largest width) and the `link` picture (its width; the height follows). 480 when left out. */
|
|
42
|
+
size?: number;
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
43
45
|
* The address of the published demo, where `embed.html` is.
|
package/dist/embed.js
CHANGED
|
@@ -190,6 +190,7 @@ const creditHtml = (credit) => (credit === "" ? "" : `\n<p>${esc(credit).replace
|
|
|
190
190
|
export function embedCode(options, kind = "element", settings = {}) {
|
|
191
191
|
const site = settings.site ?? SITE;
|
|
192
192
|
const cdn = settings.cdn ?? CDN;
|
|
193
|
+
const across = Number.isFinite(settings.size) && settings.size > 0 ? Math.round(settings.size) : 480;
|
|
193
194
|
const credit = paintedCredit(options);
|
|
194
195
|
if (kind === "svg")
|
|
195
196
|
return drawingOf(options);
|
|
@@ -199,13 +200,13 @@ export function embedCode(options, kind = "element", settings = {}) {
|
|
|
199
200
|
const path = assetPathOf(options);
|
|
200
201
|
const alt = esc(options.label ?? "Rocket");
|
|
201
202
|
const src = path === null ? `data:image/svg+xml;charset=utf-8,${encodeURIComponent(drawingOf(options))}` : `${cdn}/dist/assets/${path}`;
|
|
202
|
-
return `<img src="${esc(src)}" alt="${alt}" width="
|
|
203
|
+
return `<img src="${esc(src)}" alt="${alt}" width="${across}" height="${Math.round((across * 4) / 3)}" loading="lazy">${creditHtml(credit)}`;
|
|
203
204
|
}
|
|
204
205
|
if (kind === "iframe") {
|
|
205
206
|
const query = rocketToParams(options).toString();
|
|
206
207
|
const src = `${site}embed.html${query === "" ? "" : `?${query}`}`;
|
|
207
208
|
const title = options.label ?? "Rocket";
|
|
208
|
-
return `<iframe\n src="${esc(src)}"\n title="${esc(title)}"\n loading="lazy"\n style="width:100%;max-width
|
|
209
|
+
return `<iframe\n src="${esc(src)}"\n title="${esc(title)}"\n loading="lazy"\n style="width:100%;max-width:${across}px;aspect-ratio:3 / 4;border:0"\n></iframe>${creditHtml(credit)}`;
|
|
209
210
|
}
|
|
210
211
|
if (kind === "module") {
|
|
211
212
|
const description = descriptionOf(options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnmorrisdotca/uchusen-rockets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Uchusen Rockets 宇宙船: line drawings and 3D views of rockets, as a web component. Every variant with its own sourced sizes, a typed way to assemble a rocket from parts, stack and separate. No logos. three.js is a peer dependency.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"rocket",
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
"@gltf-transform/extensions": "4.5.1",
|
|
126
126
|
"@gltf-transform/functions": "4.5.1",
|
|
127
127
|
"@johnmorrisdotca/chikyu-earth": "0.10.0",
|
|
128
|
-
"@johnmorrisdotca/kazoku-ui": "^0.
|
|
128
|
+
"@johnmorrisdotca/kazoku-ui": "^0.15.1",
|
|
129
129
|
"@playwright/test": "1.64.0",
|
|
130
130
|
"@sveltejs/vite-plugin-svelte": "^7.3.1",
|
|
131
131
|
"@types/node": "24.10.1",
|