pixel-daylight-switch-gsvegetable 2.2.0 → 3.0.1
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/INTEGRATIONS.md +139 -0
- package/README.ja.md +54 -2
- package/README.md +77 -2
- package/README.zh-CN.md +54 -2
- package/SCENE_PLUGINS.md +94 -0
- package/app.js +90 -3
- package/browser.mjs +20 -0
- package/examples/scenes/floating-island.v2.json +152 -0
- package/examples/scenes/sakura-evening.json +38 -0
- package/index.cjs +9 -2
- package/index.d.ts +65 -0
- package/index.mjs +4 -0
- package/llms.txt +90 -86
- package/package.json +26 -8
- package/scene-plugin.js +173 -0
- package/schemas/scene-manifest.v1.schema.json +117 -0
- package/schemas/scene-manifest.v2.schema.json +229 -0
package/INTEGRATIONS.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Framework integration recipes
|
|
2
|
+
|
|
3
|
+
The core package has no React or Vue runtime dependency. Use the same HTML root and mount/unmount lifecycle from your framework. The component owns its internal canvas and interaction elements; your framework owns the surrounding container.
|
|
4
|
+
|
|
5
|
+
## React
|
|
6
|
+
|
|
7
|
+
Install the core package:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install pixel-daylight-switch-gsvegetable
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```jsx
|
|
14
|
+
import { useEffect, useRef } from 'react';
|
|
15
|
+
import { mount, unmount } from 'pixel-daylight-switch-gsvegetable';
|
|
16
|
+
|
|
17
|
+
export function DaylightSwitch({ scene = 'countryside', weather = 'default' }) {
|
|
18
|
+
const rootRef = useRef(null);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const root = rootRef.current;
|
|
22
|
+
if (!root) return undefined;
|
|
23
|
+
|
|
24
|
+
mount(root);
|
|
25
|
+
return () => unmount(root);
|
|
26
|
+
}, [scene, weather]);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div style={{ width: 'min(100%, 800px)', marginInline: 'auto' }}>
|
|
30
|
+
<div
|
|
31
|
+
ref={rootRef}
|
|
32
|
+
data-ld-part="toggle-daylight"
|
|
33
|
+
data-scene={scene}
|
|
34
|
+
data-weather={weather}
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The effect cleans up the previous instance before applying changed scene or weather attributes, so the renderer always reads the latest configuration.
|
|
42
|
+
|
|
43
|
+
## Vue 3
|
|
44
|
+
|
|
45
|
+
```vue
|
|
46
|
+
<script setup>
|
|
47
|
+
import { onMounted, onBeforeUnmount, ref } from 'vue';
|
|
48
|
+
import { mount, unmount } from 'pixel-daylight-switch-gsvegetable';
|
|
49
|
+
|
|
50
|
+
const root = ref(null);
|
|
51
|
+
onMounted(() => mount(root.value));
|
|
52
|
+
onBeforeUnmount(() => {
|
|
53
|
+
if (root.value) unmount(root.value);
|
|
54
|
+
});
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<template>
|
|
58
|
+
<div class="daylight-switch-wrap">
|
|
59
|
+
<div
|
|
60
|
+
ref="root"
|
|
61
|
+
data-ld-part="toggle-daylight"
|
|
62
|
+
data-scene="japanese-street"
|
|
63
|
+
data-weather="default"
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
</template>
|
|
67
|
+
|
|
68
|
+
<style scoped>
|
|
69
|
+
.daylight-switch-wrap {
|
|
70
|
+
width: min(100%, 800px);
|
|
71
|
+
margin-inline: auto;
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Plain HTML / CDN
|
|
77
|
+
|
|
78
|
+
For one default switch with no build step, use the one-line embed:
|
|
79
|
+
|
|
80
|
+
```html
|
|
81
|
+
<script src="https://gsvegetable.github.io/pixel-daylight-switch/embed.js"></script>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Set supported options on the script tag when you want a particular scene or weather:
|
|
85
|
+
|
|
86
|
+
```html
|
|
87
|
+
<script src="https://gsvegetable.github.io/pixel-daylight-switch/embed.js" data-scene="seaside" data-weather="snow" data-start-night="true"></script>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
For custom configuration, place a root element where you want it and load the runtime:
|
|
91
|
+
|
|
92
|
+
```html
|
|
93
|
+
<div style="width:min(100%, 800px);margin-inline:auto">
|
|
94
|
+
<div
|
|
95
|
+
data-ld-part="toggle-daylight"
|
|
96
|
+
data-scene="seaside"
|
|
97
|
+
data-weather="snow"
|
|
98
|
+
data-start-night="true"
|
|
99
|
+
></div>
|
|
100
|
+
</div>
|
|
101
|
+
<script src="https://gsvegetable.github.io/pixel-daylight-switch/app.js"></script>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The runtime injects component-scoped styles. Do not import `style.css` just to use the component; that file also contains styles for the repository's standalone demo page.
|
|
105
|
+
|
|
106
|
+
## Community scene plugins
|
|
107
|
+
|
|
108
|
+
Register a JSON scene manifest before mounting a root. The package entry point exports `registerScene`, `validateScene`, `getScene` and `listScenes` alongside `mount` and `unmount`.
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
import PixelDaylightSwitch, { registerScene } from 'pixel-daylight-switch-gsvegetable';
|
|
112
|
+
import scene from './my-scene.json';
|
|
113
|
+
|
|
114
|
+
const unregister = registerScene(scene);
|
|
115
|
+
const root = document.querySelector('[data-ld-part="toggle-daylight"]');
|
|
116
|
+
root.dataset.scene = scene.id;
|
|
117
|
+
PixelDaylightSwitch.mount(root);
|
|
118
|
+
|
|
119
|
+
// Before removing a dynamically registered scene:
|
|
120
|
+
PixelDaylightSwitch.unmount(root);
|
|
121
|
+
unregister();
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
For CDN usage, load `scene-plugin.js` before `app.js`, then call `PixelDaylightSwitchScenePlugins.registerScene(manifest)` before mounting. The embed loader loads both scripts in the correct order for built-in scenes; custom manifests still need to be registered by the host page.
|
|
125
|
+
|
|
126
|
+
Manifest v1 is data-only: a bounded pixel matrix, named palettes and a 24-hour palette timeline. See [the schema](schemas/scene-manifest.v1.schema.json) and [the example](examples/scenes/sakura-evening.json). Do not place executable code, remote script URLs or untrusted HTML in a manifest.
|
|
127
|
+
|
|
128
|
+
## Lifecycle and events
|
|
129
|
+
|
|
130
|
+
- Call `mount(root)` after the root is attached to the DOM.
|
|
131
|
+
- Call `unmount(root)` before the framework removes the root.
|
|
132
|
+
- Mounting the same root repeatedly is safe; it is idempotent.
|
|
133
|
+
- Listen for `daylight:change` on the root to synchronize application state. The event's `detail.night` property is a boolean.
|
|
134
|
+
- The root must be allowed to measure its width. A width of `100%` inside a parent with a real layout width is the usual choice.
|
|
135
|
+
- The component keeps its intrinsic 1.6439 aspect ratio and scales to the available width.
|
|
136
|
+
|
|
137
|
+
## Configuration
|
|
138
|
+
|
|
139
|
+
See [README.md](README.md) for the complete list of `data-*` attributes, CSS custom properties, rendering behavior and supported scene identifiers.
|
package/README.ja.md
CHANGED
|
@@ -23,6 +23,7 @@ Pixel Daylight Switch は、ドラッグ操作で昼・夕暮れ・夜を切り
|
|
|
23
23
|
| コンポーネントを見る | [Preview](https://gsvegetable.github.io/pixel-daylight-switch/preview.html) |
|
|
24
24
|
| Web サイトに埋め込む | [embed.js](embed.js) |
|
|
25
25
|
| JavaScript プロジェクトで使う | [npm](https://www.npmjs.com/package/pixel-daylight-switch-gsvegetable) |
|
|
26
|
+
| React / Vue の導入例 | [フレームワーク統合ガイド](INTEGRATIONS.md) |
|
|
26
27
|
| 実装を読む | [app.js](app.js) · [style.css](style.css) |
|
|
27
28
|
| モバイルビルド | [ビルド情報](builds/README.md) · [GitHub Actions](https://github.com/GSvegetable/pixel-daylight-switch/actions/workflows/mobile-build.yml) |
|
|
28
29
|
| テスト結果 | [CI ワークフロー](https://github.com/GSvegetable/pixel-daylight-switch/actions/workflows/ci.yml) |
|
|
@@ -61,7 +62,13 @@ HTML にマウント要素を配置し、ランタイムを読み込みます。
|
|
|
61
62
|
<script src="https://gsvegetable.github.io/pixel-daylight-switch/embed.js"></script>
|
|
62
63
|
```
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
script タグにシーンや天候を直接指定することもできます。
|
|
66
|
+
|
|
67
|
+
```html
|
|
68
|
+
<script src="https://gsvegetable.github.io/pixel-daylight-switch/embed.js" data-scene="seaside" data-weather="snow" data-start-night="true"></script>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
レスポンシブなインスタンスを 1 つ作成してランタイムを読み込みます。スタイルはコンポーネント内に限定され、ホストページの `body` や `main` などのグローバル要素を変更しません。script タグに `data-scene`、`data-weather`、`data-start-night` などを直接指定できます。複数インスタンスや配置の細かな制御には、上記のマウント方式を使用してください。
|
|
65
72
|
|
|
66
73
|
## npm からインストール
|
|
67
74
|
|
|
@@ -89,6 +96,51 @@ const PixelDaylightSwitch = require('pixel-daylight-switch-gsvegetable');
|
|
|
89
96
|
|
|
90
97
|
ESM と CommonJS の両方を提供します。SSR 環境でもインポートできますが、`mount()` はブラウザー上で対象要素が存在してから呼び出してください。
|
|
91
98
|
|
|
99
|
+
## コミュニティシーンプラグイン(v3.0.0)
|
|
100
|
+
|
|
101
|
+
3.0.0 では JSON Manifest によるカスタムピクセルワールドの登録に対応しました。初めての方はピクセルマップをそのまま使い、昼夜パレットの色だけを変更できます。上級者はピクセルマップと時間パレットを自由に設計できます。プラグインは宣言型データのみを受け取り、レンダラー内で第三者 JavaScript を実行しません。
|
|
102
|
+
|
|
103
|
+
**ブラウザーで使用:**
|
|
104
|
+
|
|
105
|
+
```html
|
|
106
|
+
<script src="https://gsvegetable.github.io/pixel-daylight-switch/scene-plugin.js"></script>
|
|
107
|
+
<script src="https://gsvegetable.github.io/pixel-daylight-switch/app.js"></script>
|
|
108
|
+
<div data-ld-part="toggle-daylight" data-scene="sakura-evening"></div>
|
|
109
|
+
<script>
|
|
110
|
+
PixelDaylightSwitch.registerScene(sceneManifest);
|
|
111
|
+
</script>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
コンポーネントをマウントする前に Manifest を登録してください。npm の場合:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
import PixelDaylightSwitch, { registerScene } from 'pixel-daylight-switch-gsvegetable';
|
|
118
|
+
import myScene from './my-scene.json';
|
|
119
|
+
|
|
120
|
+
const unregister = registerScene(myScene);
|
|
121
|
+
const root = document.querySelector('[data-ld-part="toggle-daylight"]');
|
|
122
|
+
root.dataset.scene = myScene.id;
|
|
123
|
+
PixelDaylightSwitch.mount(root);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Manifest v1 はシーン ID、作者・ライセンス、ピクセルキャンバス、文字ベースのピクセルマップ、複数の昼夜パレット、24 時間のタイムラインで構成されます。キャンバスは最大 192 × 112 ピクセルです。すべてのピクセル記号に各パレットの色を定義する必要があります。時間ノード間の色はレンダラーが補間します。
|
|
127
|
+
|
|
128
|
+
- [プラグイン開発ガイド](SCENE_PLUGINS.md)
|
|
129
|
+
- [Manifest v1 JSON Schema](schemas/scene-manifest.v1.schema.json)
|
|
130
|
+
- [Sakura Evening サンプル](examples/scenes/sakura-evening.json)
|
|
131
|
+
|
|
132
|
+
### Manifest v2:レイヤー付きピクセルワールド(v3.0.1)
|
|
133
|
+
|
|
134
|
+
3.0.1 では Manifest v1 を変更せずに v2 を追加しました。v2 は最大 24 個の再利用可能なピクセルアセットと、最大 16 個の順序付きレイヤーに対応します。レイヤーはシーン座標に配置でき、drift または bob の宣言型モーショントラックで横・縦方向に動かせます。重ねるアセット内の . は透明として扱い、それ以外の文字は昼夜パレットで着色されます。
|
|
135
|
+
|
|
136
|
+
- [Manifest v2 JSON Schema](schemas/scene-manifest.v2.schema.json)
|
|
137
|
+
- [Floating Island レイヤーサンプル](examples/scenes/floating-island.v2.json)
|
|
138
|
+
- [シーンプラグイン開発ガイド](SCENE_PLUGINS.md)
|
|
139
|
+
|
|
140
|
+
v2 のアセットは文字ベースのピクセルマップです。PNG/JPEG の外部ダウンロードには対応しません。これによりピクセル表現と昼夜の色変化を一貫させ、外部画像の CORS や任意コード実行を避けます。
|
|
141
|
+
|
|
142
|
+
5 つの組み込みシーンは従来の描画・アニメーション実装を維持します。
|
|
143
|
+
|
|
92
144
|
## 設定属性
|
|
93
145
|
|
|
94
146
|
すべて任意の設定で、マウント要素に指定します。省略時または無効な値の場合はデフォルト値が使われます。
|
|
@@ -176,7 +228,7 @@ Capacitor 8 アプリには、田園の家、日本の街並み、モダンな
|
|
|
176
228
|
<script src="https://gsvegetable.github.io/pixel-daylight-switch/embed.js"></script>
|
|
177
229
|
```
|
|
178
230
|
|
|
179
|
-
|
|
231
|
+
レスポンシブなデフォルトシーンのスイッチを自動作成してランタイムを読み込みます。デモページ用スタイルは挿入せず、サイトのグローバルスタイルも変更しません。シーンや天気のカスタマイズ、複数インスタンス、配置の制御には、上記の npm API と `data-scene` / `data-weather` 属性を使用してください。
|
|
180
232
|
|
|
181
233
|
### 独自の Android / iOS Capacitor アプリへの統合
|
|
182
234
|
|
package/README.md
CHANGED
|
@@ -23,6 +23,8 @@ Pixel Daylight Switch is a draggable day/night UI component. Move its glass cont
|
|
|
23
23
|
| Inspect the component | [Preview page](https://gsvegetable.github.io/pixel-daylight-switch/preview.html) |
|
|
24
24
|
| Add it to a website with one script | [Embed loader source](embed.js) |
|
|
25
25
|
| Use it in a JavaScript project | [npm package](https://www.npmjs.com/package/pixel-daylight-switch-gsvegetable) |
|
|
26
|
+
| Framework integration recipes | [React and Vue examples](INTEGRATIONS.md) |
|
|
27
|
+
| Scene plugin authoring guide | [Scene plugin guide](SCENE_PLUGINS.md) |
|
|
26
28
|
| Read implementation details | [Runtime](app.js) · [Styles](style.css) |
|
|
27
29
|
| Download mobile builds | [Build notes and limitations](builds/README.md) · [GitHub Actions artifacts](https://github.com/GSvegetable/pixel-daylight-switch/actions/workflows/mobile-build.yml) |
|
|
28
30
|
| Check tests | [CI workflow](https://github.com/GSvegetable/pixel-daylight-switch/actions/workflows/ci.yml) |
|
|
@@ -61,7 +63,13 @@ For a self-inserting embed, add this script where you want the component to appe
|
|
|
61
63
|
<script src="https://gsvegetable.github.io/pixel-daylight-switch/embed.js"></script>
|
|
62
64
|
```
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
Configure the generated instance directly on the script tag:
|
|
67
|
+
|
|
68
|
+
```html
|
|
69
|
+
<script src="https://gsvegetable.github.io/pixel-daylight-switch/embed.js" data-scene="seaside" data-weather="snow" data-start-night="true"></script>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The loader inserts one responsive instance and loads only the component runtime. Its styles are isolated to the component and do not reset or modify your page's `body`, `main`, or other global elements. You can set `data-scene`, `data-weather`, `data-start-night` and the other documented `data-*` options directly on the script tag. For multiple instances or exact placement, use the mount-point approach above.
|
|
65
73
|
|
|
66
74
|
## Install from npm
|
|
67
75
|
|
|
@@ -89,6 +97,73 @@ const PixelDaylightSwitch = require('pixel-daylight-switch-gsvegetable');
|
|
|
89
97
|
|
|
90
98
|
The package exposes ESM and CommonJS entry points. Importing is safe in an SSR environment; call `mount()` only in a browser after the target element exists. The package's published file list is defined in [package.json](package.json).
|
|
91
99
|
|
|
100
|
+
## Community scene plugins (v3.0.0)
|
|
101
|
+
|
|
102
|
+
Version 3 introduces a data-driven scene plugin API. Community scenes use a JSON manifest; they do not execute third-party JavaScript inside the renderer.
|
|
103
|
+
|
|
104
|
+
### Register a scene
|
|
105
|
+
|
|
106
|
+
Load the registry before the renderer when using script tags:
|
|
107
|
+
|
|
108
|
+
```html
|
|
109
|
+
<script src="https://gsvegetable.github.io/pixel-daylight-switch/scene-plugin.js"></script>
|
|
110
|
+
<script src="https://gsvegetable.github.io/pixel-daylight-switch/app.js"></script>
|
|
111
|
+
<div data-ld-part="toggle-daylight" data-scene="sakura-evening"></div>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Register a manifest before mounting the component:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
const unregister = PixelDaylightSwitchScenePlugins.registerScene(sceneManifest);
|
|
118
|
+
PixelDaylightSwitch.mount(document.querySelector('[data-ld-part="toggle-daylight"]'));
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
With npm:
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
import PixelDaylightSwitch, { registerScene } from 'pixel-daylight-switch-gsvegetable';
|
|
125
|
+
import sakura from './sakura-evening.json';
|
|
126
|
+
|
|
127
|
+
registerScene(sakura);
|
|
128
|
+
PixelDaylightSwitch.mount(document.querySelector('[data-ld-part="toggle-daylight"]'));
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Manifest v2 — layered pixel worlds (v3.0.1)
|
|
132
|
+
|
|
133
|
+
Version 3.0.1 extends the community scene format without changing Manifest v1. V2 scenes can define up to 24 reusable pixel-matrix assets and up to 16 ordered layers. A layer places an asset at integer scene coordinates and can optionally animate with a deterministic drift or bob track along either axis. The dot (.) is transparent in overlay assets; all other symbols are color-mapped by the scene palettes.
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
import PixelDaylightSwitch, { registerScene } from 'pixel-daylight-switch-gsvegetable';
|
|
137
|
+
import island from './floating-island.v2.json';
|
|
138
|
+
|
|
139
|
+
registerScene(island);
|
|
140
|
+
const root = document.querySelector('[data-ld-part="toggle-daylight"]');
|
|
141
|
+
root.dataset.scene = island.id;
|
|
142
|
+
PixelDaylightSwitch.mount(root);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- [Manifest v2 JSON Schema](schemas/scene-manifest.v2.schema.json)
|
|
146
|
+
- [Floating Island layered example](examples/scenes/floating-island.v2.json)
|
|
147
|
+
- [Full scene authoring guide](SCENE_PLUGINS.md)
|
|
148
|
+
|
|
149
|
+
V2 assets are native pixel matrices, not downloaded PNG/JPEG files. This keeps the scene pipeline deterministic and avoids remote asset/CORS dependencies or executable plugin code.
|
|
150
|
+
|
|
151
|
+
## Manifest v1
|
|
152
|
+
|
|
153
|
+
A manifest contains a fixed-size character pixel map, named color palettes and a 24-hour palette timeline. The renderer interpolates colors between timeline stops while keeping the same scene geometry.
|
|
154
|
+
|
|
155
|
+
- Canvas limit: 192 × 112 pixels.
|
|
156
|
+
- Pixel rows must match the declared width and height.
|
|
157
|
+
- Every symbol used by the pixel map must have a color in every palette.
|
|
158
|
+
- Colors use `#RGB` or `#RRGGBB`.
|
|
159
|
+
- Timeline stops are strictly increasing and range from 0 to 24 hours.
|
|
160
|
+
- Built-in scene IDs are reserved; community IDs must be unique.
|
|
161
|
+
- The runtime accepts declarative data only. Manifest data is validated and copied on registration; it cannot run scripts.
|
|
162
|
+
|
|
163
|
+
See the [v1 JSON Schema](schemas/scene-manifest.v1.schema.json) and [Sakura Evening example](examples/scenes/sakura-evening.json). The public API is `registerScene(manifest)`, `validateScene(manifest)`, `getScene(id)`, and `listScenes()`. The returned unregister function removes that registration.
|
|
164
|
+
|
|
165
|
+
The first manifest release focuses on palette-driven pixel worlds. Sprite assets, layered tile maps and declarative animation tracks are planned as compatible schema extensions rather than arbitrary executable plugin code.
|
|
166
|
+
|
|
92
167
|
## Configuration
|
|
93
168
|
|
|
94
169
|
Set optional attributes on the mount element. Defaults apply when an attribute is omitted or invalid.
|
|
@@ -176,7 +251,7 @@ For one basic day/night switch, no package installation is required. Paste this
|
|
|
176
251
|
<script src="https://gsvegetable.github.io/pixel-daylight-switch/embed.js"></script>
|
|
177
252
|
```
|
|
178
253
|
|
|
179
|
-
The loader
|
|
254
|
+
The loader creates a responsive default-scene switch and loads the runtime. It does not inject the demo page stylesheet or alter your site's global styles. For custom scenes, weather, multiple instances or full control over placement, use the npm API above and its `data-scene` / `data-weather` attributes.
|
|
180
255
|
|
|
181
256
|
### Integrate with your own Android / iOS Capacitor app
|
|
182
257
|
|
package/README.zh-CN.md
CHANGED
|
@@ -23,6 +23,7 @@ Pixel Daylight Switch 是一个可拖动的昼夜切换 UI 组件。拖动玻璃
|
|
|
23
23
|
| 查看组件预览 | [Preview 页面](https://gsvegetable.github.io/pixel-daylight-switch/preview.html) |
|
|
24
24
|
| 用一个脚本嵌入网站 | [嵌入加载器源码](embed.js) |
|
|
25
25
|
| 在 JavaScript 项目中使用 | [npm 包](https://www.npmjs.com/package/pixel-daylight-switch-gsvegetable) |
|
|
26
|
+
| React / Vue 接入示例 | [框架集成指南](INTEGRATIONS.md) |
|
|
26
27
|
| 阅读核心实现 | [运行时 app.js](app.js) · [样式 style.css](style.css) |
|
|
27
28
|
| 获取移动端构建 | [构建说明与限制](builds/README.md) · [GitHub Actions 构建任务](https://github.com/GSvegetable/pixel-daylight-switch/actions/workflows/mobile-build.yml) |
|
|
28
29
|
| 查看自动化测试 | [CI 工作流](https://github.com/GSvegetable/pixel-daylight-switch/actions/workflows/ci.yml) |
|
|
@@ -61,7 +62,13 @@ Pixel Daylight Switch 是一个可拖动的昼夜切换 UI 组件。拖动玻璃
|
|
|
61
62
|
<script src="https://gsvegetable.github.io/pixel-daylight-switch/embed.js"></script>
|
|
62
63
|
```
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
也可以直接在 script 标签上配置场景和天气:
|
|
66
|
+
|
|
67
|
+
```html
|
|
68
|
+
<script src="https://gsvegetable.github.io/pixel-daylight-switch/embed.js" data-scene="seaside" data-weather="snow" data-start-night="true"></script>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
加载器会自动创建响应式实例并加载运行时。组件样式仅作用于组件本身,不会重置或修改宿主网页的 `body`、`main` 等全局元素。你也可以直接在 script 标签上设置 `data-scene`、`data-weather`、`data-start-night` 等参数。若要创建多个实例或精确控制位置,请使用上面的挂载节点方式。
|
|
65
72
|
|
|
66
73
|
## 通过 npm 安装
|
|
67
74
|
|
|
@@ -89,6 +96,51 @@ const PixelDaylightSwitch = require('pixel-daylight-switch-gsvegetable');
|
|
|
89
96
|
|
|
90
97
|
npm 包提供 ESM 和 CommonJS 入口,可在 SSR 环境中安全导入;只有在浏览器中且目标元素已经存在时,才能调用 `mount()`。npm 发布文件清单以 [package.json](package.json) 为准。
|
|
91
98
|
|
|
99
|
+
## 社区场景插件(v3.0.0)
|
|
100
|
+
|
|
101
|
+
3.0.0 开始支持通过 JSON Manifest 注册自定义像素世界。普通创作者可以保留像素矩阵、只修改昼夜色板;进阶创作者可以自己设计像素矩阵和时间色板。插件只提供声明式数据,不会在渲染器中执行第三方 JavaScript。
|
|
102
|
+
|
|
103
|
+
**浏览器使用:**
|
|
104
|
+
|
|
105
|
+
```html
|
|
106
|
+
<script src="https://gsvegetable.github.io/pixel-daylight-switch/scene-plugin.js"></script>
|
|
107
|
+
<script src="https://gsvegetable.github.io/pixel-daylight-switch/app.js"></script>
|
|
108
|
+
<div data-ld-part="toggle-daylight" data-scene="sakura-evening"></div>
|
|
109
|
+
<script>
|
|
110
|
+
PixelDaylightSwitch.registerScene(sceneManifest);
|
|
111
|
+
</script>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
请在组件挂载前注册 Manifest。使用 npm 时:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
import PixelDaylightSwitch, { registerScene } from 'pixel-daylight-switch-gsvegetable';
|
|
118
|
+
import myScene from './my-scene.json';
|
|
119
|
+
|
|
120
|
+
const unregister = registerScene(myScene);
|
|
121
|
+
const root = document.querySelector('[data-ld-part="toggle-daylight"]');
|
|
122
|
+
root.dataset.scene = myScene.id;
|
|
123
|
+
PixelDaylightSwitch.mount(root);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Manifest v1 包含场景 ID、作者与许可证、像素画布、字符像素矩阵、多个昼夜色板和 24 小时时间节点。画布最大为 192 × 112;每个像素符号都必须在每套色板中定义颜色。渲染器会在相邻时间节点之间插值颜色。
|
|
127
|
+
|
|
128
|
+
- [完整插件开发指南](SCENE_PLUGINS.md)
|
|
129
|
+
- [Manifest v1 JSON Schema](schemas/scene-manifest.v1.schema.json)
|
|
130
|
+
- [樱花黄昏示例](examples/scenes/sakura-evening.json)
|
|
131
|
+
|
|
132
|
+
### Manifest v2:分层像素世界(v3.0.1)
|
|
133
|
+
|
|
134
|
+
3.0.1 在不改变 Manifest v1 的前提下增加了 v2。v2 支持最多 24 个可复用像素素材、最多 16 个按顺序合成的图层。图层可以指定场景坐标,并使用 drift 或 bob 声明式运动轨道沿水平或垂直方向移动。叠加素材中的点号 . 表示透明,其余字符由昼夜色板统一着色。
|
|
135
|
+
|
|
136
|
+
- [Manifest v2 JSON Schema](schemas/scene-manifest.v2.schema.json)
|
|
137
|
+
- [Floating Island 分层场景示例](examples/scenes/floating-island.v2.json)
|
|
138
|
+
- [完整场景插件开发指南](SCENE_PLUGINS.md)
|
|
139
|
+
|
|
140
|
+
当前 v2 素材采用原生字符像素矩阵,而不是远程 PNG/JPEG 图片;这样可以保持像素边缘清晰、昼夜调色一致,也不需要额外的跨域图片加载或执行第三方代码。
|
|
141
|
+
|
|
142
|
+
五个内置场景仍保留原有绘制与动态逻辑。
|
|
143
|
+
|
|
92
144
|
## 配置参数
|
|
93
145
|
|
|
94
146
|
所有参数均为可选项,写在挂载元素上。省略参数或参数值无效时使用默认值。
|
|
@@ -176,7 +228,7 @@ Capacitor 移动端现已提供完整的场景控制界面:可在乡间小屋
|
|
|
176
228
|
<script src="https://gsvegetable.github.io/pixel-daylight-switch/embed.js"></script>
|
|
177
229
|
```
|
|
178
230
|
|
|
179
|
-
|
|
231
|
+
这是一键式基础嵌入,会创建响应式的默认场景开关;不会注入演示页面样式,也不会修改网站全局样式。如果需要自定义场景、天气或多个实例,请使用上面的 npm API(`data-scene`、`data-weather` 等参数)。
|
|
180
232
|
|
|
181
233
|
### 集成到自己的 Android / iOS Capacitor 工程
|
|
182
234
|
|
package/SCENE_PLUGINS.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Scene Plugin Guide
|
|
2
|
+
|
|
3
|
+
Pixel Daylight Switch 3.0.0 introduced a declarative community-scene format. A scene is JSON data, not an executable JavaScript plugin. The renderer validates the manifest and draws the resulting pixel world through the existing refractive-glass pipeline.
|
|
4
|
+
|
|
5
|
+
## Two ways to create a scene
|
|
6
|
+
|
|
7
|
+
**Palette creator:** Start with `examples/scenes/sakura-evening.json`, keep its pixel rows, and change the colors under `palettes`. This is the quickest way to create a distinct atmosphere.
|
|
8
|
+
|
|
9
|
+
**Pixel-world creator:** Edit `canvas` and `pixels` to design your own geometry, then define a color for every symbol in each palette. Each character represents one pixel; rows must all have the declared width.
|
|
10
|
+
|
|
11
|
+
## Register with npm
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install pixel-daylight-switch-gsvegetable
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
import PixelDaylightSwitch, { registerScene } from 'pixel-daylight-switch-gsvegetable';
|
|
19
|
+
import myScene from './my-scene.json';
|
|
20
|
+
|
|
21
|
+
const unregister = registerScene(myScene);
|
|
22
|
+
const root = document.querySelector('[data-ld-part="toggle-daylight"]');
|
|
23
|
+
root.dataset.scene = myScene.id;
|
|
24
|
+
PixelDaylightSwitch.mount(root);
|
|
25
|
+
|
|
26
|
+
// Call when this scene is no longer needed:
|
|
27
|
+
unregister();
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The scene must be registered before its root is mounted. Registering a scene returns an idempotent unregister function.
|
|
31
|
+
|
|
32
|
+
## Register in a browser
|
|
33
|
+
|
|
34
|
+
Load the registry before the renderer:
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<script src="https://gsvegetable.github.io/pixel-daylight-switch/scene-plugin.js"></script>
|
|
38
|
+
<script src="https://gsvegetable.github.io/pixel-daylight-switch/app.js"></script>
|
|
39
|
+
<div data-ld-part="toggle-daylight" data-scene="sakura-evening"></div>
|
|
40
|
+
<script>
|
|
41
|
+
PixelDaylightSwitch.registerScene(sceneManifest);
|
|
42
|
+
</script>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
If the runtime auto-mounts on `DOMContentLoaded`, register the manifest before that event. For deterministic ordering, load both scripts with `defer` and register from a later deferred script, or register before loading `app.js`.
|
|
46
|
+
|
|
47
|
+
The one-line `embed.js` loader loads the registry before the renderer, but it does not fetch or execute arbitrary remote scene scripts. Register your manifest in your own page before the component mounts.
|
|
48
|
+
|
|
49
|
+
## Manifest v1
|
|
50
|
+
|
|
51
|
+
Required fields:
|
|
52
|
+
|
|
53
|
+
- `schemaVersion`: `1` for the original single-matrix format or `2` for layered pixel assets and motion.
|
|
54
|
+
- `id`: unique lowercase ID, 3–64 characters. Built-in scene IDs are reserved.
|
|
55
|
+
- `name`, `author`, `license`: scene metadata.
|
|
56
|
+
- `canvas`: width 1–192 and height 1–112.
|
|
57
|
+
- `pixels`: one printable-ASCII string per row; every row must match the declared width.
|
|
58
|
+
- `palettes`: one or more named maps from single characters to `#RGB` or `#RRGGBB`.
|
|
59
|
+
- `timeCycle`: strictly increasing time stops, beginning at hour 0 and ending at hour 24. Each stop references a defined palette.
|
|
60
|
+
|
|
61
|
+
Every symbol used in `pixels` must have a color in every palette. The renderer interpolates the RGB values between adjacent time stops, including across midnight. Keep the first and last palette compatible to avoid a visible midnight jump.
|
|
62
|
+
|
|
63
|
+
The authoritative machine-readable contract is [schemas/scene-manifest.v1.schema.json](schemas/scene-manifest.v1.schema.json). A complete sample is [examples/scenes/sakura-evening.json](examples/scenes/sakura-evening.json).
|
|
64
|
+
|
|
65
|
+
## Layered scenes and pixel assets (Manifest v2)
|
|
66
|
+
|
|
67
|
+
Version 3.0.1 adds reusable pixel-art assets, compositing layers and deterministic motion tracks. Manifest v1 remains supported without changes; use `schemaVersion: 2` to opt in.
|
|
68
|
+
|
|
69
|
+
A v2 manifest keeps the base `canvas`, `pixels`, `palettes` and `timeCycle`, and adds:
|
|
70
|
+
|
|
71
|
+
- `assets`: up to 24 named pixel matrices (each up to 192 × 112).
|
|
72
|
+
- `layers`: 1–16 ordered placements that reference an asset and specify integer `x` / `y` coordinates.
|
|
73
|
+
- `animation`: optional `drift` or `bob` motion on either axis, with bounded speed, distance and phase.
|
|
74
|
+
|
|
75
|
+
The dot character (`.`) is transparent when an asset is composited as a layer. All other characters are palette symbols, so every palette must define colors for symbols used by the base matrix and every asset. Layers are drawn in array order; later layers appear above earlier ones. Motion is deterministic and uses the scene's animation clock; it does not execute plugin code.
|
|
76
|
+
|
|
77
|
+
Example: [Floating Island v2](examples/scenes/floating-island.v2.json). The v2 contract is [schemas/scene-manifest.v2.schema.json](schemas/scene-manifest.v2.schema.json); the original v1 contract remains at [schemas/scene-manifest.v1.schema.json](schemas/scene-manifest.v1.schema.json).
|
|
78
|
+
|
|
79
|
+
The current asset format is deliberately pixel-matrix based. Arbitrary PNG/JPEG downloads and executable animation callbacks are not part of v3.0.1; this keeps rendering crisp, portable, CSP-friendly and safe for community contributions.
|
|
80
|
+
|
|
81
|
+
## API
|
|
82
|
+
|
|
83
|
+
- `registerScene(manifest)`: validate, snapshot and register a scene; returns an unregister function.
|
|
84
|
+
- `validateScene(manifest)`: return `{ valid, errors }` without registering.
|
|
85
|
+
- `getScene(id)`: return a registered, deeply frozen manifest or `null`.
|
|
86
|
+
- `listScenes()`: return a new array of registered manifests.
|
|
87
|
+
|
|
88
|
+
Registration rejects duplicate IDs, built-in IDs, malformed dimensions, invalid colors, missing palette symbols, unsupported manifest fields and invalid timelines. Registered manifests are copied and deeply frozen so later edits to the original object cannot silently alter a running scene.
|
|
89
|
+
|
|
90
|
+
## Scope and safety
|
|
91
|
+
|
|
92
|
+
Manifest v1 supports palette-driven pixel maps; Manifest v2 adds bounded pixel-matrix assets, ordered layers and declarative drift/bob tracks. Neither version executes plugin JavaScript, evaluates expressions, loads arbitrary code or downloads external images. Raster image imports and tile-atlas tooling are outside the v3.0.1 scope.
|
|
93
|
+
|
|
94
|
+
All five built-in scenes continue to use their original hand-authored painters and scene-specific live behavior. Community scenes use the data-driven renderer and do not replace or mutate built-in scene definitions.
|
package/app.js
CHANGED
|
@@ -915,12 +915,90 @@ function mount(root) {
|
|
|
915
915
|
}
|
|
916
916
|
};
|
|
917
917
|
|
|
918
|
-
const
|
|
918
|
+
const scenePlugins = typeof window !== 'undefined' ? window.PixelDaylightSwitchScenePlugins : null;
|
|
919
|
+
const pluginScene = scenePlugins && typeof scenePlugins.getScene === 'function' ? scenePlugins.getScene(dataset.scene) : null;
|
|
920
|
+
const builtInScene = ['countryside', 'japanese-street', 'apartment', 'seaside', 'mountain-cabin'].includes(dataset.scene);
|
|
921
|
+
const scene = builtInScene ? dataset.scene : (pluginScene ? dataset.scene : 'countryside');
|
|
919
922
|
const LIVE = { seaside: liveSeaside, 'mountain-cabin': liveCabin, 'japanese-street': liveStreet, apartment: liveApartment };
|
|
920
923
|
const drawSceneLive = (h, dt) => { const fn = LIVE[scene]; if (fn) fn(h, dt); };
|
|
924
|
+
const parsePluginColor = (value) => {
|
|
925
|
+
const hex = value.slice(1);
|
|
926
|
+
const full = hex.length === 3 ? hex.split('').map((v) => v + v).join('') : hex;
|
|
927
|
+
return [parseInt(full.slice(0, 2), 16), parseInt(full.slice(2, 4), 16), parseInt(full.slice(4, 6), 16)];
|
|
928
|
+
};
|
|
929
|
+
const pluginPaletteRgb = pluginScene ? Object.fromEntries(Object.entries(pluginScene.palettes).map(([name, palette]) => {
|
|
930
|
+
const colors = new Array(128);
|
|
931
|
+
Object.keys(palette).forEach((symbol) => { colors[symbol.charCodeAt(0)] = parsePluginColor(palette[symbol]); });
|
|
932
|
+
return [name, colors];
|
|
933
|
+
})) : null;
|
|
934
|
+
let pluginAnimClock = 0;
|
|
935
|
+
const renderPluginWorld = (hour) => {
|
|
936
|
+
const stops = pluginScene.timeCycle;
|
|
937
|
+
let left = stops[0]; let right = stops[stops.length - 1]; let sampleHour = hour;
|
|
938
|
+
if (hour < stops[0].at) sampleHour += 24;
|
|
939
|
+
for (let i = 0; i < stops.length - 1; i++) {
|
|
940
|
+
const a = stops[i]; const b = stops[i + 1];
|
|
941
|
+
if (sampleHour >= a.at && sampleHour <= b.at) { left = a; right = b; break; }
|
|
942
|
+
}
|
|
943
|
+
if (sampleHour > stops[stops.length - 1].at) { left = stops[stops.length - 1]; right = stops[0]; right = { at: right.at + 24, palette: right.palette }; }
|
|
944
|
+
const span = Math.max(0.0001, right.at - left.at);
|
|
945
|
+
const t = clamp((sampleHour - left.at) / span);
|
|
946
|
+
const aPalette = pluginPaletteRgb[left.palette];
|
|
947
|
+
const bPalette = pluginPaletteRgb[right.palette];
|
|
948
|
+
const width = pluginScene.canvas.width; const height = pluginScene.canvas.height;
|
|
949
|
+
let scenePixels = pluginScene.pixels;
|
|
950
|
+
if (pluginScene.schemaVersion === 2 && Array.isArray(pluginScene.layers)) {
|
|
951
|
+
const composed = pluginScene.pixels.map(row => row.split(''));
|
|
952
|
+
for (const layer of pluginScene.layers) {
|
|
953
|
+
const asset = pluginScene.assets[layer.asset];
|
|
954
|
+
const motion = layer.animation;
|
|
955
|
+
let dx = 0, dy = 0;
|
|
956
|
+
if (motion) {
|
|
957
|
+
const phase = pluginAnimClock * motion.speed + (motion.phase || 0);
|
|
958
|
+
const wave = motion.type === 'drift' ? (2 / Math.PI) * Math.asin(Math.sin(phase)) : Math.sin(phase);
|
|
959
|
+
const offset = wave * motion.distance;
|
|
960
|
+
if (motion.axis === 'x') dx = Math.round(offset);
|
|
961
|
+
else dy = Math.round(offset);
|
|
962
|
+
}
|
|
963
|
+
const originX = layer.x + dx, originY = layer.y + dy;
|
|
964
|
+
for (let sy = 0; sy < asset.height; sy++) {
|
|
965
|
+
const targetY = originY + sy;
|
|
966
|
+
if (targetY < 0 || targetY >= height) continue;
|
|
967
|
+
const row = asset.pixels[sy];
|
|
968
|
+
for (let sx = 0; sx < asset.width; sx++) {
|
|
969
|
+
const targetX = originX + sx;
|
|
970
|
+
if (targetX < 0 || targetX >= width) continue;
|
|
971
|
+
const symbol = row[sx];
|
|
972
|
+
if (symbol !== '.') composed[targetY][targetX] = symbol;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
scenePixels = composed.map(row => row.join(''));
|
|
977
|
+
}
|
|
978
|
+
const scale = Math.min(bw / width, bh / height);
|
|
979
|
+
const drawW = Math.max(1, Math.round(width * scale)); const drawH = Math.max(1, Math.round(height * scale));
|
|
980
|
+
const offsetX = Math.floor((bw - drawW) / 2); const offsetY = Math.floor((bh - drawH) / 2);
|
|
981
|
+
const background = scenePixels[0].charCodeAt(0);
|
|
982
|
+
for (let py = 0; py < bh; py++) {
|
|
983
|
+
const insideY = py >= offsetY && py < offsetY + drawH;
|
|
984
|
+
const sy = insideY ? clamp(Math.floor(((py - offsetY) / drawH) * height), 0, height - 1) : 0;
|
|
985
|
+
const row = scenePixels[sy];
|
|
986
|
+
for (let px = 0; px < bw; px++) {
|
|
987
|
+
const inside = insideY && px >= offsetX && px < offsetX + drawW;
|
|
988
|
+
const sx = inside ? clamp(Math.floor(((px - offsetX) / drawW) * width), 0, width - 1) : 0;
|
|
989
|
+
const symbol = inside ? row.charCodeAt(sx) : background;
|
|
990
|
+
const ca = aPalette[symbol]; const cb = bPalette[symbol];
|
|
991
|
+
const r = Math.round(mix(ca[0], cb[0], t));
|
|
992
|
+
const g = Math.round(mix(ca[1], cb[1], t));
|
|
993
|
+
const b = Math.round(mix(ca[2], cb[2], t));
|
|
994
|
+
pixels[py * bw + px] = (255 << 24) | (b << 16) | (g << 8) | r;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
};
|
|
921
998
|
|
|
922
999
|
|
|
923
1000
|
const buildBase = () => {
|
|
1001
|
+
if (pluginScene) { matBuf.fill(0); baseBuf.set(matBuf); skyKeys = []; return; }
|
|
924
1002
|
matBuf.fill(0); if (scene === 'countryside') { paintGround(); paintFence(); paintBush(22, 78, 16, 8, 61); paintBush(176, 80, 14, 7, 62); paintBush(-6, 81, 11, 6, 63); paintBush(204, 81, 10, 5, 64); paintTrunk(); paintHouse(); paintFlowers(96, 126, 85, 92, 26, 91); paintFlowers(14, 38, 86, 96, 16, 92); paintFlowers(150, 205, 90, 106, 24, 93); paintFlowers(-14, 14, 88, 104, 12, 95); paintFlowers(40, 62, 90, 104, 12, 94); buildCanopy(); } else { canopy = []; if (scene === 'japanese-street') paintJapaneseStreet(); else if (scene === 'apartment') paintApartment(); else if (scene === 'seaside') paintSeaside(); else paintMountainCabin(); } baseBuf.set(matBuf);
|
|
925
1003
|
const r = seeded(808); stars = [];
|
|
926
1004
|
for (let i = 0; i < 95; i++) { stars.push({ x: Math.floor(r() * (CORE_W + 40)) - 20, y: Math.floor(r() * 66), seed: r(), big: r() < 0.14 }); }
|
|
@@ -1034,7 +1112,9 @@ function mount(root) {
|
|
|
1034
1112
|
let keyA = 0; let keyB = 1; let keyP = 0;
|
|
1035
1113
|
const pickKeys = (h) => { for (let i = 0; i < KEYS.length - 1; i++) { if (h >= KEYS[i][0] && h < KEYS[i + 1][0]) { keyA = i; keyB = i + 1; keyP = clamp(((h - KEYS[i][0]) / (KEYS[i + 1][0] - KEYS[i][0]) - 0.32) / 0.36); return; } } keyA = 0; keyB = 1; keyP = 0; };
|
|
1036
1114
|
const renderWorld = (clock, dt, hurry) => {
|
|
1037
|
-
const h = hourOf(clock);
|
|
1115
|
+
const h = hourOf(clock);
|
|
1116
|
+
if (pluginScene) { if (!calm) pluginAnimClock += dt; renderPluginWorld(h); return; }
|
|
1117
|
+
if (!calm) anim += dt; matBuf.set(baseBuf); varBuf.fill(0); emitList.length = 0;
|
|
1038
1118
|
for (const c of sources) { c.w = 0; c.x = 0; c.y = 0; }
|
|
1039
1119
|
lampFlick = calm ? 1 : 0.95 + 0.05 * Math.sin(anim * 13) * Math.sin(anim * 5.3);
|
|
1040
1120
|
if (!calm) { birds.next -= dt; if (birds.t >= 0) { birds.t += dt; if (birds.t > 9) birds.t = -1; } else if (birds.next <= 0) { birds.t = 0; birds.next = 12 + Math.random() * 14; birds.y = 10 + Math.random() * 24; birds.dir = Math.random() < 0.5 ? 1 : -1; } shoot.next -= dt; if (shoot.t >= 0) { shoot.t += dt; if (shoot.t > 0.6) shoot.t = -1; } else if (shoot.next <= 0) { shoot.t = 0; shoot.next = 9 + Math.random() * 16; shoot.x = 10 + Math.random() * 130; shoot.y = 2 + Math.random() * 20; } }
|
|
@@ -1326,7 +1406,14 @@ function unmountOne(root) {
|
|
|
1326
1406
|
}
|
|
1327
1407
|
function mountAll() { document.querySelectorAll(SELECTOR).forEach(mountOne); }
|
|
1328
1408
|
const api = { mount: mountOne, unmount: unmountOne };
|
|
1329
|
-
if (typeof
|
|
1409
|
+
if (typeof window !== 'undefined' && window.PixelDaylightSwitchScenePlugins) {
|
|
1410
|
+
const sceneApi = window.PixelDaylightSwitchScenePlugins;
|
|
1411
|
+
api.registerScene = sceneApi.registerScene;
|
|
1412
|
+
api.validateScene = sceneApi.validateScene;
|
|
1413
|
+
api.getScene = sceneApi.getScene;
|
|
1414
|
+
api.listScenes = sceneApi.listScenes;
|
|
1415
|
+
}
|
|
1416
|
+
if (typeof document !== 'undefined' && !(typeof window !== 'undefined' && window.__PDS_DEFER_AUTO_MOUNT__)) {
|
|
1330
1417
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', mountAll, { once: true });
|
|
1331
1418
|
else mountAll();
|
|
1332
1419
|
}
|
package/browser.mjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import './scene-plugin.js';
|
|
2
|
+
|
|
3
|
+
// Browser ESM consumers need to register their scene before mounting.
|
|
4
|
+
// Suppress the classic script's automatic DOM scan until the caller invokes mount().
|
|
5
|
+
globalThis.__PDS_DEFER_AUTO_MOUNT__ = true;
|
|
6
|
+
try {
|
|
7
|
+
await import('./app.js');
|
|
8
|
+
} finally {
|
|
9
|
+
delete globalThis.__PDS_DEFER_AUTO_MOUNT__;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const api = globalThis.PixelDaylightSwitch;
|
|
13
|
+
const scenes = globalThis.PixelDaylightSwitchScenePlugins;
|
|
14
|
+
export const mount = api.mount;
|
|
15
|
+
export const unmount = api.unmount;
|
|
16
|
+
export const registerScene = scenes.registerScene;
|
|
17
|
+
export const validateScene = scenes.validateScene;
|
|
18
|
+
export const getScene = scenes.getScene;
|
|
19
|
+
export const listScenes = scenes.listScenes;
|
|
20
|
+
export default api;
|