@a2f0/skyline 0.2.22 → 0.2.24
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/docs/package.md +13 -0
- package/lib/skyline-package.d.ts +3 -1
- package/lib/skyline-package.js +2 -2
- package/lib/skyline-shell.d.ts +4 -1
- package/lib/skyline-shell.js +10 -2
- package/package.json +1 -1
- package/site/index.html +14 -1
- package/site/skyline-3d.html +2 -0
- package/site/skyline-shell.js +10 -2
- package/site/skyline-viewer.js +4 -1
- package/site/study.css +15 -0
- package/site/viewer-page.js +2 -0
- package/site/viewer.css +13 -0
package/docs/package.md
CHANGED
|
@@ -10,6 +10,19 @@ To start an embed in sunny-day colour, pass `colour: true` to `mountSkyline` alo
|
|
|
10
10
|
Omitting `colour` keeps the existing greyscale default. The setting belongs to that instance;
|
|
11
11
|
it does not use storage or affect another embed. The standalone viewer accepts `?colour=1`.
|
|
12
12
|
|
|
13
|
+
Pass `theme: "light"` or `theme: "dark"` from the host's active theme to choose the
|
|
14
|
+
loading backdrop, animated outline, error palette and bottom control bar.
|
|
15
|
+
The loading palette applies synchronously,
|
|
16
|
+
before the viewer's code or stylesheets load. After the first frame, the scene
|
|
17
|
+
supplies its own sky; returning to greyscale restores the black sky and stars.
|
|
18
|
+
The control bar keeps the host's theme, including its buttons, focus outlines,
|
|
19
|
+
time slider and celebration badges, independently of the scene's time or materials.
|
|
20
|
+
Without `theme`, `colour: true`
|
|
21
|
+
boots light and greyscale boots dark. The theme does not select scene materials or
|
|
22
|
+
time of day: for a light host, pass both `theme: "light"` and `colour: true`.
|
|
23
|
+
Destroy and remount when the host theme changes. The standalone viewer also accepts
|
|
24
|
+
`?theme=light&colour=1` (or `?theme=dark`); each instance keeps its own setting.
|
|
25
|
+
|
|
13
26
|
## Install from npm
|
|
14
27
|
|
|
15
28
|
```sh
|
package/lib/skyline-package.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface SkylineOptions {
|
|
|
18
18
|
readonly controls?: "open" | "closed";
|
|
19
19
|
/** Start in the opt-in sunny-day colour view. Greyscale by default. */
|
|
20
20
|
readonly colour?: boolean;
|
|
21
|
+
/** Loading backdrop and trace. Defaults to light with colour: true, dark otherwise. */
|
|
22
|
+
readonly theme?: "light" | "dark";
|
|
21
23
|
/**
|
|
22
24
|
* Show the OpenStreetMap credit with the 3D scene's controls. Shown by default. OpenStreetMap's
|
|
23
25
|
* licence requires that credit wherever its data appears, so hide it only when the host page
|
|
@@ -38,4 +40,4 @@ export interface SkylineOptions {
|
|
|
38
40
|
* size and placement: give it an explicit height. Call `destroy` when the hosting component
|
|
39
41
|
* leaves.
|
|
40
42
|
*/
|
|
41
|
-
export declare function mountSkyline(container: HTMLElement, { assetsUrl, title, controls, colour, attribution, three }: SkylineOptions): SkylineInstance;
|
|
43
|
+
export declare function mountSkyline(container: HTMLElement, { assetsUrl, title, controls, colour, theme, attribution, three }: SkylineOptions): SkylineInstance;
|
package/lib/skyline-package.js
CHANGED
|
@@ -8,10 +8,10 @@ import { mountViewer } from "./skyline-shell.js";
|
|
|
8
8
|
* size and placement: give it an explicit height. Call `destroy` when the hosting component
|
|
9
9
|
* leaves.
|
|
10
10
|
*/
|
|
11
|
-
export function mountSkyline(container, { assetsUrl, title = "Interactive Chicago skyline", controls = "closed", colour = false, attribution = true, three }) {
|
|
11
|
+
export function mountSkyline(container, { assetsUrl, title = "Interactive Chicago skyline", controls = "closed", colour = false, theme, attribution = true, three }) {
|
|
12
12
|
if (controls !== "open" && controls !== "closed")
|
|
13
13
|
throw new TypeError('Skyline controls must be "open" or "closed".');
|
|
14
14
|
if (typeof colour !== "boolean")
|
|
15
15
|
throw new TypeError("Skyline colour must be a boolean.");
|
|
16
|
-
return mountViewer(container, { assetsUrl, title, controls, colour, attribution, navigation: false, page: false, three });
|
|
16
|
+
return mountViewer(container, { assetsUrl, title, controls, colour, theme, attribution, navigation: false, page: false, three });
|
|
17
17
|
}
|
package/lib/skyline-shell.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface ViewerContext {
|
|
|
13
13
|
readonly controls: "open" | "closed";
|
|
14
14
|
/** Start the scene and its building details in sunny-day colour. */
|
|
15
15
|
readonly colour?: boolean;
|
|
16
|
+
/** Loading palette and control bar; defaults to light for colour, dark otherwise. */
|
|
17
|
+
readonly theme: "light" | "dark";
|
|
16
18
|
/** Show the OpenStreetMap credit with the scene's controls. */
|
|
17
19
|
readonly attribution: boolean;
|
|
18
20
|
/**
|
|
@@ -56,7 +58,8 @@ export interface SkylineInstance {
|
|
|
56
58
|
*/
|
|
57
59
|
destroy(): void;
|
|
58
60
|
}
|
|
59
|
-
export interface ViewerOptions extends Omit<ViewerContext, "assets" | "signal"> {
|
|
61
|
+
export interface ViewerOptions extends Omit<ViewerContext, "assets" | "signal" | "theme"> {
|
|
62
|
+
readonly theme?: "light" | "dark" | undefined;
|
|
60
63
|
/** The assets' directory; relative URLs resolve against the container's document. */
|
|
61
64
|
readonly assetsUrl: string | URL;
|
|
62
65
|
/** The viewer's accessible name: `element` is a region with this label. */
|
package/lib/skyline-shell.js
CHANGED
|
@@ -33,7 +33,7 @@ function loaded(link) {
|
|
|
33
33
|
// mirroring the viewer's layout, and the rest sizes it to fill the container.
|
|
34
34
|
const HOST_STYLE = "all:initial;direction:ltr;display:block;position:relative;isolation:isolate;width:100%;height:100%;overflow:hidden";
|
|
35
35
|
// The failure message's own look, since the viewer's stylesheets may be what failed.
|
|
36
|
-
const FAILURE_STYLE = "box-sizing:border-box;height:100%;margin:0;padding:24px;background
|
|
36
|
+
const FAILURE_STYLE = "box-sizing:border-box;height:100%;margin:0;padding:24px;background:inherit;color:inherit;font:12px/1.5 ui-monospace,monospace";
|
|
37
37
|
/** Shows why the viewer couldn't start, in place of the viewer. */
|
|
38
38
|
function showFailure(root, error) {
|
|
39
39
|
const message = root.ownerDocument.createElement("p");
|
|
@@ -49,8 +49,16 @@ function showFailure(root, error) {
|
|
|
49
49
|
export function mountViewer(container, { assetsUrl, title, ...options }) {
|
|
50
50
|
const document = container.ownerDocument;
|
|
51
51
|
const assets = assetsBase(document, assetsUrl);
|
|
52
|
+
const theme = options.theme === undefined ? (options.colour ? "light" : "dark") : options.theme;
|
|
53
|
+
if (theme !== "light" && theme !== "dark")
|
|
54
|
+
throw new TypeError('Skyline theme must be "light" or "dark".');
|
|
52
55
|
const element = document.createElement("div");
|
|
53
56
|
element.style.cssText = HOST_STYLE;
|
|
57
|
+
// Paint before any assets arrive, including when a stylesheet fails to load.
|
|
58
|
+
// Keep the boot palette in sync with viewer.css and index.html.
|
|
59
|
+
element.style.colorScheme = theme;
|
|
60
|
+
element.style.backgroundColor = theme === "light" ? "#f5f5f5" : "#000";
|
|
61
|
+
element.style.color = theme === "light" ? "#444" : "#aaa";
|
|
54
62
|
element.setAttribute("role", "region");
|
|
55
63
|
element.setAttribute("aria-label", title);
|
|
56
64
|
element.setAttribute("aria-busy", "true");
|
|
@@ -70,7 +78,7 @@ export function mountViewer(container, { assetsUrl, title, ...options }) {
|
|
|
70
78
|
Promise.resolve(options.three).catch(() => { });
|
|
71
79
|
const controller = new AbortController();
|
|
72
80
|
let destroyed = false, mounted = false;
|
|
73
|
-
const context = { ...options, assets, signal: controller.signal };
|
|
81
|
+
const context = { ...options, theme, assets, signal: controller.signal };
|
|
74
82
|
const ready = new Promise((resolve, reject) => {
|
|
75
83
|
Promise.all([importViewer(new URL(VIEWER, assets).href), ...stylesheets.map(loaded)])
|
|
76
84
|
.then(async ([viewer]) => {
|
package/package.json
CHANGED
package/site/index.html
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
height: 100%;
|
|
26
26
|
margin: 0;
|
|
27
27
|
overflow: hidden;
|
|
28
|
-
background:
|
|
28
|
+
background: inherit;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
.notice {
|
|
@@ -35,7 +35,20 @@
|
|
|
35
35
|
color: #aaa;
|
|
36
36
|
font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
37
37
|
}
|
|
38
|
+
/* Keep the boot palette in sync with skyline-shell.ts and viewer.css. */
|
|
39
|
+
:root[data-theme="light"] { color-scheme: light; background: #f5f5f5; }
|
|
40
|
+
:root[data-theme="light"] .notice { color: #444; }
|
|
38
41
|
</style>
|
|
42
|
+
<script>
|
|
43
|
+
// Apply the backdrop before first paint, while the viewer modules load.
|
|
44
|
+
{
|
|
45
|
+
const settings = new URLSearchParams(location.search);
|
|
46
|
+
const light = settings.get("theme") === "light" ||
|
|
47
|
+
(settings.get("theme") !== "dark" && settings.get("colour") === "1");
|
|
48
|
+
document.documentElement.dataset.theme = light ? "light" : "dark";
|
|
49
|
+
document.querySelector('meta[name="theme-color"]').content = light ? "#f5f5f5" : "#000000";
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
39
52
|
</head>
|
|
40
53
|
<body>
|
|
41
54
|
<!-- The viewer renders into a shadow root inside this element, as in a host's page. -->
|
package/site/skyline-3d.html
CHANGED
|
@@ -92,6 +92,8 @@
|
|
|
92
92
|
// markup in its shadow root, applies its own options the same way (skyline-viewer.ts).
|
|
93
93
|
(() => {
|
|
94
94
|
const settings = new URLSearchParams(location.search);
|
|
95
|
+
const theme = settings.get("theme");
|
|
96
|
+
document.body.dataset.theme = theme === "light" || (theme !== "dark" && settings.get("colour") === "1") ? "light" : "dark";
|
|
95
97
|
if (settings.get("controls") === "open") {
|
|
96
98
|
const toggle = document.querySelector("#menu-toggle");
|
|
97
99
|
toggle.setAttribute("aria-expanded", "true");
|
package/site/skyline-shell.js
CHANGED
|
@@ -33,7 +33,7 @@ function loaded(link) {
|
|
|
33
33
|
// mirroring the viewer's layout, and the rest sizes it to fill the container.
|
|
34
34
|
const HOST_STYLE = "all:initial;direction:ltr;display:block;position:relative;isolation:isolate;width:100%;height:100%;overflow:hidden";
|
|
35
35
|
// The failure message's own look, since the viewer's stylesheets may be what failed.
|
|
36
|
-
const FAILURE_STYLE = "box-sizing:border-box;height:100%;margin:0;padding:24px;background
|
|
36
|
+
const FAILURE_STYLE = "box-sizing:border-box;height:100%;margin:0;padding:24px;background:inherit;color:inherit;font:12px/1.5 ui-monospace,monospace";
|
|
37
37
|
/** Shows why the viewer couldn't start, in place of the viewer. */
|
|
38
38
|
function showFailure(root, error) {
|
|
39
39
|
const message = root.ownerDocument.createElement("p");
|
|
@@ -49,8 +49,16 @@ function showFailure(root, error) {
|
|
|
49
49
|
export function mountViewer(container, { assetsUrl, title, ...options }) {
|
|
50
50
|
const document = container.ownerDocument;
|
|
51
51
|
const assets = assetsBase(document, assetsUrl);
|
|
52
|
+
const theme = options.theme === undefined ? (options.colour ? "light" : "dark") : options.theme;
|
|
53
|
+
if (theme !== "light" && theme !== "dark")
|
|
54
|
+
throw new TypeError('Skyline theme must be "light" or "dark".');
|
|
52
55
|
const element = document.createElement("div");
|
|
53
56
|
element.style.cssText = HOST_STYLE;
|
|
57
|
+
// Paint before any assets arrive, including when a stylesheet fails to load.
|
|
58
|
+
// Keep the boot palette in sync with viewer.css and index.html.
|
|
59
|
+
element.style.colorScheme = theme;
|
|
60
|
+
element.style.backgroundColor = theme === "light" ? "#f5f5f5" : "#000";
|
|
61
|
+
element.style.color = theme === "light" ? "#444" : "#aaa";
|
|
54
62
|
element.setAttribute("role", "region");
|
|
55
63
|
element.setAttribute("aria-label", title);
|
|
56
64
|
element.setAttribute("aria-busy", "true");
|
|
@@ -70,7 +78,7 @@ export function mountViewer(container, { assetsUrl, title, ...options }) {
|
|
|
70
78
|
Promise.resolve(options.three).catch(() => { });
|
|
71
79
|
const controller = new AbortController();
|
|
72
80
|
let destroyed = false, mounted = false;
|
|
73
|
-
const context = { ...options, assets, signal: controller.signal };
|
|
81
|
+
const context = { ...options, theme, assets, signal: controller.signal };
|
|
74
82
|
const ready = new Promise((resolve, reject) => {
|
|
75
83
|
Promise.all([importViewer(new URL(VIEWER, assets).href), ...stylesheets.map(loaded)])
|
|
76
84
|
.then(async ([viewer]) => {
|
package/site/skyline-viewer.js
CHANGED
|
@@ -22,7 +22,7 @@ function element(document, tag, properties = {}, attributes = {}) {
|
|
|
22
22
|
created.setAttribute(name, value);
|
|
23
23
|
return created;
|
|
24
24
|
}
|
|
25
|
-
export function mount(root, { assets, signal, controls, colour, attribution, navigation, page, three }) {
|
|
25
|
+
export function mount(root, { assets, signal, controls, colour, theme, attribution, navigation, page, three }) {
|
|
26
26
|
const document = root.ownerDocument;
|
|
27
27
|
const host = root.host;
|
|
28
28
|
const listening = { signal };
|
|
@@ -35,10 +35,12 @@ export function mount(root, { assets, signal, controls, colour, attribution, nav
|
|
|
35
35
|
const sceneMarkup = loadMarkup(new URL("skyline-3d.html", assets), signal);
|
|
36
36
|
sceneMarkup.catch(() => { });
|
|
37
37
|
const viewer = element(document, "div", { className: "skyline-viewer" });
|
|
38
|
+
viewer.dataset["theme"] = theme;
|
|
38
39
|
if (!navigation)
|
|
39
40
|
viewer.dataset["embedded"] = "";
|
|
40
41
|
const stars = element(document, "div", { className: "stars", id: "stars" }, { "aria-hidden": "true" });
|
|
41
42
|
const scene = element(document, "div", { className: "skyline-3d", id: "skyline-3d-scene" });
|
|
43
|
+
scene.dataset["theme"] = theme;
|
|
42
44
|
const drawing = element(document, "img", { className: "scene drawing", id: "drawing", alt: "Original Chicago skyline", hidden: true });
|
|
43
45
|
const originalReturn = element(document, "button", { type: "button", className: "original-return", id: "return-skyline-3d", title: home.title, hidden: true, textContent: home.label });
|
|
44
46
|
const embeddedOriginal = element(document, "button", { type: "button", className: "original-return", id: "embedded-original", title: toggles.original.title, hidden: true, textContent: toggles.original.label });
|
|
@@ -52,6 +54,7 @@ export function mount(root, { assets, signal, controls, colour, attribution, nav
|
|
|
52
54
|
const frames = {};
|
|
53
55
|
const toolbar = navigation ? createToolbar() : null;
|
|
54
56
|
function updateView() {
|
|
57
|
+
viewer.toggleAttribute("data-booting", sceneUnavailable && mode === "3d");
|
|
55
58
|
scene.hidden = mode !== "3d";
|
|
56
59
|
if (mode === "original" && !drawing.getAttribute("src"))
|
|
57
60
|
drawing.src = new URL("skyline-original-fit.svg", assets).href;
|
package/site/study.css
CHANGED
|
@@ -185,6 +185,21 @@ body.skyline-3d { height: 100vh; height: 100dvh; }
|
|
|
185
185
|
.control-bar .menu-toggle:hover { color: #fff; background: #ffffff14; }
|
|
186
186
|
.menu-toggle svg { width: 20px; height: 20px; fill: currentColor; transition: rotate 0.35s cubic-bezier(0.2, 0.7, 0.2, 1); }
|
|
187
187
|
.menu-toggle[aria-expanded="true"] svg { rotate: 30deg; }
|
|
188
|
+
/* The host theme owns the bar throughout loading and interaction, independently
|
|
189
|
+
of the scene's materials or time of day. The standalone page uses the same
|
|
190
|
+
theme/colour defaults as mountSkyline. Keep the other studies' palette intact. */
|
|
191
|
+
.skyline-3d[data-theme="light"] .control-bar { color-scheme: light; background: #f5f5f5; border-color: #bbb; color: #444; }
|
|
192
|
+
.skyline-3d[data-theme="light"] .control-bar button { --control-button-background: #fff; background: var(--control-button-background); border-color: #888; color: #444; -webkit-tap-highlight-color: #00000022; }
|
|
193
|
+
.skyline-3d[data-theme="light"] .control-bar button:is(:hover, [aria-pressed="true"]) { --control-button-background: #e0e0e0; border-color: #666; color: #111; }
|
|
194
|
+
.skyline-3d[data-theme="light"] .control-bar :focus-visible { outline-color: #111; }
|
|
195
|
+
.skyline-3d[data-theme="light"] .time-controls :is(label, output) { color: #444; }
|
|
196
|
+
.skyline-3d[data-theme="light"] :is(.time-controls #time-date, .celebration-caption) { color: #666; }
|
|
197
|
+
.skyline-3d[data-theme="light"] .time-controls input { accent-color: #666; }
|
|
198
|
+
.skyline-3d[data-theme="light"] .celebration-controls .logo-cut { stroke: var(--control-button-background); }
|
|
199
|
+
.skyline-3d[data-theme="light"] .celebration-controls button[aria-pressed="true"] { border-color: #666; box-shadow: inset 0 -2px #444; }
|
|
200
|
+
.skyline-3d[data-theme="light"] .celebration-controls button[aria-pressed="mixed"] { color: #333; border-color: #666; }
|
|
201
|
+
.skyline-3d[data-theme="light"] .control-bar .menu-toggle { background: none; color: #666; }
|
|
202
|
+
.skyline-3d[data-theme="light"] .control-bar .menu-toggle:hover { background: #00000014; color: #111; }
|
|
188
203
|
/* Compact badges leave room for the time slider. Smaller screens retain 36px
|
|
189
204
|
touch targets. */
|
|
190
205
|
@container skyline (min-width: 1440px) {
|
package/site/viewer-page.js
CHANGED
|
@@ -6,6 +6,7 @@ import { mountViewer } from "./skyline-shell.js";
|
|
|
6
6
|
// bar open or folded; and `?attribution=hidden` leaves out the OpenStreetMap credit. The site
|
|
7
7
|
// starts the bar open, to show what the scene can do; embedded, it starts folded behind its
|
|
8
8
|
// star, as `mountSkyline` does.
|
|
9
|
+
// `?colour=1` starts in daylight; `?theme=light` or `?theme=dark` overrides the boot palette.
|
|
9
10
|
const settings = new URLSearchParams(location.search);
|
|
10
11
|
const embedded = settings.get("embed") === "1";
|
|
11
12
|
const controls = settings.get("controls");
|
|
@@ -15,6 +16,7 @@ mountViewer(document.querySelector("#viewer"), {
|
|
|
15
16
|
controls: controls === "open" || controls === "closed" ? controls : embedded ? "closed" : "open",
|
|
16
17
|
attribution: settings.get("attribution") !== "hidden",
|
|
17
18
|
colour: settings.get("colour") === "1",
|
|
19
|
+
theme: document.documentElement.dataset["theme"] === "light" ? "light" : "dark",
|
|
18
20
|
navigation: !embedded,
|
|
19
21
|
page: true,
|
|
20
22
|
});
|
package/site/viewer.css
CHANGED
|
@@ -35,6 +35,19 @@
|
|
|
35
35
|
pointer-events: none;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/* Keep this boot palette in sync with skyline-shell.ts and index.html. It gives
|
|
39
|
+
way to the scene's own sky at the first frame. The original drawing always
|
|
40
|
+
keeps its dark sky, including when opened during loading or after a failure. */
|
|
41
|
+
.skyline-viewer[data-theme="light"][data-booting] { color-scheme: light; background: #f5f5f5; color: #444; }
|
|
42
|
+
.skyline-viewer[data-theme="light"][data-booting] > .stars { visibility: hidden; }
|
|
43
|
+
.skyline-viewer[data-theme="light"][data-booting] .loading { color: #444; }
|
|
44
|
+
.skyline-viewer[data-theme="light"][data-booting] .skyline-trace { stroke: #555; }
|
|
45
|
+
.skyline-viewer[data-theme="light"][data-booting] .skyline-trace-tip { stroke: #222; filter: drop-shadow(0 0 9px #22222266); }
|
|
46
|
+
.skyline-viewer[data-theme="light"][data-booting] .controls { color: #444; }
|
|
47
|
+
.skyline-viewer[data-theme="light"][data-booting] .motion-status { color: #555; }
|
|
48
|
+
.skyline-viewer[data-theme="light"][data-booting] .controls :is(button, a):is(:hover, :focus-visible, [aria-pressed="true"]) { color: #111; }
|
|
49
|
+
.skyline-viewer[data-theme="light"][data-booting] .controls :focus-visible { outline-color: #111; }
|
|
50
|
+
|
|
38
51
|
.stars svg {
|
|
39
52
|
display: block;
|
|
40
53
|
width: 100%;
|