@a2f0/skyline 0.2.62 → 0.2.64
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/README.md +1 -1
- package/docs/package.md +2 -2
- package/package.json +1 -1
- package/site/skyline-3d.js +48 -26
- package/site/skyline-animated.svg +37 -4
- package/site/skyline-viewer.js +42 -49
- package/site/skyline-webgl.html +22 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Serve `dist/` over HTTP rather than opening files directly: WebGL and the 3D pag
|
|
|
23
23
|
|
|
24
24
|
| Page | Shows | Docs |
|
|
25
25
|
| --- | --- | --- |
|
|
26
|
-
| `index.html` | The viewer, mounted as the package mounts it: opens on the 3D skyline, with the
|
|
26
|
+
| `index.html` | The viewer, mounted as the package mounts it: opens on the 3D skyline, with the original drawing one button away and links to the enhanced drawing and the WebGL prototype | [The viewer](docs/viewer.md) |
|
|
27
27
|
| `skyline-3d.html` | The full-screen 3D skyline on its own: camera views, streets, footprints, celebratory lights, and a building menu | [Full-screen 3D skyline](docs/viewer.md#full-screen-3d-skyline) |
|
|
28
28
|
| `building-detail.html?building=<id>` | One mapped building alone on its platform | [Building menu and detail](docs/viewer.md#building-menu-and-detail) |
|
|
29
29
|
| `skyline-study.html` | The 3D scene over the drawing, in the geographic layout or the original drawing's fit | [3D studies](docs/studies.md#skyline-study) |
|
package/docs/package.md
CHANGED
|
@@ -312,8 +312,8 @@ Before 0.2, the viewer ran in an iframe. Hosts upgrading:
|
|
|
312
312
|
- If the viewer's or scene's code fails to load, as on a dropped connection, the
|
|
313
313
|
browser keeps that failure for the page: later mounts on the same page fail the same
|
|
314
314
|
way until it reloads. A frame used to retry with a new document.
|
|
315
|
-
- The site's own `index.html`
|
|
316
|
-
enhanced drawing and the WebGL prototype, as pages of their own
|
|
315
|
+
- The site's own `index.html` links two prototypes from its toolbar, the hover-scripted
|
|
316
|
+
enhanced drawing and the WebGL prototype, which open as pages of their own; a host's
|
|
317
317
|
viewer never shows them.
|
|
318
318
|
|
|
319
319
|
## Import a scene or a building
|
package/package.json
CHANGED
package/site/skyline-3d.js
CHANGED
|
@@ -6,6 +6,41 @@ import { createGeographicRoads, geographicOrigin } from "./models/skyline-geogra
|
|
|
6
6
|
import { celebrations } from "./models/celebrations.js";
|
|
7
7
|
import { createDaylight, daylightGround, deferSkylineColour } from "./models/colour-materials.js";
|
|
8
8
|
import { loadMarkup, loadStylesheet } from "./markup.js";
|
|
9
|
+
/**
|
|
10
|
+
* The press's next state, and the menu to open now, if any. A mouse press holds the menu its
|
|
11
|
+
* event asks for until it is released without moving, so a right-drag still pans; a touch's
|
|
12
|
+
* long press, or an event after a release, opens at once, unless a mouse dragged first.
|
|
13
|
+
*/
|
|
14
|
+
export function pressTransition(press, event) {
|
|
15
|
+
const stay = { press, open: null };
|
|
16
|
+
switch (event.type) {
|
|
17
|
+
case "down":
|
|
18
|
+
return { press: { status: "down", x: event.x, y: event.y, mouse: event.mouse, moved: false, pending: null }, open: null };
|
|
19
|
+
case "move":
|
|
20
|
+
if (press.status !== "down" || press.moved || Math.hypot(event.x - press.x, event.y - press.y) <= 4)
|
|
21
|
+
return stay;
|
|
22
|
+
return { press: { ...press, moved: true }, open: null };
|
|
23
|
+
case "up":
|
|
24
|
+
if (press.status !== "down")
|
|
25
|
+
return stay;
|
|
26
|
+
return { press: { status: "released", mouse: press.mouse, moved: press.moved }, open: press.moved ? null : press.pending };
|
|
27
|
+
case "cancel":
|
|
28
|
+
if (press.status !== "down")
|
|
29
|
+
return stay;
|
|
30
|
+
return { press: { status: "released", mouse: press.mouse, moved: press.moved }, open: null };
|
|
31
|
+
case "menu":
|
|
32
|
+
if (press.status === "down" && press.mouse)
|
|
33
|
+
return { press: { ...press, pending: event.menu }, open: null };
|
|
34
|
+
return { press, open: press.status !== "idle" && press.mouse && press.moved ? null : event.menu };
|
|
35
|
+
case "dismiss":
|
|
36
|
+
return press.status === "down" && press.pending ? { press: { ...press, pending: null }, open: null } : stay;
|
|
37
|
+
default:
|
|
38
|
+
return assertNever(event);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function assertNever(value) {
|
|
42
|
+
throw new Error(`Unhandled press event: ${JSON.stringify(value)}`);
|
|
43
|
+
}
|
|
9
44
|
// The skyline viewer's full-screen 3D mode: every mapped building, seen through the
|
|
10
45
|
// drawing's own camera and framed as the viewer frames the drawing, so each tower stands
|
|
11
46
|
// where its drawn one does until the camera moves. It renders into skyline-3d.html's own
|
|
@@ -266,8 +301,13 @@ export function startSkyline3d({ root, frame, assets, signal, colour: initiallyC
|
|
|
266
301
|
const detailLink = root.querySelector("#building-detail-link");
|
|
267
302
|
const lightingMenu = root.querySelector("#building-lighting-menu");
|
|
268
303
|
let menuBuilding = "";
|
|
269
|
-
let press =
|
|
270
|
-
|
|
304
|
+
let press = { status: "idle" };
|
|
305
|
+
function sendPress(event) {
|
|
306
|
+
const next = pressTransition(press, event);
|
|
307
|
+
press = next.press;
|
|
308
|
+
if (next.open)
|
|
309
|
+
openMenu(next.open.building, next.open.x, next.open.y);
|
|
310
|
+
}
|
|
271
311
|
function openMenu(building, x, y) {
|
|
272
312
|
menuBuilding = building.building.userData["buildingId"];
|
|
273
313
|
menuTitle.textContent = building.building.userData["geography"]?.name ?? building.building.name;
|
|
@@ -310,7 +350,7 @@ export function startSkyline3d({ root, frame, assets, signal, colour: initiallyC
|
|
|
310
350
|
detailLink.focus({ preventScroll: true });
|
|
311
351
|
}
|
|
312
352
|
function closeMenu() {
|
|
313
|
-
|
|
353
|
+
sendPress({ type: "dismiss" });
|
|
314
354
|
if (menu.hidden)
|
|
315
355
|
return;
|
|
316
356
|
if (menu.contains(root.activeElement))
|
|
@@ -329,35 +369,17 @@ export function startSkyline3d({ root, frame, assets, signal, colour: initiallyC
|
|
|
329
369
|
menuPress = null;
|
|
330
370
|
}, listening);
|
|
331
371
|
canvas.addEventListener("pointerdown", (event) => {
|
|
332
|
-
|
|
333
|
-
}, listening);
|
|
334
|
-
window.addEventListener("pointermove", (event) => {
|
|
335
|
-
if (press?.down && Math.hypot(event.clientX - press.x, event.clientY - press.y) > 4)
|
|
336
|
-
press.moved = true;
|
|
337
|
-
}, listening);
|
|
338
|
-
window.addEventListener("pointerup", () => {
|
|
339
|
-
if (!press?.down)
|
|
340
|
-
return;
|
|
341
|
-
press.down = false;
|
|
342
|
-
const pending = pendingMenu;
|
|
343
|
-
pendingMenu = null;
|
|
344
|
-
if (pending && !press.moved)
|
|
345
|
-
openMenu(pending.building, pending.x, pending.y);
|
|
346
|
-
}, listening);
|
|
347
|
-
window.addEventListener("pointercancel", () => {
|
|
348
|
-
if (press)
|
|
349
|
-
press.down = false;
|
|
350
|
-
pendingMenu = null;
|
|
372
|
+
sendPress({ type: "down", x: event.clientX, y: event.clientY, mouse: event.pointerType === "mouse" });
|
|
351
373
|
}, listening);
|
|
374
|
+
window.addEventListener("pointermove", (event) => sendPress({ type: "move", x: event.clientX, y: event.clientY }), listening);
|
|
375
|
+
window.addEventListener("pointerup", () => sendPress({ type: "up" }), listening);
|
|
376
|
+
window.addEventListener("pointercancel", () => sendPress({ type: "cancel" }), listening);
|
|
352
377
|
canvas.addEventListener("contextmenu", (event) => {
|
|
353
378
|
const building = viewer.buildingAt(event.clientX, event.clientY);
|
|
354
379
|
if (!building)
|
|
355
380
|
return closeMenu();
|
|
356
381
|
event.preventDefault();
|
|
357
|
-
|
|
358
|
-
pendingMenu = { building, x: event.clientX, y: event.clientY };
|
|
359
|
-
else if (!(press?.mouse && press.moved))
|
|
360
|
-
openMenu(building, event.clientX, event.clientY);
|
|
382
|
+
sendPress({ type: "menu", menu: { building, x: event.clientX, y: event.clientY } });
|
|
361
383
|
}, listening);
|
|
362
384
|
canvas.addEventListener("wheel", closeMenu, { passive: true, signal });
|
|
363
385
|
window.addEventListener("blur", closeMenu, listening);
|
|
@@ -5,9 +5,6 @@
|
|
|
5
5
|
preserveAspectRatio="xMidYMax meet"
|
|
6
6
|
version="1.1"
|
|
7
7
|
id="svg6766"
|
|
8
|
-
role="img"
|
|
9
|
-
aria-label="Chicago skyline at night"
|
|
10
|
-
aria-describedby="skyline-description"
|
|
11
8
|
style="color-scheme:dark"
|
|
12
9
|
sodipodi:docname="skyline-animated.svg"
|
|
13
10
|
inkscape:version="1.1 (c4e8f9e, 2021-05-24)"
|
|
@@ -144,12 +141,22 @@
|
|
|
144
141
|
.building-tooltip {
|
|
145
142
|
pointer-events: none;
|
|
146
143
|
}
|
|
144
|
+
.back-link text {
|
|
145
|
+
fill: #a8a8a8;
|
|
146
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
|
147
|
+
text-decoration: underline;
|
|
148
|
+
}
|
|
149
|
+
.back-link:hover text,
|
|
150
|
+
.back-link:focus-visible text {
|
|
151
|
+
fill: #ffffff;
|
|
152
|
+
}
|
|
147
153
|
@media (prefers-reduced-motion: reduce) {
|
|
148
154
|
.interactive-building.is-active { transition: none; }
|
|
149
155
|
}
|
|
150
156
|
]]></style>
|
|
151
157
|
</defs>
|
|
152
|
-
|
|
158
|
+
<!-- The drawing is one image; the root stays a plain document so the link back reaches assistive technology. -->
|
|
159
|
+
<g id="skyline-position" transform="translate(1105.5923,-87.26366)" role="img" aria-label="Chicago skyline at night" aria-describedby="skyline-description">
|
|
153
160
|
<g
|
|
154
161
|
inkscape:groupmode="layer"
|
|
155
162
|
id="layer4"
|
|
@@ -18670,6 +18677,17 @@
|
|
|
18670
18677
|
style="display:inline;stroke-width:1.33526" />
|
|
18671
18678
|
</g>
|
|
18672
18679
|
</g>
|
|
18680
|
+
<!-- Opened on its own, the drawing links back to the viewer; the script keeps it in the top-left corner. -->
|
|
18681
|
+
<a
|
|
18682
|
+
id="back-link"
|
|
18683
|
+
class="back-link"
|
|
18684
|
+
href="index.html">
|
|
18685
|
+
<text
|
|
18686
|
+
id="back-link-text"
|
|
18687
|
+
font-size="70"
|
|
18688
|
+
x="40"
|
|
18689
|
+
y="110">← back to skyline</text>
|
|
18690
|
+
</a>
|
|
18673
18691
|
<g
|
|
18674
18692
|
id="building-tooltip"
|
|
18675
18693
|
class="building-tooltip"
|
|
@@ -18788,6 +18806,21 @@
|
|
|
18788
18806
|
}
|
|
18789
18807
|
}
|
|
18790
18808
|
|
|
18809
|
+
// The way back to the viewer: 12px text, 12px in from the window's top left, at every
|
|
18810
|
+
// window size.
|
|
18811
|
+
const backLink = document.querySelector("#back-link-text");
|
|
18812
|
+
function placeBackLink() {
|
|
18813
|
+
const matrix = svg.getScreenCTM();
|
|
18814
|
+
if (!matrix) return;
|
|
18815
|
+
const scale = Math.hypot(matrix.a, matrix.b) || 1;
|
|
18816
|
+
const start = svgPoint(0, 0, matrix.inverse());
|
|
18817
|
+
backLink.setAttribute("font-size", 12 / scale);
|
|
18818
|
+
backLink.setAttribute("x", start.x + 12 / scale);
|
|
18819
|
+
backLink.setAttribute("y", start.y + 24 / scale);
|
|
18820
|
+
}
|
|
18821
|
+
placeBackLink();
|
|
18822
|
+
window.addEventListener("resize", placeBackLink, { passive: true });
|
|
18823
|
+
|
|
18791
18824
|
svg.addEventListener("pointerover", updateHover, { passive: true });
|
|
18792
18825
|
svg.addEventListener("pointermove", updateHover, { passive: true });
|
|
18793
18826
|
svg.addEventListener("pointerleave", deactivate);
|
package/site/skyline-viewer.js
CHANGED
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
import { loadMarkup } from "./markup.js";
|
|
2
2
|
import { provideThree } from "./three-engine.js";
|
|
3
|
-
// What
|
|
4
|
-
//
|
|
3
|
+
// What the site toolbar's mode button shows, says and announces. The viewer opens on the 3D
|
|
4
|
+
// skyline; the toggle shows the original drawing and, pressed again, returns to it.
|
|
5
5
|
const home = { label: "3d skyline", title: "Return to the 3D skyline", status: "3D skyline displayed" };
|
|
6
6
|
const toggles = {
|
|
7
7
|
original: { id: "toggle-skyline", label: "show original", title: "Compare with the original skyline SVG", status: "Original skyline displayed" },
|
|
8
|
-
webgl: { id: "toggle-webgl", label: "show webgl", title: "Preview the WebGL skyline prototype", status: "WebGL skyline prototype displayed" },
|
|
9
|
-
enhanced: { id: "toggle-enhanced", label: "show enhanced", title: "Show the enhanced interactive skyline drawing", status: "Enhanced interactive skyline displayed" },
|
|
10
|
-
};
|
|
11
|
-
// The two prototypes the site's toolbar shows in place are pages of their own, as the
|
|
12
|
-
// studies are: the hover-scripted drawing and the WebGL prototype. Only the site's own
|
|
13
|
-
// index.html shows them, in frames; a host's viewer never does.
|
|
14
|
-
const prototypes = {
|
|
15
|
-
enhanced: { src: "skyline-animated.svg", title: "Animated Chicago skyline at night" },
|
|
16
|
-
webgl: { src: "skyline-webgl.html", title: "WebGL Chicago skyline prototype" },
|
|
17
8
|
};
|
|
9
|
+
// The two prototypes are pages of their own, as the studies are: the hover-scripted drawing
|
|
10
|
+
// and the WebGL prototype. The site's toolbar links to them, and each page keeps its own
|
|
11
|
+
// scripts and a way back; a host's viewer never shows them.
|
|
12
|
+
const prototypes = [
|
|
13
|
+
{ id: "link-webgl", href: "skyline-webgl.html", label: "webgl prototype", title: "Open the WebGL skyline prototype", ct: "viewer.webgl" },
|
|
14
|
+
{ id: "link-enhanced", href: "skyline-animated.svg", label: "enhanced drawing", title: "Open the enhanced interactive skyline drawing", ct: "viewer.enhanced" },
|
|
15
|
+
];
|
|
16
|
+
export function sceneTransition(status, event) {
|
|
17
|
+
switch (event) {
|
|
18
|
+
case "ready":
|
|
19
|
+
return status === "booting" ? "ready" : status;
|
|
20
|
+
case "failed":
|
|
21
|
+
return status === "booting" ? "unavailable" : status;
|
|
22
|
+
case "lost":
|
|
23
|
+
return "unavailable";
|
|
24
|
+
default:
|
|
25
|
+
return assertNever(event);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function assertNever(value) {
|
|
29
|
+
throw new Error(`Unhandled scene event: ${String(value)}`);
|
|
30
|
+
}
|
|
18
31
|
const SCENE_FAILURE = "The 3D preview could not load. Check that WebGL 2 is enabled, then reload. You can still compare the SVG or return to the skyline.";
|
|
19
32
|
function element(document, tag, properties = {}, attributes = {}) {
|
|
20
33
|
const created = Object.assign(document.createElement(tag), properties);
|
|
@@ -48,26 +61,18 @@ export function mount(root, { assets, signal, controls, colour, theme, attributi
|
|
|
48
61
|
viewer.append(stars, scene, drawing, originalReturn, embeddedOriginal, status);
|
|
49
62
|
root.append(viewer);
|
|
50
63
|
let mode = "3d";
|
|
51
|
-
//
|
|
52
|
-
// or
|
|
53
|
-
let
|
|
54
|
-
// Both show original buttons wait while the scene boots, until its first frame,
|
|
55
|
-
// a failure to load, or a lost graphics context.
|
|
56
|
-
let booting = true;
|
|
57
|
-
const frames = {};
|
|
64
|
+
// Both show original buttons wait while the scene boots, until its first frame, a failure
|
|
65
|
+
// to load, or a lost graphics context; the drawing stays reachable while it is unavailable.
|
|
66
|
+
let sceneStatus = "booting";
|
|
58
67
|
const toolbar = navigation ? createToolbar() : null;
|
|
59
68
|
function updateView() {
|
|
60
|
-
viewer.toggleAttribute("data-booting",
|
|
69
|
+
viewer.toggleAttribute("data-booting", sceneStatus !== "ready" && mode === "3d");
|
|
61
70
|
scene.hidden = mode !== "3d";
|
|
62
71
|
if (mode === "original" && !drawing.getAttribute("src"))
|
|
63
72
|
drawing.src = new URL("skyline-original-fit.svg", assets).href;
|
|
64
73
|
drawing.hidden = mode !== "original";
|
|
65
|
-
if (mode === "enhanced" || mode === "webgl")
|
|
66
|
-
frames[mode] ??= createPrototype(mode);
|
|
67
|
-
for (const [name, frame] of Object.entries(frames))
|
|
68
|
-
frame.hidden = name !== mode;
|
|
69
74
|
originalReturn.hidden = mode !== "original";
|
|
70
|
-
embeddedOriginal.hidden = navigation || mode !== "3d" ||
|
|
75
|
+
embeddedOriginal.hidden = navigation || mode !== "3d" || sceneStatus !== "unavailable";
|
|
71
76
|
if (toolbar) {
|
|
72
77
|
for (const [name, toggle] of Object.entries(toggles)) {
|
|
73
78
|
const button = toolbar.querySelector(`#${toggle.id}`), showing = name === mode;
|
|
@@ -75,9 +80,13 @@ export function mount(root, { assets, signal, controls, colour, theme, attributi
|
|
|
75
80
|
button.setAttribute("aria-pressed", String(showing));
|
|
76
81
|
button.title = showing ? home.title : toggle.title;
|
|
77
82
|
}
|
|
78
|
-
toolbar.querySelector(`#${toggles.original.id}`).hidden = booting;
|
|
83
|
+
toolbar.querySelector(`#${toggles.original.id}`).hidden = sceneStatus === "booting";
|
|
79
84
|
}
|
|
80
85
|
}
|
|
86
|
+
function sendScene(event) {
|
|
87
|
+
sceneStatus = sceneTransition(sceneStatus, event);
|
|
88
|
+
updateView();
|
|
89
|
+
}
|
|
81
90
|
function showMode(next) {
|
|
82
91
|
mode = next;
|
|
83
92
|
updateView();
|
|
@@ -99,7 +108,7 @@ export function mount(root, { assets, signal, controls, colour, theme, attributi
|
|
|
99
108
|
showMode("3d");
|
|
100
109
|
const star = root.querySelector("#menu-toggle");
|
|
101
110
|
const focus = star?.getAttribute("aria-expanded") === "true" ? root.querySelector("#show-original") : star;
|
|
102
|
-
const target =
|
|
111
|
+
const target = sceneStatus === "ready" && focus?.matches(":enabled") ? focus : null;
|
|
103
112
|
target?.focus({ preventScroll: true });
|
|
104
113
|
if (!target || root.activeElement !== target) {
|
|
105
114
|
(embeddedOriginal.hidden ? toolbar?.querySelector(`#${toggles.original.id}`) : embeddedOriginal)?.focus({ preventScroll: true });
|
|
@@ -156,8 +165,7 @@ export function mount(root, { assets, signal, controls, colour, theme, attributi
|
|
|
156
165
|
return new Promise(() => { });
|
|
157
166
|
const message = element(document, "p", { className: "loading", textContent: SCENE_FAILURE }, { role: "status" });
|
|
158
167
|
scene.append(message);
|
|
159
|
-
|
|
160
|
-
updateView();
|
|
168
|
+
sendScene("failed");
|
|
161
169
|
throw error;
|
|
162
170
|
}
|
|
163
171
|
if (signal.aborted)
|
|
@@ -192,15 +200,11 @@ export function mount(root, { assets, signal, controls, colour, theme, attributi
|
|
|
192
200
|
colour,
|
|
193
201
|
onShowOriginal: showOriginal,
|
|
194
202
|
onReady() {
|
|
195
|
-
|
|
196
|
-
booting = false;
|
|
197
|
-
updateView();
|
|
203
|
+
sendScene("ready");
|
|
198
204
|
resolve();
|
|
199
205
|
},
|
|
200
206
|
onUnavailable() {
|
|
201
|
-
|
|
202
|
-
booting = false;
|
|
203
|
-
updateView();
|
|
207
|
+
sendScene("lost");
|
|
204
208
|
},
|
|
205
209
|
});
|
|
206
210
|
});
|
|
@@ -213,8 +217,7 @@ export function mount(root, { assets, signal, controls, colour, theme, attributi
|
|
|
213
217
|
// press away.
|
|
214
218
|
scene.querySelector("#loading").textContent = SCENE_FAILURE;
|
|
215
219
|
scene.querySelectorAll("button, input").forEach((control) => { control.disabled = true; });
|
|
216
|
-
|
|
217
|
-
updateView();
|
|
220
|
+
sendScene("failed");
|
|
218
221
|
throw error;
|
|
219
222
|
}
|
|
220
223
|
}
|
|
@@ -285,6 +288,9 @@ export function mount(root, { assets, signal, controls, colour, theme, attributi
|
|
|
285
288
|
toggleButton.addEventListener("click", () => showMode(mode === name ? "3d" : name), listening);
|
|
286
289
|
nav.append(toggleButton);
|
|
287
290
|
}
|
|
291
|
+
for (const prototype of prototypes) {
|
|
292
|
+
nav.append(element(document, "a", { id: prototype.id, href: new URL(prototype.href, assets).href, title: prototype.title, textContent: prototype.label }, { "data-ct": prototype.ct }));
|
|
293
|
+
}
|
|
288
294
|
nav.append(element(document, "a", { href: new URL("building-study.html", assets).href, textContent: "3d building" }, { "data-ct": "viewer.building-study" }), element(document, "a", { href: new URL("skyline-study.html", assets).href, textContent: "skyline study" }, { "data-ct": "viewer.skyline-study" }));
|
|
289
295
|
const debugButton = button("debug-motion", "debug motion", { "aria-describedby": "stars-motion-status" });
|
|
290
296
|
const motionStatus = element(document, "p", { className: "motion-status", id: "stars-motion-status" }, { role: "status" });
|
|
@@ -319,17 +325,4 @@ export function mount(root, { assets, signal, controls, colour, theme, attributi
|
|
|
319
325
|
resizing.observe(scene);
|
|
320
326
|
signal.addEventListener("abort", () => resizing.disconnect(), { once: true });
|
|
321
327
|
}
|
|
322
|
-
// A prototype's page, in a frame of its own, shown in place the first time it is chosen.
|
|
323
|
-
// Keys pressed inside it go to its own window, so F listens there too.
|
|
324
|
-
function createPrototype(name) {
|
|
325
|
-
const frame = element(document, "iframe", { className: "scene", id: `${name}-scene`, title: prototypes[name].title, src: new URL(prototypes[name].src, assets).href });
|
|
326
|
-
frame.addEventListener("load", () => {
|
|
327
|
-
try {
|
|
328
|
-
frame.contentWindow?.addEventListener("keydown", fullscreenShortcut, listening);
|
|
329
|
-
}
|
|
330
|
-
catch { /* A frame from another origin keeps its keys. */ }
|
|
331
|
-
}, listening);
|
|
332
|
-
viewer.insertBefore(frame, originalReturn);
|
|
333
|
-
return frame;
|
|
334
|
-
}
|
|
335
328
|
}
|
package/site/skyline-webgl.html
CHANGED
|
@@ -73,6 +73,26 @@
|
|
|
73
73
|
white-space: nowrap;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
.back {
|
|
77
|
+
position: fixed;
|
|
78
|
+
z-index: 3;
|
|
79
|
+
top: max(12px, env(safe-area-inset-top));
|
|
80
|
+
left: max(12px, env(safe-area-inset-left));
|
|
81
|
+
color: #a8a8a8;
|
|
82
|
+
font-size: 12px;
|
|
83
|
+
text-decoration: underline;
|
|
84
|
+
text-decoration-thickness: 1px;
|
|
85
|
+
text-underline-offset: 3px;
|
|
86
|
+
}
|
|
87
|
+
.back:hover,
|
|
88
|
+
.back:focus-visible {
|
|
89
|
+
color: #fff;
|
|
90
|
+
text-decoration-thickness: 2px;
|
|
91
|
+
}
|
|
92
|
+
.back:focus-visible {
|
|
93
|
+
outline: 1px solid #fff;
|
|
94
|
+
outline-offset: 4px;
|
|
95
|
+
}
|
|
76
96
|
[hidden] {
|
|
77
97
|
display: none !important;
|
|
78
98
|
}
|
|
@@ -80,6 +100,7 @@
|
|
|
80
100
|
<script type="module" src="click-tracking-page.js"></script>
|
|
81
101
|
</head>
|
|
82
102
|
<body>
|
|
103
|
+
<a class="back" id="back-link" href="index.html">← back to skyline</a>
|
|
83
104
|
<canvas id="skyline" role="img" aria-label="Interactive WebGL rendering of the Chicago skyline"></canvas>
|
|
84
105
|
<p class="loading" id="loading" role="status">preparing WebGL building layers…</p>
|
|
85
106
|
<div class="tooltip" id="tooltip" aria-hidden="true" hidden></div>
|
|
@@ -435,7 +456,7 @@
|
|
|
435
456
|
}
|
|
436
457
|
|
|
437
458
|
const sourceRoot = sourceDocument.documentElement;
|
|
438
|
-
sourceRoot.querySelectorAll("script, #building-tooltip").forEach((element) => element.remove());
|
|
459
|
+
sourceRoot.querySelectorAll("script, #building-tooltip, #back-link").forEach((element) => element.remove());
|
|
439
460
|
sourceRoot.setAttribute("width", "850");
|
|
440
461
|
sourceRoot.setAttribute("height", "278");
|
|
441
462
|
const sourceHost = document.createElement("div");
|