pixel-daylight-switch-gsvegetable 3.0.1 → 3.1.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/README.ja.md CHANGED
@@ -12,22 +12,22 @@
12
12
 
13
13
  Pixel Daylight Switch は、ドラッグ操作で昼・夕暮れ・夜を切り替えられる UI コンポーネントです。ガラスレンズの中のピクセルアートの世界が、スイッチの動きに合わせて変化します。Vanilla JavaScript、WebGL2、Canvas 2D フォールバックで構成され、実行時にサードパーティ製ライブラリを必要としません。
14
14
 
15
- **[ライブデモ](https://gsvegetable.github.io/pixel-daylight-switch/)** · **[パラメーター Playground](https://gsvegetable.github.io/pixel-daylight-switch/playground.html)** · **[npm パッケージ](https://www.npmjs.com/package/pixel-daylight-switch-gsvegetable)**
15
+ **[ライブデモ](https://gsvegetable.github.io/pixel-daylight-switch/)** · **[パラメーター Playground](https://gsvegetable.github.io/pixel-daylight-switch/demos/playground.html)** · **[npm パッケージ](https://www.npmjs.com/package/pixel-daylight-switch-gsvegetable)**
16
16
 
17
17
  ## 目的別リンク
18
18
 
19
19
  | 目的 | リンク |
20
20
  | --- | --- |
21
21
  | スイッチを体験する | [ライブデモ](https://gsvegetable.github.io/pixel-daylight-switch/) |
22
- | シーンや表示を調整する | [パラメーター Playground](https://gsvegetable.github.io/pixel-daylight-switch/playground.html) |
23
- | コンポーネントを見る | [Preview](https://gsvegetable.github.io/pixel-daylight-switch/preview.html) |
22
+ | シーンや表示を調整する | [パラメーター Playground](https://gsvegetable.github.io/pixel-daylight-switch/demos/playground.html) |
23
+ | コンポーネントを見る | [Preview](https://gsvegetable.github.io/pixel-daylight-switch/demos/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
+ | React / Vue の導入例 | [フレームワーク統合ガイド](docs/INTEGRATIONS.md) |
27
27
  | 実装を読む | [app.js](app.js) · [style.css](style.css) |
28
28
  | モバイルビルド | [ビルド情報](builds/README.md) · [GitHub Actions](https://github.com/GSvegetable/pixel-daylight-switch/actions/workflows/mobile-build.yml) |
29
29
  | テスト結果 | [CI ワークフロー](https://github.com/GSvegetable/pixel-daylight-switch/actions/workflows/ci.yml) |
30
- | 更新履歴 | [CHANGELOG](CHANGELOG.md) |
30
+ | 更新履歴 | [CHANGELOG](docs/CHANGELOG.md) |
31
31
  | 不具合を報告する | [Issues](https://github.com/GSvegetable/pixel-daylight-switch/issues) |
32
32
 
33
33
  ## 主な機能
@@ -96,50 +96,61 @@ const PixelDaylightSwitch = require('pixel-daylight-switch-gsvegetable');
96
96
 
97
97
  ESM と CommonJS の両方を提供します。SSR 環境でもインポートできますが、`mount()` はブラウザー上で対象要素が存在してから呼び出してください。
98
98
 
99
- ## コミュニティシーンプラグイン(v3.0.0)
100
99
 
101
- 3.0.0 では JSON Manifest によるカスタムピクセルワールドの登録に対応しました。初めての方はピクセルマップをそのまま使い、昼夜パレットの色だけを変更できます。上級者はピクセルマップと時間パレットを自由に設計できます。プラグインは宣言型データのみを受け取り、レンダラー内で第三者 JavaScript を実行しません。
100
+ ## 独立ガラス屈折エンジン(v3.1.1)
102
101
 
103
- **ブラウザーで使用:**
102
+ ガラスレンズを昼夜スイッチから独立して利用できるようになりました。固定アスペクト比に依存せず、幅・高さ・位置・角丸・屈折・色分散・縁の厚さ・ハイライトを個別に設定できます。Canvas、画像、動画、ImageBitmap、またはそれらを返す関数を背景ソースとして指定します。[ガイド](docs/GLASS_ENGINE.md) と [ライブデモ](https://gsvegetable.github.io/pixel-daylight-switch/demos/glass-engine.html) を参照してください。
104
103
 
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>
104
+ ```js
105
+ import { createGlass } from 'pixel-daylight-switch-gsvegetable/glass-engine';
106
+ const glass = createGlass(container, { source: backgroundCanvas, width: '72%', height: 180, radius: 28 });
107
+ glass.setOptions({ refraction: 1.3, dispersion: 0.08 });
108
+ glass.destroy();
112
109
  ```
113
110
 
114
- コンポーネントをマウントする前に Manifest を登録してください。npm の場合:
111
+ WebGL は任意の DOM ピクセルを直接読み取れません。Web ページの背景を屈折させるには、ホストアプリが Canvas などの描画ソースを提供する必要があります。クロスオリジンメディアには CORS 制約があります。
112
+
113
+ ## コミュニティシーン
114
+
115
+ コミュニティシーンは宣言型 JSON Manifest で定義し、コンポーネントのマウント前に登録します。現在の形式は **Manifest v2** です。再利用可能なピクセル素材、順序付きレイヤー、制限付きモーショントラックに対応し、既存の Manifest v1 シーンも引き続き利用できます。
116
+
117
+ ### シーンの登録
118
+
119
+ npm プロジェクトの場合:
115
120
 
116
121
  ```js
117
122
  import PixelDaylightSwitch, { registerScene } from 'pixel-daylight-switch-gsvegetable';
118
- import myScene from './my-scene.json';
123
+ import scene from './floating-island.v2.json';
119
124
 
120
- const unregister = registerScene(myScene);
125
+ const unregister = registerScene(scene);
121
126
  const root = document.querySelector('[data-ld-part="toggle-daylight"]');
122
- root.dataset.scene = myScene.id;
127
+ root.dataset.scene = scene.id;
123
128
  PixelDaylightSwitch.mount(root);
124
129
  ```
125
130
 
126
- Manifest v1 はシーン ID、作者・ライセンス、ピクセルキャンバス、文字ベースのピクセルマップ、複数の昼夜パレット、24 時間のタイムラインで構成されます。キャンバスは最大 192 × 112 ピクセルです。すべてのピクセル記号に各パレットの色を定義する必要があります。時間ノード間の色はレンダラーが補間します。
131
+ ブラウザーで直接使用する場合は、`scene-plugin.js` を `app.js` より先に読み込み、マウント前に Manifest を登録します。
127
132
 
128
- - [プラグイン開発ガイド](SCENE_PLUGINS.md)
129
- - [Manifest v1 JSON Schema](schemas/scene-manifest.v1.schema.json)
130
- - [Sakura Evening サンプル](examples/scenes/sakura-evening.json)
133
+ ```html
134
+ <script src="https://gsvegetable.github.io/pixel-daylight-switch/scene-plugin.js"></script>
135
+ <script src="https://gsvegetable.github.io/pixel-daylight-switch/app.js"></script>
136
+ <div data-ld-part="toggle-daylight" data-scene="my-community-scene"></div>
137
+ <script>
138
+ PixelDaylightSwitchScenePlugins.registerScene(sceneManifest);
139
+ </script>
140
+ ```
131
141
 
132
- ### Manifest v2:レイヤー付きピクセルワールド(v3.0.1)
142
+ ### Manifest v2 の機能
133
143
 
134
- 3.0.1 では Manifest v1 を変更せずに v2 を追加しました。v2 は最大 24 個の再利用可能なピクセルアセットと、最大 16 個の順序付きレイヤーに対応します。レイヤーはシーン座標に配置でき、drift または bob の宣言型モーショントラックで横・縦方向に動かせます。重ねるアセット内の . は透明として扱い、それ以外の文字は昼夜パレットで着色されます。
144
+ - 最大 24 個の再利用可能な文字ベースのピクセル素材と、最大 16 個の順序付きレイヤー。
145
+ - 整数のシーン座標を使用し、後のレイヤーほど手前に合成されます。
146
+ - オーバーレイ素材のドット文字 `.` は透明ピクセルとして扱われます。
147
+ - `drift` と `bob` の決定論的なモーショントラックに対応し、X/Y 軸上で動作します。
148
+ - 既存の Manifest v1 と 5 つの組み込みシーンは引き続きサポートされます。
135
149
 
136
- - [Manifest v2 JSON Schema](schemas/scene-manifest.v2.schema.json)
137
- - [Floating Island レイヤーサンプル](examples/scenes/floating-island.v2.json)
138
- - [シーンプラグイン開発ガイド](SCENE_PLUGINS.md)
150
+ [Manifest v2 Schema](schemas/scene-manifest.v2.schema.json)、[Floating Island サンプル](examples/scenes/floating-island.v2.json)、[シーン制作ガイド](docs/SCENE_PLUGINS.md)を参照してください。互換性のため [v1 Schema](schemas/scene-manifest.v1.schema.json) も維持されています。
139
151
 
140
- v2 のアセットは文字ベースのピクセルマップです。PNG/JPEG の外部ダウンロードには対応しません。これによりピクセル表現と昼夜の色変化を一貫させ、外部画像の CORS や任意コード実行を避けます。
152
+ 現在の素材形式は文字ベースのピクセルマトリックスです。PNG/JPEG のアップロードや任意の実行コードには対応していません。
141
153
 
142
- 5 つの組み込みシーンは従来の描画・アニメーション実装を維持します。
143
154
 
144
155
  ## 設定属性
145
156
 
@@ -248,35 +259,27 @@ iOS は初回に `npm run mobile:ios:init`、以降は `npm run mobile:ios` を
248
259
  ```text
249
260
  pixel-daylight-switch/
250
261
  ├── .github/workflows/ # CI、モバイルビルド、npm 公開
251
- ├── assets/
252
- │ └── scene-gallery.png # 5 シーンの一覧画像
253
- ├── builds/
254
- │ └── README.md # モバイル成果物の説明(バイナリは Actions が生成)
255
- ├── mobile/
256
- │ ├── index.html # Capacitor アプリの入口
257
- │ └── mobile.css # モバイル専用レイアウト
258
- ├── scripts/
259
- │ └── prepare-mobile.cjs # Capacitor 用 Web ランタイム準備
260
- ├── tests/
261
- │ ├── browser/ # Playwright 操作・モバイル回帰テスト
262
- │ ├── fixture.html
263
- │ ├── mobile-smoke.cjs
264
- │ ├── package-smoke.cjs
265
- │ └── server.cjs
266
- ├── app.js # コンポーネント、シーン、入力、描画
267
- ├── style.css # コンポーネントのスタイル
268
- ├── embed.js # 1 スクリプト埋め込みローダー
269
- ├── index.html # ライブデモ
270
- ├── preview.html # コンポーネントプレビュー
271
- ├── playground.html # パラメーター Playground
272
- ├── index.mjs / index.cjs # ESM / CommonJS エントリー
273
- ├── package.json # npm メタデータとスクリプト
274
- ├── capacitor.config.json # Capacitor 設定
275
- ├── playwright.config.cjs # ブラウザーテスト設定
276
- ├── CHANGELOG.md
277
- ├── llms.txt
262
+ ├── assets/ # シーンギャラリー画像
263
+ ├── builds/ # モバイルビルド情報
264
+ ├── docs/ # 統合ガイド、シーン制作ガイド、変更履歴
265
+ ├── examples/scenes/ # コミュニティシーン Manifest
266
+ ├── mobile/ # Capacitor アプリシェル
267
+ ├── schemas/ # Manifest v1 / v2 JSON Schema
268
+ ├── scripts/ # モバイル準備・バージョン設定
269
+ ├── tests/ # Node / Chromium 回帰テスト
270
+ ├── app.js / style.css # ランタイムとコンポーネントスタイル
271
+ ├── scene-plugin.js # シーン登録・検証 API
272
+ ├── embed.js # Web サイト埋め込みローダー
273
+ ├── index.html # ライブデモ
274
+ ├── demos/ # パラメーター Playground と Preview
275
+ ├── config/ # 開発ツール設定
276
+ ├── index.mjs / index.cjs # ESM / CommonJS エントリー
277
+ ├── package.json # パッケージ設定とスクリプト
278
+ ├── capacitor.config.json # Capacitor 設定
279
+ ├── playwright.config.cjs # ブラウザーテスト設定
280
+ ├── llms.txt # AI 向けプロジェクトガイド
278
281
  ├── LICENSE
279
- └── README*.md # 英語・簡体字中国語・日本語の README
282
+ └── README*.md # 英語・簡体字中国語・日本語 README
280
283
  ```
281
284
 
282
285
  ## ライセンス
package/README.md CHANGED
@@ -12,23 +12,23 @@
12
12
 
13
13
  Pixel Daylight Switch is a draggable day/night UI component. Move its glass control to travel between day, dusk and night while a miniature pixel-art world changes inside the lens. It uses vanilla JavaScript, WebGL2 and a Canvas 2D fallback, with no third-party runtime dependency.
14
14
 
15
- **[Open the live demo](https://gsvegetable.github.io/pixel-daylight-switch/)** · **[Launch the parameter lab](https://gsvegetable.github.io/pixel-daylight-switch/playground.html)** · **[Install from npm](https://www.npmjs.com/package/pixel-daylight-switch-gsvegetable)**
15
+ **[Open the live demo](https://gsvegetable.github.io/pixel-daylight-switch/)** · **[Launch the parameter lab](https://gsvegetable.github.io/pixel-daylight-switch/demos/playground.html)** · **[Install from npm](https://www.npmjs.com/package/pixel-daylight-switch-gsvegetable)**
16
16
 
17
17
  ## Start here
18
18
 
19
19
  | Goal | Direct link |
20
20
  | --- | --- |
21
21
  | Experience the switch | [Live demo](https://gsvegetable.github.io/pixel-daylight-switch/) |
22
- | Customize scenes and visual behavior | [Interactive parameter lab](https://gsvegetable.github.io/pixel-daylight-switch/playground.html) |
23
- | Inspect the component | [Preview page](https://gsvegetable.github.io/pixel-daylight-switch/preview.html) |
22
+ | Customize scenes and visual behavior | [Interactive parameter lab](https://gsvegetable.github.io/pixel-daylight-switch/demos/playground.html) |
23
+ | Inspect the component | [Preview page](https://gsvegetable.github.io/pixel-daylight-switch/demos/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
+ | Framework integration recipes | [React and Vue examples](docs/INTEGRATIONS.md) |\n| Standalone glass refraction | [Glass engine guide](docs/GLASS_ENGINE.md) · [Live demo](https://gsvegetable.github.io/pixel-daylight-switch/demos/glass-engine.html) |
27
+ | Scene plugin authoring guide | [Scene plugin guide](docs/SCENE_PLUGINS.md) |
28
28
  | Read implementation details | [Runtime](app.js) · [Styles](style.css) |
29
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) |
30
30
  | Check tests | [CI workflow](https://github.com/GSvegetable/pixel-daylight-switch/actions/workflows/ci.yml) |
31
- | Review changes | [Changelog](CHANGELOG.md) |
31
+ | Review changes | [Changelog](docs/CHANGELOG.md) |
32
32
  | Report a bug | [Issue tracker](https://github.com/GSvegetable/pixel-daylight-switch/issues) |
33
33
 
34
34
  ## Features
@@ -97,72 +97,61 @@ const PixelDaylightSwitch = require('pixel-daylight-switch-gsvegetable');
97
97
 
98
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).
99
99
 
100
- ## Community scene plugins (v3.0.0)
101
100
 
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.
101
+ ## Standalone glass engine (v3.1.1)
103
102
 
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:
103
+ The glass lens is also available independently of the day/night switch. It accepts a canvas, image, video, bitmap, or source callback and lets you set any width, height, position, radius, refraction, dispersion, bezel and highlight values. See the [glass engine guide](docs/GLASS_ENGINE.md) and [live demo](https://gsvegetable.github.io/pixel-daylight-switch/demos/glass-engine.html).
115
104
 
116
105
  ```js
117
- const unregister = PixelDaylightSwitchScenePlugins.registerScene(sceneManifest);
118
- PixelDaylightSwitch.mount(document.querySelector('[data-ld-part="toggle-daylight"]'));
106
+ import { createGlass } from 'pixel-daylight-switch-gsvegetable/glass-engine';
107
+ const glass = createGlass(container, { source: backgroundCanvas, width: '72%', height: 180, radius: 28 });
108
+ glass.setOptions({ refraction: 1.3, dispersion: 0.08 });
109
+ glass.destroy();
119
110
  ```
120
111
 
121
- With npm:
112
+ WebGL cannot sample arbitrary DOM pixels directly. To refract a live page background, provide an origin-clean canvas/render target from your application. Cross-origin media is subject to browser CORS rules.
122
113
 
123
- ```js
124
- import PixelDaylightSwitch, { registerScene } from 'pixel-daylight-switch-gsvegetable';
125
- import sakura from './sakura-evening.json';
114
+ ## Community scenes
126
115
 
127
- registerScene(sakura);
128
- PixelDaylightSwitch.mount(document.querySelector('[data-ld-part="toggle-daylight"]'));
129
- ```
116
+ Community scenes are declarative JSON manifests registered before the component is mounted. The current format is **Manifest v2**, which supports reusable pixel-matrix assets, ordered layers and bounded motion tracks. Existing Manifest v1 scenes remain compatible.
130
117
 
131
- ### Manifest v2 — layered pixel worlds (v3.0.1)
118
+ ### Register a scene
132
119
 
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.
120
+ For npm projects:
134
121
 
135
122
  ```js
136
123
  import PixelDaylightSwitch, { registerScene } from 'pixel-daylight-switch-gsvegetable';
137
- import island from './floating-island.v2.json';
124
+ import scene from './floating-island.v2.json';
138
125
 
139
- registerScene(island);
126
+ const unregister = registerScene(scene);
140
127
  const root = document.querySelector('[data-ld-part="toggle-daylight"]');
141
- root.dataset.scene = island.id;
128
+ root.dataset.scene = scene.id;
142
129
  PixelDaylightSwitch.mount(root);
143
130
  ```
144
131
 
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)
132
+ For direct browser usage, load `scene-plugin.js` before `app.js`, then register the manifest before mounting:
148
133
 
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.
134
+ ```html
135
+ <script src="https://gsvegetable.github.io/pixel-daylight-switch/scene-plugin.js"></script>
136
+ <script src="https://gsvegetable.github.io/pixel-daylight-switch/app.js"></script>
137
+ <div data-ld-part="toggle-daylight" data-scene="my-community-scene"></div>
138
+ <script>
139
+ PixelDaylightSwitchScenePlugins.registerScene(sceneManifest);
140
+ </script>
141
+ ```
150
142
 
151
- ## Manifest v1
143
+ ### Manifest v2 capabilities
152
144
 
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.
145
+ - Up to 24 reusable pixel-matrix assets and 16 ordered layers.
146
+ - Integer scene coordinates; later layers are composited above earlier layers.
147
+ - The dot character (`.`) is transparent in overlay assets.
148
+ - Optional deterministic `drift` and `bob` motion tracks on either axis.
149
+ - Existing v1 manifests and all five built-in scenes remain supported and unchanged.
154
150
 
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.
151
+ See the [Manifest v2 schema](schemas/scene-manifest.v2.schema.json), [Floating Island example](examples/scenes/floating-island.v2.json) and [scene authoring guide](docs/SCENE_PLUGINS.md). The v1 schema remains available for compatibility at [schemas/scene-manifest.v1.schema.json](schemas/scene-manifest.v1.schema.json).
162
152
 
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.
153
+ Assets currently use native character pixel matrices; PNG/JPEG uploads and arbitrary executable plugin callbacks are not part of the current format.
164
154
 
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
155
 
167
156
  ## Configuration
168
157
 
@@ -271,35 +260,27 @@ For iOS, use `npm run mobile:ios:init` once and `npm run mobile:ios` for later s
271
260
  ```text
272
261
  pixel-daylight-switch/
273
262
  ├── .github/workflows/ # CI, native mobile builds, npm publishing
274
- ├── assets/
275
- │ └── scene-gallery.png # Five-scene overview image
276
- ├── builds/
277
- │ └── README.md # Mobile artifact notes; binaries are workflow outputs
278
- ├── mobile/
279
- │ ├── index.html # Capacitor app entry
280
- │ └── mobile.css # Mobile-only layout
281
- ├── scripts/
282
- │ └── prepare-mobile.cjs # Prepare the shared web runtime for Capacitor
283
- ├── tests/
284
- │ ├── browser/ # Playwright interaction and mobile regressions
285
- │ ├── fixture.html
286
- │ ├── mobile-smoke.cjs
287
- │ ├── package-smoke.cjs
288
- │ └── server.cjs
289
- ├── app.js # Component runtime, scenes, input and rendering
290
- ├── style.css # Component styles
291
- ├── embed.js # One-script website embed loader
292
- ├── index.html # Live demo page
293
- ├── preview.html # Component preview
294
- ├── playground.html # Interactive parameter lab
295
- ├── index.mjs / index.cjs # ESM and CommonJS package entry points
296
- ├── package.json # Package metadata and scripts
297
- ├── capacitor.config.json # Capacitor configuration
298
- ├── playwright.config.cjs # Browser test configuration
299
- ├── CHANGELOG.md
300
- ├── llms.txt
263
+ ├── assets/ # Gallery artwork
264
+ ├── builds/ # Mobile build notes
265
+ ├── docs/ # Integration guides, scene guide, changelog
266
+ ├── examples/scenes/ # Community scene manifests
267
+ ├── mobile/ # Capacitor app shell
268
+ ├── schemas/ # Manifest v1 and v2 JSON Schemas
269
+ ├── scripts/ # Mobile preparation/version scripts
270
+ ├── tests/ # Node and Chromium regression tests
271
+ ├── app.js / style.css # Runtime and component styles
272
+ ├── scene-plugin.js # Community scene registry and validator
273
+ ├── embed.js # One-script website embed loader
274
+ ├── index.html # Live demo
275
+ ├── demos/ # Public parameter lab and preview pages
276
+ ├── config/ # Tooling configuration
277
+ ├── index.mjs / index.cjs # ESM and CommonJS package entry points
278
+ ├── package.json # Package metadata and scripts
279
+ ├── capacitor.config.json # Capacitor configuration
280
+ ├── playwright.config.cjs # Browser test configuration
281
+ ├── llms.txt # AI-readable project guide
301
282
  ├── LICENSE
302
- └── README*.md # English, Simplified Chinese and Japanese docs
283
+ └── README*.md # English, Simplified Chinese and Japanese
303
284
  ```
304
285
 
305
286
  ## License
package/README.zh-CN.md CHANGED
@@ -12,22 +12,22 @@
12
12
 
13
13
  Pixel Daylight Switch 是一个可拖动的昼夜切换 UI 组件。拖动玻璃开关,可以让内部的像素世界从白天经过黄昏切换到夜晚。项目使用原生 JavaScript、WebGL2 和 Canvas 2D 回退渲染,不依赖第三方运行时库。
14
14
 
15
- **[直接体验在线演示](https://gsvegetable.github.io/pixel-daylight-switch/)** · **[打开参数实验室](https://gsvegetable.github.io/pixel-daylight-switch/playground.html)** · **[npm 安装](https://www.npmjs.com/package/pixel-daylight-switch-gsvegetable)**
15
+ **[直接体验在线演示](https://gsvegetable.github.io/pixel-daylight-switch/)** · **[打开参数实验室](https://gsvegetable.github.io/pixel-daylight-switch/demos/playground.html)** · **[npm 安装](https://www.npmjs.com/package/pixel-daylight-switch-gsvegetable)**
16
16
 
17
17
  ## 快速入口
18
18
 
19
19
  | 你想做什么 | 直接打开 |
20
20
  | --- | --- |
21
21
  | 体验昼夜开关 | [在线演示](https://gsvegetable.github.io/pixel-daylight-switch/) |
22
- | 调整场景与视觉参数 | [交互式参数实验室](https://gsvegetable.github.io/pixel-daylight-switch/playground.html) |
23
- | 查看组件预览 | [Preview 页面](https://gsvegetable.github.io/pixel-daylight-switch/preview.html) |
22
+ | 调整场景与视觉参数 | [交互式参数实验室](https://gsvegetable.github.io/pixel-daylight-switch/demos/playground.html) |
23
+ | 查看组件预览 | [Preview 页面](https://gsvegetable.github.io/pixel-daylight-switch/demos/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
+ | React / Vue 接入示例 | [框架集成指南](docs/INTEGRATIONS.md) |\n| 独立玻璃折射引擎 | [引擎文档](docs/GLASS_ENGINE.md) · [在线演示](https://gsvegetable.github.io/pixel-daylight-switch/demos/glass-engine.html) |
27
27
  | 阅读核心实现 | [运行时 app.js](app.js) · [样式 style.css](style.css) |
28
28
  | 获取移动端构建 | [构建说明与限制](builds/README.md) · [GitHub Actions 构建任务](https://github.com/GSvegetable/pixel-daylight-switch/actions/workflows/mobile-build.yml) |
29
29
  | 查看自动化测试 | [CI 工作流](https://github.com/GSvegetable/pixel-daylight-switch/actions/workflows/ci.yml) |
30
- | 查看版本变化 | [更新日志](CHANGELOG.md) |
30
+ | 查看版本变化 | [更新日志](docs/CHANGELOG.md) |
31
31
  | 反馈问题 | [GitHub Issues](https://github.com/GSvegetable/pixel-daylight-switch/issues) |
32
32
 
33
33
  ## 功能特点
@@ -96,50 +96,61 @@ const PixelDaylightSwitch = require('pixel-daylight-switch-gsvegetable');
96
96
 
97
97
  npm 包提供 ESM 和 CommonJS 入口,可在 SSR 环境中安全导入;只有在浏览器中且目标元素已经存在时,才能调用 `mount()`。npm 发布文件清单以 [package.json](package.json) 为准。
98
98
 
99
- ## 社区场景插件(v3.0.0)
100
99
 
101
- 3.0.0 开始支持通过 JSON Manifest 注册自定义像素世界。普通创作者可以保留像素矩阵、只修改昼夜色板;进阶创作者可以自己设计像素矩阵和时间色板。插件只提供声明式数据,不会在渲染器中执行第三方 JavaScript。
100
+ ## 独立玻璃折射引擎(v3.1.1)
102
101
 
103
- **浏览器使用:**
102
+ 这次升级把玻璃镜片从昼夜开关中独立出来。现在可以单独创建一层玻璃,不再受原开关的固定宽高比和尺寸限制;可分别设置宽度、高度、位置、圆角、折射、色散、边缘厚度和高光。支持 Canvas、图片、视频、ImageBitmap 或返回这些对象的函数作为背景源。查看[完整接入文档](docs/GLASS_ENGINE.md)和[在线调参演示](https://gsvegetable.github.io/pixel-daylight-switch/demos/glass-engine.html)。
104
103
 
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>
104
+ ```js
105
+ import { createGlass } from 'pixel-daylight-switch-gsvegetable/glass-engine';
106
+ const glass = createGlass(container, { source: backgroundCanvas, width: '72%', height: 180, radius: 28 });
107
+ glass.setOptions({ refraction: 1.3, dispersion: 0.08 });
108
+ glass.destroy();
112
109
  ```
113
110
 
114
- 请在组件挂载前注册 Manifest。使用 npm 时:
111
+ 注意:浏览器不允许 WebGL 直接读取任意网页 DOM 像素。若要折射实时网页背景,宿主应用必须提供可采样的 Canvas / 渲染目标;跨域媒体还必须满足 CORS 要求。
112
+
113
+ ## 社区场景
114
+
115
+ 社区场景使用声明式 JSON Manifest,在组件挂载前注册。当前采用 **Manifest v2**,支持可复用像素素材、顺序图层和受限动画轨道,同时继续兼容已有的 Manifest v1 场景。
116
+
117
+ ### 注册场景
118
+
119
+ 在 npm 项目中:
115
120
 
116
121
  ```js
117
122
  import PixelDaylightSwitch, { registerScene } from 'pixel-daylight-switch-gsvegetable';
118
- import myScene from './my-scene.json';
123
+ import scene from './floating-island.v2.json';
119
124
 
120
- const unregister = registerScene(myScene);
125
+ const unregister = registerScene(scene);
121
126
  const root = document.querySelector('[data-ld-part="toggle-daylight"]');
122
- root.dataset.scene = myScene.id;
127
+ root.dataset.scene = scene.id;
123
128
  PixelDaylightSwitch.mount(root);
124
129
  ```
125
130
 
126
- Manifest v1 包含场景 ID、作者与许可证、像素画布、字符像素矩阵、多个昼夜色板和 24 小时时间节点。画布最大为 192 × 112;每个像素符号都必须在每套色板中定义颜色。渲染器会在相邻时间节点之间插值颜色。
131
+ 直接在浏览器中使用时,先加载 `scene-plugin.js`,再加载 `app.js`,并在挂载组件前注册 Manifest:
127
132
 
128
- - [完整插件开发指南](SCENE_PLUGINS.md)
129
- - [Manifest v1 JSON Schema](schemas/scene-manifest.v1.schema.json)
130
- - [樱花黄昏示例](examples/scenes/sakura-evening.json)
133
+ ```html
134
+ <script src="https://gsvegetable.github.io/pixel-daylight-switch/scene-plugin.js"></script>
135
+ <script src="https://gsvegetable.github.io/pixel-daylight-switch/app.js"></script>
136
+ <div data-ld-part="toggle-daylight" data-scene="my-community-scene"></div>
137
+ <script>
138
+ PixelDaylightSwitchScenePlugins.registerScene(sceneManifest);
139
+ </script>
140
+ ```
131
141
 
132
- ### Manifest v2:分层像素世界(v3.0.1)
142
+ ### Manifest v2 能力
133
143
 
134
- 3.0.1 在不改变 Manifest v1 的前提下增加了 v2。v2 支持最多 24 个可复用像素素材、最多 16 个按顺序合成的图层。图层可以指定场景坐标,并使用 drift 或 bob 声明式运动轨道沿水平或垂直方向移动。叠加素材中的点号 . 表示透明,其余字符由昼夜色板统一着色。
144
+ - 最多 24 个可复用字符像素素材、最多 16 个顺序合成图层。
145
+ - 图层使用整数场景坐标,后声明的图层显示在前面图层之上。
146
+ - 叠加素材中的点号 `.` 表示透明像素。
147
+ - 支持 `drift`(往返漂移)和 `bob`(正弦浮动)两种受限动画轨道,可沿水平或垂直方向运行。
148
+ - 继续兼容已有 v1 Manifest,原有五个内置场景保持不变。
135
149
 
136
- - [Manifest v2 JSON Schema](schemas/scene-manifest.v2.schema.json)
137
- - [Floating Island 分层场景示例](examples/scenes/floating-island.v2.json)
138
- - [完整场景插件开发指南](SCENE_PLUGINS.md)
150
+ 查看 [Manifest v2 Schema](schemas/scene-manifest.v2.schema.json)、[Floating Island 分层场景示例](examples/scenes/floating-island.v2.json) 和[场景创作指南](docs/SCENE_PLUGINS.md)。为保证兼容性,原始 [v1 Schema](schemas/scene-manifest.v1.schema.json) 仍然保留。
139
151
 
140
- 当前 v2 素材采用原生字符像素矩阵,而不是远程 PNG/JPEG 图片;这样可以保持像素边缘清晰、昼夜调色一致,也不需要额外的跨域图片加载或执行第三方代码。
152
+ 当前素材采用字符像素矩阵,不支持 PNG/JPEG 上传,也不会执行社区提交的任意脚本。
141
153
 
142
- 五个内置场景仍保留原有绘制与动态逻辑。
143
154
 
144
155
  ## 配置参数
145
156
 
@@ -248,35 +259,27 @@ iOS 首次创建使用 `npm run mobile:ios:init`,后续使用 `npm run mobile:
248
259
  ```text
249
260
  pixel-daylight-switch/
250
261
  ├── .github/workflows/ # CI、原生移动端构建、npm 发布
251
- ├── assets/
252
- │ └── scene-gallery.png # 五种场景总览图
253
- ├── builds/
254
- │ └── README.md # 移动端产物说明;二进制由工作流生成
255
- ├── mobile/
256
- │ ├── index.html # Capacitor 应用入口
257
- │ └── mobile.css # 移动端专用布局
258
- ├── scripts/
259
- │ └── prepare-mobile.cjs # 为 Capacitor 准备共用 Web 运行时
260
- ├── tests/
261
- │ ├── browser/ # Playwright 交互与移动端回归
262
- │ ├── fixture.html
263
- │ ├── mobile-smoke.cjs
264
- │ ├── package-smoke.cjs
265
- │ └── server.cjs
266
- ├── app.js # 组件运行时、场景、交互与渲染
267
- ├── style.css # 组件样式
268
- ├── embed.js # 单脚本网站嵌入加载器
269
- ├── index.html # 在线演示主页
270
- ├── preview.html # 组件预览
271
- ├── playground.html # 交互式参数实验室
272
- ├── index.mjs / index.cjs # ESM 与 CommonJS 包入口
273
- ├── package.json # npm 元数据与脚本
274
- ├── capacitor.config.json # Capacitor 配置
275
- ├── playwright.config.cjs # 浏览器测试配置
276
- ├── CHANGELOG.md
277
- ├── llms.txt
262
+ ├── assets/ # 场景展示图片
263
+ ├── builds/ # 移动端构建说明
264
+ ├── docs/ # 集成指南、场景创作指南、更新日志
265
+ ├── examples/scenes/ # 社区场景 Manifest 示例
266
+ ├── mobile/ # Capacitor 应用外壳
267
+ ├── schemas/ # Manifest v1 / v2 JSON Schema
268
+ ├── scripts/ # 移动端准备与版本脚本
269
+ ├── tests/ # Node 与 Chromium 回归测试
270
+ ├── app.js / style.css # 运行时与组件样式
271
+ ├── scene-plugin.js # 场景注册与校验 API
272
+ ├── embed.js # 网站单脚本嵌入加载器
273
+ ├── index.html # 在线演示
274
+ ├── demos/ # 参数实验室与组件预览页面
275
+ ├── config/ # 工具配置
276
+ ├── index.mjs / index.cjs # ESM / CommonJS 入口
277
+ ├── package.json # 包元数据与脚本
278
+ ├── capacitor.config.json # Capacitor 配置
279
+ ├── playwright.config.cjs # 浏览器测试配置
280
+ ├── llms.txt # 面向 AI 的项目说明
278
281
  ├── LICENSE
279
- └── README*.md # 英文、简体中文、日文文档
282
+ └── README*.md # 英文、简体中文和日文 README
280
283
  ```
281
284
 
282
285
  ## 许可证
package/browser.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import './glass-engine.js';
1
2
  import './scene-plugin.js';
2
3
 
3
4
  // Browser ESM consumers need to register their scene before mounting.
@@ -10,6 +11,7 @@ try {
10
11
  }
11
12
 
12
13
  const api = globalThis.PixelDaylightSwitch;
14
+ api.createGlass = globalThis.PixelGlassEngine.createGlass;
13
15
  const scenes = globalThis.PixelDaylightSwitchScenePlugins;
14
16
  export const mount = api.mount;
15
17
  export const unmount = api.unmount;
@@ -18,3 +20,5 @@ export const validateScene = scenes.validateScene;
18
20
  export const getScene = scenes.getScene;
19
21
  export const listScenes = scenes.listScenes;
20
22
  export default api;
23
+
24
+ export const createGlass = globalThis.PixelGlassEngine.createGlass;
@@ -0,0 +1,127 @@
1
+ # Changelog\n\n## [3.1.1] — Standalone refractive glass engine\n\n- Extract the refractive lens into a separately mountable WebGL2 overlay with no fixed aspect ratio or switch dependency.\n- Expose configurable geometry, refraction, dispersion, bezel, shine, opacity and lifecycle methods.\n- Accept origin-clean canvas, image, video and bitmap sources; document browser security boundaries for DOM backgrounds.\n- Add a dedicated glass-engine demo, integration guide, TypeScript API and smoke checks.\n- Keep the existing day/night switch renderer and all five scenes unchanged.\n\n [3.0.1] — Layered community scenes
2
+
3
+ - Add Manifest v2 with reusable pixel-matrix assets and ordered scene layers.
4
+ - Add bounded, deterministic drift and bob motion tracks without executing third-party code.
5
+ - Preserve the original Manifest v1 schema and registration behavior.
6
+ - Add a Floating Island example, v2 JSON Schema, TypeScript declarations and validation tests.
7
+
8
+
9
+ ## [3.0.0] — Community scene plugins
10
+
11
+ - Add a validated, data-only Scene Manifest v1 and public scene registry API.
12
+ - Render registered pixel-matrix scenes through the existing refractive glass renderer with interpolated 24-hour palettes.
13
+ - Expose scene registration from CommonJS and ESM package entry points.
14
+ - Add CDN registry loading, a Sakura Evening example, JSON Schema, documentation and automated tests.
15
+ - Preserve all five built-in scene implementations and their dedicated live animation systems.
16
+
17
+
18
+ All notable changes to Pixel Daylight Switch are documented here.
19
+
20
+ ## [Unreleased] — Developer integration
21
+
22
+ - Make the one-line embed responsive and prevent demo-page global CSS from leaking into host websites.
23
+ - Generate responsive, copy-ready integration snippets from the Playground.
24
+ - Add React and Vue 3 lifecycle integration recipes.
25
+ - Refresh `llms.txt` and add smoke/browser regression coverage for embed isolation.
26
+
27
+
28
+ ## [2.2.0] — Mobile scene controls
29
+
30
+ ### Mobile app experience
31
+ - Add a dedicated mobile app shell with five selectable scenes: countryside, Japanese street, apartment, seaside and mountain cabin.
32
+ - Add an optional snowfall control.
33
+ - Add live day/night status, device-language UI (English, Simplified Chinese and Japanese), accessible pressed states and compact portrait/landscape layouts.
34
+ - Reuse the shared renderer and preserve the glass shader, refraction, highlights, dispersion and drag interaction.
35
+
36
+ ### Documentation and verification
37
+ - Update English, Simplified Chinese and Japanese README files with the mobile controls.
38
+ - Extend mobile smoke checks for all five scene choices, weather control and renderer remount behavior.
39
+
40
+ ## [2.1.0] — Android visual and versioning fix
41
+
42
+ ### Android app
43
+ - Replace the warm yellow-beige native page background with a neutral cool gray.
44
+ - Match the Capacitor native window background and mobile theme color to the page background.
45
+ - Set Android versionName and versionCode from the package version so new APKs no longer appear as version 1.0.
46
+
47
+ ### Build reliability
48
+ - Apply package-driven Android version metadata after each generated Capacitor project.
49
+ - Keep the shared WebGL renderer and scene implementation unchanged.
50
+
51
+ ## [2.0.0] — Released
52
+
53
+ ### Android 与 iOS
54
+ - 引入 Capacitor 8 原生应用构建路径,支持 Android 与 iOS 共用现有 Web 渲染器。
55
+ - 新增独立移动端入口与全屏布局,处理 viewport、安全区域、横竖屏和触控拖动。
56
+ - 新增移动资源准备脚本与 smoke test,确保原生包使用与 Web 完全相同的 `app.js`。
57
+ - 新增 Android Debug APK 与 iOS Simulator 自动构建工作流。
58
+
59
+ ### 架构与兼容性
60
+ - Web 继续保持独立运行;不重写、不复制 WebGL shader、场景数据或交互物理逻辑。
61
+ - 原生 Android / iOS 工程按需由 Capacitor 生成,不把 IDE 派生文件提交到仓库。
62
+ - 本版本不包含已签名商店包;正式发布仍需开发者签名与商店配置。
63
+
64
+ ## [1.3.1]
65
+
66
+ ### 稳定性与生命周期
67
+ - 修复组件卸载时主动销毁 WebGL context,导致同一 canvas/root 后续重新挂载可能异常的问题。
68
+ - 保留 GPU 资源释放与事件监听器、观察器清理,同时允许 canvas 在卸载后重新挂载。
69
+
70
+ ### 自动化验证
71
+ - 增加 CommonJS、ESM 与 SSR 导入检查。
72
+ - 增加 Node.js 18、20、22 的 CI 兼容性检查。
73
+ - 增加 Chromium 浏览器回归:自动挂载、重复挂载/卸载、多实例、指针交互、事件监听器清理。
74
+ - 增加移动端触控、Playground 小屏布局与参数应用检查。
75
+ - 将浏览器回归纳入 npm 发布前置检查。
76
+
77
+ ## [1.3.0]
78
+
79
+ <details>
80
+ <summary>查看 v1.3.0 详细更新</summary>
81
+
82
+ ### 场景与世界
83
+ - 新增日本街道:店铺、暖帘、路灯、自动售货机、自行车与道路细节。
84
+ - 新增现代公寓:城市窗景、沙发、茶几、落地灯、室内植物与地毯。
85
+ - 新增海边:海面、沙滩、灯塔、帆船与海岸植被。
86
+ - 新增山间木屋:层叠山脉、针叶林、雪顶、木屋与烟囱。
87
+ - 保留乡村小屋作为默认场景。
88
+ - 新增 `data-scene` 场景属性,并在 Playground 中加入场景选择。
89
+
90
+ ### Playground 与参数组织
91
+ - 参数按用途拆分:滑动响应速度、惯性与回弹归入运动反馈;折射、边缘厚度、色散与高光归入玻璃光学;时间倍率单独归入世界节奏;场景、天气和初始昼夜归入场景环境。
92
+
93
+ ### 兼容性
94
+ - 默认仍为 `countryside`,既有接入方式不需要修改。
95
+ - 场景切换不改变开关物理、玻璃 shader 与昼夜时间逻辑。
96
+
97
+ </details>
98
+
99
+ ## [1.2.0]
100
+
101
+ <details>
102
+ <summary>查看 v1.2.0 详细更新</summary>
103
+
104
+ ### 参数与世界
105
+ - 新增 `data-friction`、`data-bounce`,分别调节惯性阻尼与边缘回弹。
106
+ - 新增 `data-dispersion`、`data-shine`,调节玻璃边缘色散与表面高光。
107
+ - 新增 `data-time-speed` 时间倍率,以及 `data-weather="snow"` 飘雪天气。
108
+
109
+ ### Playground
110
+ - 新增独立参数实验室,支持分组调节、预设和 HTML 代码复制。
111
+ - 重构实验室视觉,采用更克制的编辑式布局。
112
+ - 通过独立预览与双缓冲切换,减少快速调参时的 WebGL 重建与白屏。
113
+
114
+ ### 文档
115
+ - README 改为中文优先,并整理参数说明与英文文档。
116
+
117
+ ### 兼容性
118
+ - 新增参数均为可选项;未设置时保留原有默认行为。
119
+
120
+ </details>
121
+
122
+ ## [1.1.0]
123
+ - 新增动画速度、镜片折射、外框厚度和初始夜晚状态的 HTML 参数配置。
124
+
125
+ ## [1.0.0]
126
+ - 首个公开版本。
127
+
@@ -0,0 +1,65 @@
1
+ # Standalone Glass Engine
2
+
3
+ PixelGlassEngine extracts the project's refractive lens into a reusable WebGL2 overlay. It is independent of the day/night switch: it does not create a switch, impose an aspect ratio, or set a fixed component size.
4
+
5
+ ## Browser
6
+
7
+ Load `glass-engine.js`, then pass a source that the browser can upload to WebGL:
8
+
9
+ ```html
10
+ <div id="card" style="position:relative;width:100%;height:260px;overflow:hidden">
11
+ <canvas id="background" width="1200" height="520"></canvas>
12
+ </div>
13
+ <script src="https://gsvegetable.github.io/pixel-daylight-switch/glass-engine.js"></script>
14
+ <script>
15
+ const card = document.querySelector('#card');
16
+ const background = document.querySelector('#background');
17
+ const glass = PixelGlassEngine.createGlass(card, {
18
+ source: background,
19
+ width: '72%',
20
+ height: '68%',
21
+ left: '14%',
22
+ top: '16%',
23
+ radius: 32,
24
+ refraction: 1.24,
25
+ dispersion: 0.05,
26
+ bezel: 0.046,
27
+ shine: 0.44
28
+ });
29
+ // Later: glass.setOptions({ refraction: 1.32 });
30
+ // Before removing the card: glass.destroy();
31
+ </script>
32
+ ```
33
+
34
+ ## npm
35
+
36
+ ```js
37
+ import { createGlass } from 'pixel-daylight-switch-gsvegetable/glass-engine';
38
+
39
+ const glass = createGlass(container, { source: backgroundCanvas, width: '70%', height: 160, radius: 28 });
40
+ glass.setOptions({ dispersion: 0.08 });
41
+ glass.setSource(nextCanvasOrImage);
42
+ glass.destroy();
43
+ ```
44
+
45
+ ## Source contract and browser security
46
+
47
+ The source may be an HTMLCanvasElement, OffscreenCanvas, HTMLImageElement, HTMLVideoElement, ImageBitmap, or a function returning one of those objects. The source must be ready and origin-clean. Cross-origin images require appropriate CORS headers and `crossOrigin = "anonymous"` before assigning `src`.
48
+
49
+ A browser does not expose arbitrary DOM pixels as a WebGL texture. Therefore this engine does not claim to refract any arbitrary page behind an overlay automatically. For a live webpage, provide a canvas snapshot/render target from the host application (for example, the same canvas used by a chart, game, or custom renderer). A DOM-to-canvas adapter is application-specific and may be blocked by cross-origin content, video, fonts, or browser security.
50
+
51
+ ## Options
52
+
53
+ - `source`: canvas/image/video/bitmap or a callback returning one.
54
+ - `width`, `height`, `left`, `top`: numbers in CSS pixels or CSS length strings; no fixed aspect ratio.
55
+ - `radius`: number in CSS pixels or CSS length string.
56
+ - `refraction`: magnification factor; default `1.24`.
57
+ - `dispersion`: RGB edge separation, clamped to `0–0.25`.
58
+ - `bezel`: relative edge thickness, clamped to `0–0.3`.
59
+ - `shine`: highlight intensity.
60
+ - `opacity`: glass opacity from `0–1`.
61
+ - `ink`, `light`: RGB arrays in normalized `0–1`.\n- `shellDay`, `shellNight`, `hiNight`, `loNight`, `night`: day/night environment colors and blend amount, preserving the original renderer's lighting controls.
62
+ - `dpr`: render pixel-ratio ceiling, default `2`.
63
+ - `continuous`: redraw continuously for animated sources; set `false` for static sources and call `render()` when the source changes.
64
+
65
+ The renderer uses WebGL2. It throws a clear error if WebGL2 is unavailable rather than silently substituting a visually unrelated fake-refraction effect.
@@ -123,7 +123,7 @@ unregister();
123
123
 
124
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
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.
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
127
 
128
128
  ## Lifecycle and events
129
129
 
@@ -137,3 +137,24 @@ Manifest v1 is data-only: a bounded pixel matrix, named palettes and a 24-hour p
137
137
  ## Configuration
138
138
 
139
139
  See [README.md](README.md) for the complete list of `data-*` attributes, CSS custom properties, rendering behavior and supported scene identifiers.
140
+
141
+
142
+ ## Standalone refractive glass
143
+
144
+ The independent glass layer is exported as `createGlass` and does not mount or depend on the day/night switch:
145
+
146
+ ```js
147
+ import { createGlass } from 'pixel-daylight-switch-gsvegetable/glass-engine';
148
+ const glass = createGlass(container, {
149
+ source: backgroundCanvas,
150
+ width: '70%', height: 180, left: '15%', top: 24,
151
+ radius: 28, refraction: 1.24, dispersion: 0.05,
152
+ bezel: 0.046, shine: 0.44
153
+ });
154
+ // Update, swap source, and clean up independently.
155
+ glass.setOptions({ refraction: 1.32 });
156
+ glass.setSource(nextCanvas);
157
+ glass.destroy();
158
+ ```
159
+
160
+ See [the complete glass engine guide](GLASS_ENGINE.md) and [live parameter demo](https://gsvegetable.github.io/pixel-daylight-switch/demos/glass-engine.html). The source must be an origin-clean canvas, image, video, bitmap or callback returning one. Arbitrary DOM cannot be sampled directly by WebGL; host applications must provide a renderable source.
package/docs/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Project documentation
2
+
3
+ - [Framework integration recipes](INTEGRATIONS.md) — React, Vue and plain HTML integration.
4
+ - [Scene plugin authoring guide](SCENE_PLUGINS.md) — Manifest v2 assets, layers, motion and v1 compatibility.
5
+ - [Changelog](CHANGELOG.md) — release history.
@@ -1,10 +1,10 @@
1
1
  # Scene Plugin Guide
2
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.
3
+ The Pixel Daylight Switch community scene system supports 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
4
 
5
5
  ## Two ways to create a scene
6
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.
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
8
 
9
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
10
 
@@ -38,7 +38,7 @@ Load the registry before the renderer:
38
38
  <script src="https://gsvegetable.github.io/pixel-daylight-switch/app.js"></script>
39
39
  <div data-ld-part="toggle-daylight" data-scene="sakura-evening"></div>
40
40
  <script>
41
- PixelDaylightSwitch.registerScene(sceneManifest);
41
+ PixelDaylightSwitchScenePlugins.registerScene(sceneManifest);
42
42
  </script>
43
43
  ```
44
44
 
@@ -60,7 +60,7 @@ Required fields:
60
60
 
61
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
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).
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
64
 
65
65
  ## Layered scenes and pixel assets (Manifest v2)
66
66
 
@@ -74,7 +74,7 @@ A v2 manifest keeps the base `canvas`, `pixels`, `palettes` and `timeCycle`, and
74
74
 
75
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
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).
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
78
 
79
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
80
 
@@ -0,0 +1,162 @@
1
+ /*!
2
+ * Pixel Daylight Switch — standalone refractive glass engine
3
+ * Unlicense. The optical lens equations are adapted from the original app.js shader.
4
+ */
5
+ (function (root, factory) {
6
+ 'use strict';
7
+ const api = factory();
8
+ if (typeof module === 'object' && module.exports) module.exports = api;
9
+ if (root) root.PixelGlassEngine = api;
10
+ })(typeof globalThis !== 'undefined' ? globalThis : this, function () {
11
+ 'use strict';
12
+ const VERT = `#version 300 es
13
+ in vec2 aPos;
14
+ void main(){ gl_Position=vec4(aPos,0.0,1.0); }`;
15
+ const FRAG = `#version 300 es
16
+ precision highp float;
17
+ uniform sampler2D uSource;
18
+ uniform vec2 uResolution, uCenter, uHalf, uSourceSize;
19
+ uniform float uRadius, uRefraction, uDispersion, uBezel, uShine, uOpacity, uEdgeDark;
20
+ uniform vec3 uInk, uLight, uShellDay, uShellNight, uHiNight, uLoNight;\n uniform float uNight;\n vec3 envLow, envHigh;
21
+ out vec4 outColor;
22
+ float sdRoundBox(vec2 p, vec2 b, float r) {
23
+ vec2 q=abs(p)-b+vec2(r);
24
+ return length(max(q,0.0))+min(max(q.x,q.y),0.0)-r;
25
+ }
26
+ float softBox(vec2 p, vec2 c, vec2 h, float s) {
27
+ vec2 q=abs(p-c)-h;
28
+ float d=length(max(q,0.0))+min(max(q.x,q.y),0.0);
29
+ return 1.0-smoothstep(-s,s,d);
30
+ }
31
+ vec3 smoothTex(vec2 uv) {
32
+ vec2 st=uv*uSourceSize-0.5, i=floor(st), f=st-i, f2=f*f, f3=f2*f;
33
+ vec2 w0=(1.0-3.0*f+3.0*f2-f3)/6.0;
34
+ vec2 w1=(4.0-6.0*f2+3.0*f3)/6.0;
35
+ vec2 w2=(1.0+3.0*f+3.0*f2-3.0*f3)/6.0;
36
+ vec2 w3=f3/6.0, g0=w0+w1, g1=w2+w3;
37
+ vec2 h0=(i-0.5+w1/g0+0.5)/uSourceSize;
38
+ vec2 h1=(i+1.5+w3/g1+0.5)/uSourceSize;
39
+ return g0.y*(g0.x*texture(uSource,vec2(h0.x,h0.y)).rgb+g1.x*texture(uSource,vec2(h1.x,h0.y)).rgb)
40
+ +g1.y*(g0.x*texture(uSource,vec2(h0.x,h1.y)).rgb+g1.x*texture(uSource,vec2(h1.x,h1.y)).rgb);
41
+ }
42
+ vec3 studio(vec3 R) {
43
+ vec3 c=mix(envLow,envHigh,smoothstep(-0.55,0.85,R.y));
44
+ vec2 s=R.xy/(1.0+max(R.z,0.0));
45
+ float key=softBox(s,vec2(-0.1,0.56),vec2(0.5,0.11),0.17);
46
+ float fill=softBox(s,vec2(0.34,-0.6),vec2(0.3,0.05),0.2);
47
+ c+=envHigh*key*1.15;
48
+ c+=envHigh*fill*0.14;
49
+ return c;
50
+ }
51
+ void main(){
52
+ envLow=mix(uShellDay*0.74,uLoNight,uNight);
53
+ envHigh=mix(uLight,uHiNight,uNight);
54
+ vec2 frag=gl_FragCoord.xy;
55
+ vec2 p=frag-uCenter;
56
+ float d=sdRoundBox(p,uHalf,uRadius);
57
+ if(d>1.0){outColor=vec4(0.0);return;}
58
+ vec2 q=p;
59
+ vec2 inner=max(uHalf-uRadius,vec2(0.0));
60
+ q.x=sign(p.x)*max(abs(p.x)-inner.x,0.0);
61
+ q.y=sign(p.y)*max(abs(p.y)-inner.y,0.0);
62
+ float rad=length(q);
63
+ vec2 gr=q/max(rad,1.0e-3);
64
+ float rho=clamp(rad/max(uRadius,1.0),0.0,1.0);
65
+ float mag=max(uRefraction,1.001);
66
+ float bt=clamp((rho-0.7)/0.3,0.0,1.0);
67
+ float band=bt*bt*(3.0-2.0*bt);
68
+ float strength=1.0-1.0/mag+0.2;
69
+ float bend=pow(band,2.2)*uRadius*strength;
70
+ vec2 base=uCenter+p/mag;
71
+ vec2 shiftDir=gr*bend;
72
+ float ca=uDispersion*band;
73
+ vec2 uvR=(base+shiftDir*(1.0-ca))/uResolution;
74
+ vec2 uvG=(base+shiftDir)/uResolution;
75
+ vec2 uvB=(base+shiftDir*(1.0+ca))/uResolution;
76
+ vec3 seen;
77
+ if(band>0.02) seen=vec3(smoothTex(uvR).r,smoothTex(uvG).g,smoothTex(uvB).b);
78
+ else seen=smoothTex(uvG);
79
+ vec2 tilt=gr*(0.22*rho+0.78*pow(rho,7.0));
80
+ vec3 N=normalize(vec3(tilt,sqrt(max(1.0-dot(tilt,tilt),0.02))));
81
+ vec3 R=vec3(2.0*N.z*N.xy,2.0*N.z*N.z-1.0);
82
+ vec2 lp=q/max(uRadius,1.0);
83
+ vec3 lens=seen*1.035;
84
+ float edge=pow(rho,16.0);
85
+ lens=mix(lens,uInk,edge*0.42);
86
+ float pool=smoothstep(0.05,0.95,-lp.y*0.9+lp.x*0.2)*smoothstep(1.0,0.8,rho)*smoothstep(0.42,0.86,rho);
87
+ lens+=envHigh*pool*mix(0.3,0.1,uNight);
88
+ float broad=softBox(lp,vec2(-0.1,0.62),vec2(0.5,0.17),0.3)*smoothstep(0.98,0.72,rho);
89
+ lens=mix(lens,envHigh,broad*mix(uShine,0.1,uNight));
90
+ float fresnel=pow(1.0-N.z,3.0);
91
+ float dir=0.2+0.8*smoothstep(-0.6,0.85,dot(gr,normalize(vec2(-0.42,0.9))));
92
+ float mirror=clamp(fresnel*dir*dir*0.6,0.0,0.42);
93
+ if(mirror>1.0e-3) lens=mix(lens,studio(R),mirror);
94
+ float alpha=uOpacity*(1.0-smoothstep(0.0,1.0,d));
95
+ outColor=vec4(clamp(lens,0.0,1.0)*alpha,alpha);
96
+ }`;
97
+ const defaults={width:'100%',height:'100%',left:'0px',top:'0px',radius:'28px',refraction:1.24,dispersion:0.05,bezel:0.046,shine:0.44,opacity:1,edgeDark:0.42,ink:[0.08,0.1,0.16],light:[0.94,0.97,1],shellDay:[0.42,0.52,0.62],shellNight:[0.08,0.1,0.16],hiNight:[0.42,0.5,0.62],loNight:[0.06,0.08,0.13],night:0,dpr:2,continuous:true};
98
+ function createGlass(container, options) {
99
+ if(!container || !container.ownerDocument) throw new TypeError('createGlass(container): a DOM element is required');
100
+ const opts={...defaults,...(options||{})};
101
+ if(!opts.source) throw new TypeError('createGlass requires a renderable source (canvas, image, video, bitmap, or callback).');
102
+ const doc=container.ownerDocument;
103
+ const canvas=doc.createElement('canvas');
104
+ canvas.setAttribute('aria-hidden','true');
105
+ canvas.className='pixel-glass-engine';
106
+ canvas.style.cssText='position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:1;display:block;max-width:none;max-height:none;';
107
+ const computed=doc.defaultView.getComputedStyle(container);
108
+ const previousPosition=container.style.position;
109
+ if(computed.position==='static') container.style.position='relative';
110
+ container.appendChild(canvas);
111
+ const gl=canvas.getContext('webgl2',{alpha:true,premultipliedAlpha:true,antialias:false,depth:false,stencil:false,powerPreference:'high-performance'});
112
+ if(!gl){canvas.remove();if(previousPosition!==container.style.position)container.style.position=previousPosition;throw new Error('PixelGlassEngine requires WebGL2.');}
113
+ const compile=(type,src)=>{const s=gl.createShader(type);gl.shaderSource(s,src);gl.compileShader(s);if(!gl.getShaderParameter(s,gl.COMPILE_STATUS)){const msg=gl.getShaderInfoLog(s);gl.deleteShader(s);throw new Error(msg||'Shader compilation failed');}return s;};
114
+ let program,vao,buffer,texture,raf=0,destroyed=false,source=opts.source||null,resizeObserver=null,dim={w:1,h:1};
115
+ try{
116
+ program=gl.createProgram();const vs=compile(gl.VERTEX_SHADER,VERT),fs=compile(gl.FRAGMENT_SHADER,FRAG);
117
+ gl.attachShader(program,vs);gl.attachShader(program,fs);gl.bindAttribLocation(program,0,'aPos');gl.linkProgram(program);gl.deleteShader(vs);gl.deleteShader(fs);
118
+ if(!gl.getProgramParameter(program,gl.LINK_STATUS))throw new Error(gl.getProgramInfoLog(program)||'Shader link failed');
119
+ gl.useProgram(program);vao=gl.createVertexArray();gl.bindVertexArray(vao);buffer=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,buffer);gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,-1,3,-1,-1,3]),gl.STATIC_DRAW);gl.enableVertexAttribArray(0);gl.vertexAttribPointer(0,2,gl.FLOAT,false,0,0);
120
+ texture=gl.createTexture();gl.activeTexture(gl.TEXTURE0);gl.bindTexture(gl.TEXTURE_2D,texture);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.LINEAR);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE);gl.uniform1i(gl.getUniformLocation(program,'uSource'),0);
121
+ }catch(error){canvas.remove();throw error;}
122
+ const U={};for(const n of ['uResolution','uCenter','uHalf','uSourceSize','uRadius','uRefraction','uDispersion','uBezel','uShine','uOpacity','uEdgeDark','uInk','uLight','uShellDay','uShellNight','uHiNight','uLoNight','uNight'])U[n]=gl.getUniformLocation(program,n);
123
+ function resolveSource(){if(typeof source==='function')return source();return source;}
124
+ function dimensions(el){return {w:el?.videoWidth||el?.naturalWidth||el?.width||0,h:el?.videoHeight||el?.naturalHeight||el?.height||0};}
125
+ function draw(){
126
+ raf=0;if(destroyed)return;
127
+ const rect=container.getBoundingClientRect(),dpr=Math.min(Number(opts.dpr)||2,doc.defaultView.devicePixelRatio||1,2);
128
+ const W=Math.max(2,Math.round(rect.width*dpr)),H=Math.max(2,Math.round(rect.height*dpr));
129
+ if(!W||!H)return;
130
+ if(canvas.width!==W||canvas.height!==H){canvas.width=W;canvas.height=H;gl.viewport(0,0,W,H);}
131
+ const el=resolveSource();
132
+ if(!el){if(opts.background){gl.bindTexture(gl.TEXTURE_2D,texture);gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,1,1,0,gl.RGBA,gl.UNSIGNED_BYTE,new Uint8Array([245,236,217,255]));}else{if(opts.continuous)raf=doc.defaultView.requestAnimationFrame(draw);return;}}
133
+ else{
134
+ dim=dimensions(el);if(!dim.w||!dim.h){if(opts.continuous)raf=doc.defaultView.requestAnimationFrame(draw);return;}
135
+ gl.activeTexture(gl.TEXTURE0);gl.bindTexture(gl.TEXTURE_2D,texture);gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1);gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,el);
136
+ }
137
+ gl.clearColor(0,0,0,0);gl.clear(gl.COLOR_BUFFER_BIT);gl.useProgram(program);gl.bindVertexArray(vao);
138
+ const width=opts.width,height=opts.height,left=opts.left,top=opts.top;
139
+ const cssPx=(v,total)=>{if(typeof v==='number')return v;const value=String(v??'').trim();if(value.endsWith('%'))return (parseFloat(value)||0)*total/100;return parseFloat(value)||total;};
140
+ const w=cssPx(width,rect.width),h=cssPx(height,rect.height),x=cssPx(left,rect.width),y=cssPx(top,rect.height);
141
+ const cx=(x+w/2)*dpr,cy=H-(y+h/2)*dpr,halfX=w*dpr/2,halfY=h*dpr/2;
142
+ const radius=Math.min(cssPx(opts.radius,Math.min(w,h)*0.28)*dpr,halfX,halfY);
143
+ const bezel=Math.max(0.1,Math.min(0.3,Number(opts.bezel)||0.046))*Math.min(halfX,halfY);
144
+ gl.uniform2f(U.uResolution,W,H);gl.uniform2f(U.uCenter,cx,cy);gl.uniform2f(U.uHalf,halfX,halfY);gl.uniform2f(U.uSourceSize,dim?.w||W,dim?.h||H);
145
+ gl.uniform1f(U.uRadius,radius);gl.uniform1f(U.uRefraction,Math.max(1.001,Number(opts.refraction)||1.24));gl.uniform1f(U.uDispersion,Math.max(0,Math.min(.25,Number(opts.dispersion)||0)));gl.uniform1f(U.uBezel,bezel);gl.uniform1f(U.uShine,Math.max(0,Number(opts.shine)||0));gl.uniform1f(U.uOpacity,Math.max(0,Math.min(1,Number(opts.opacity)??1)));gl.uniform1f(U.uEdgeDark,Math.max(0,Math.min(1,Number(opts.edgeDark)??.42)));gl.uniform3fv(U.uInk,new Float32Array(opts.ink||defaults.ink));gl.uniform3fv(U.uLight,new Float32Array(opts.light||defaults.light));gl.uniform3fv(U.uShellDay,new Float32Array(opts.shellDay||defaults.shellDay));gl.uniform3fv(U.uShellNight,new Float32Array(opts.shellNight||defaults.shellNight));gl.uniform3fv(U.uHiNight,new Float32Array(opts.hiNight||defaults.hiNight));gl.uniform3fv(U.uLoNight,new Float32Array(opts.loNight||defaults.loNight));gl.uniform1f(U.uNight,Math.max(0,Math.min(1,Number(opts.night)||0)));
146
+ gl.drawArrays(gl.TRIANGLES,0,3);
147
+ if(opts.continuous)raf=doc.defaultView.requestAnimationFrame(draw);
148
+ }
149
+ function schedule(){if(!raf&&!destroyed)raf=doc.defaultView.requestAnimationFrame(draw);}
150
+ resizeObserver=typeof ResizeObserver!=='undefined'?new ResizeObserver(schedule):null;resizeObserver?.observe(container);
151
+ if(source&&typeof source==='object'&&'addEventListener'in source){source.addEventListener('loadeddata',schedule);source.addEventListener('resize',schedule);}
152
+ schedule();
153
+ return {
154
+ canvas,
155
+ setSource(next){source=next;schedule();return this;},
156
+ setOptions(next){Object.assign(opts,next||{});schedule();return this;},
157
+ render(){if(raf)doc.defaultView.cancelAnimationFrame(raf);raf=0;draw();return this;},
158
+ destroy(){if(destroyed)return;destroyed=true;if(raf)doc.defaultView.cancelAnimationFrame(raf);resizeObserver?.disconnect();if(source&&typeof source==='object'&&'removeEventListener'in source){source.removeEventListener('loadeddata',schedule);source.removeEventListener('resize',schedule);}gl.deleteTexture(texture);gl.deleteBuffer(buffer);gl.deleteVertexArray(vao);gl.deleteProgram(program);canvas.remove();if(previousPosition!==container.style.position&&container.style.position==='relative')container.style.position=previousPosition;}
159
+ };
160
+ }
161
+ return {createGlass,version:'3.1.1'};
162
+ });
@@ -0,0 +1,4 @@
1
+ import './glass-engine.js';
2
+ const createGlass = globalThis.PixelGlassEngine.createGlass;
3
+ export { createGlass };
4
+ export default globalThis.PixelGlassEngine;
package/index.cjs CHANGED
@@ -8,4 +8,5 @@ api.registerScene = scenes.registerScene;
8
8
  api.validateScene = scenes.validateScene;
9
9
  api.getScene = scenes.getScene;
10
10
  api.listScenes = scenes.listScenes;
11
+ api.createGlass = require('./glass-engine.js').createGlass;
11
12
  module.exports = api;
package/index.d.ts CHANGED
@@ -54,6 +54,7 @@ export interface PixelDaylightSwitchAPI {
54
54
  validateScene(manifest: unknown): SceneValidationResult;
55
55
  getScene(id: string): SceneManifest | null;
56
56
  listScenes(): SceneManifest[];
57
+ createGlass: typeof createGlass;
57
58
  }
58
59
  declare const PixelDaylightSwitch: PixelDaylightSwitchAPI;
59
60
  export declare const mount: PixelDaylightSwitchAPI['mount'];
@@ -63,3 +64,22 @@ export declare const validateScene: PixelDaylightSwitchAPI['validateScene'];
63
64
  export declare const getScene: PixelDaylightSwitchAPI['getScene'];
64
65
  export declare const listScenes: PixelDaylightSwitchAPI['listScenes'];
65
66
  export default PixelDaylightSwitch;
67
+
68
+ export type GlassSource = HTMLCanvasElement | OffscreenCanvas | HTMLImageElement | HTMLVideoElement | ImageBitmap | (() => GlassSource);
69
+ export interface GlassEngineOptions {
70
+ source?: GlassSource;
71
+ width?: number | string; height?: number | string;
72
+ left?: number | string; top?: number | string;
73
+ radius?: number | string; refraction?: number; dispersion?: number;
74
+ bezel?: number; shine?: number; opacity?: number; edgeDark?: number;
75
+ ink?: [number, number, number]; light?: [number, number, number];\n shellDay?: [number, number, number]; shellNight?: [number, number, number];\n hiNight?: [number, number, number]; loNight?: [number, number, number]; night?: number;
76
+ dpr?: number; continuous?: boolean;
77
+ }
78
+ export interface GlassEngineInstance {
79
+ canvas: HTMLCanvasElement;
80
+ setSource(source: GlassSource): this;
81
+ setOptions(options: Partial<GlassEngineOptions>): this;
82
+ render(): this;
83
+ destroy(): void;
84
+ }
85
+ export declare function createGlass(container: HTMLElement, options?: GlassEngineOptions): GlassEngineInstance;
package/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import api from './index.cjs';
2
+ import { createGlass } from './glass-engine.mjs';
2
3
 
3
4
  export const mount = api.mount;
4
5
  export const unmount = api.unmount;
@@ -6,4 +7,5 @@ export const registerScene = api.registerScene;
6
7
  export const validateScene = api.validateScene;
7
8
  export const getScene = api.getScene;
8
9
  export const listScenes = api.listScenes;
10
+ export { createGlass };
9
11
  export default api;
package/llms.txt CHANGED
@@ -42,11 +42,17 @@ CommonJS: `const PixelDaylightSwitch = require('pixel-daylight-switch-gsvegetabl
42
42
 
43
43
  The package is safe to import during SSR; mount only after the target DOM element exists. `mount(element)` is idempotent and returns a cleanup function on success (or null on failure). `unmount(element)` stops rendering and removes observers/listeners; it does not remove the DOM root or forcibly invalidate the canvas context.
44
44
 
45
- Framework lifecycle examples are in [INTEGRATIONS.md](INTEGRATIONS.md). Scene plugin authoring, manifest constraints and API examples are in [SCENE_PLUGINS.md](SCENE_PLUGINS.md).
45
+ Framework lifecycle examples are in [INTEGRATIONS.md](docs/INTEGRATIONS.md). Scene plugin authoring, manifest constraints and API examples are in [SCENE_PLUGINS.md](docs/SCENE_PLUGINS.md).
46
46
 
47
- ## Community scene plugins (v3.0.0)
47
+ ## Standalone refractive glass engine (v3.1.1)
48
48
 
49
- The optional `scene-plugin.js` registry exposes `registerScene(manifest)`, `validateScene(manifest)`, `getScene(id)` and `listScenes()`. Load the registry before `app.js` in a browser, or import `registerScene` from the npm package. Register a scene before mounting its root. Manifest v1 uses a 1–192 by 1–112 printable-ASCII pixel map, up to 8 named palettes, and a 24-hour timeline that starts at 0 and ends at 24. Every used pixel symbol must have a valid `#RGB` or `#RRGGBB` color in every palette. Data is copied and deeply frozen on registration. Arbitrary plugin JavaScript is never executed. External image/sprite assets, layered tile maps and custom animation tracks are not supported in v1; they are reserved for a future schema extension. See [SCENE_PLUGINS.md](SCENE_PLUGINS.md), the [v1 JSON Schema](schemas/scene-manifest.v1.schema.json), and the [Sakura example](examples/scenes/sakura-evening.json).
49
+ Import `createGlass` from `pixel-daylight-switch-gsvegetable/glass-engine`, or load `glass-engine.js` directly and use `PixelGlassEngine.createGlass(container, options)`. The independent WebGL2 overlay has configurable width, height, position, radius, refraction, dispersion, bezel, shine, opacity, source and lifecycle methods. Sources are origin-clean canvas, image, video, ImageBitmap or a callback returning one. WebGL cannot sample arbitrary DOM pixels; host applications must supply a renderable background source. See [GLASS_ENGINE.md](docs/GLASS_ENGINE.md) and [the demo](https://gsvegetable.github.io/pixel-daylight-switch/demos/glass-engine.html).
50
+
51
+ ## Community scene plugins (current: Manifest v2)
52
+
53
+ The optional `scene-plugin.js` registry exposes `registerScene(manifest)`, `validateScene(manifest)`, `getScene(id)` and `listScenes()`. Register a data-only JSON manifest before mounting the scene. Manifest v2 supports up to 24 reusable pixel-matrix assets, up to 16 ordered layers, transparent overlay pixels (.), and bounded deterministic drift/bob motion tracks. Manifest v1 remains supported and its schema is unchanged. Every symbol in the base matrix and assets must be defined in each palette. Registration validates, copies and freezes the data; arbitrary JavaScript, remote image downloads and executable callbacks are not run.
54
+
55
+ For npm, import `registerScene` from the package. In a browser, load `scene-plugin.js` before `app.js` and register the manifest before mounting. See [the scene authoring guide](docs/SCENE_PLUGINS.md), [Manifest v2 Schema](schemas/scene-manifest.v2.schema.json), [v1 compatibility schema](schemas/scene-manifest.v1.schema.json) and [Floating Island v2 example](examples/scenes/floating-island.v2.json).
50
56
 
51
57
  ## Scenes and weather
52
58
 
@@ -93,30 +99,19 @@ Invalid values fall back to defaults; documented numeric ranges are clamped.
93
99
  ## Links
94
100
 
95
101
  - [Live demo](https://gsvegetable.github.io/pixel-daylight-switch/)
96
- - [Interactive Playground](https://gsvegetable.github.io/pixel-daylight-switch/playground.html)
97
- - [Preview](https://gsvegetable.github.io/pixel-daylight-switch/preview.html)
98
- - [Integration recipes](INTEGRATIONS.md)
102
+ - [Interactive Playground](https://gsvegetable.github.io/pixel-daylight-switch/demos/playground.html)
103
+ - [Preview](https://gsvegetable.github.io/pixel-daylight-switch/demos/preview.html)
104
+ - [Integration recipes](docs/INTEGRATIONS.md)
99
105
  - [English README](README.md) · [简体中文](README.zh-CN.md) · [日本語](README.ja.md)
100
- - [Changelog](CHANGELOG.md)
101
-
102
-
103
- ## Scene plugins (v3.0.0)
104
-
105
- - Public API: registerScene(manifest), validateScene(manifest), getScene(id), listScenes().
106
- - Manifest schema: schemas/scene-manifest.v1.schema.json.
107
- - Example: examples/scenes/sakura-evening.json.
108
- - Manifest v1: printable-ASCII pixel matrix (up to 192x112), named palettes, 24-hour palette interpolation.
109
- - Scene registration is data-only; arbitrary plugin JavaScript is not executed.
110
- - Browser script order: scene-plugin.js, register the manifest, then app.js. The one-line embed loader loads the registry before the renderer.
111
- - Existing built-in scenes and their custom live animation code remain unchanged.
106
+ - [Changelog](docs/CHANGELOG.md)
112
107
 
113
108
 
114
- ## Scene Manifest v2 (v3.0.1)
109
+ ## Scene plugins — current Manifest v2
115
110
 
116
- - Manifest v1 remains supported and its schema is unchanged.
117
- - Manifest v2 adds up to 24 reusable pixel-matrix assets and up to 16 ordered layers.
118
- - Layers reference named assets and integer x/y positions; '.' is transparent in overlay assets.
119
- - Optional deterministic motion tracks: drift or bob, on x or y, bounded speed/distance/phase.
120
- - Every symbol across the base matrix and assets must be defined in every palette.
121
- - Schema: schemas/scene-manifest.v2.schema.json. Example: examples/scenes/floating-island.v2.json.
111
+ - Current schema: `schemas/scene-manifest.v2.schema.json`.
112
+ - Example: `examples/scenes/floating-island.v2.json`.
113
+ - Reusable assets: up to 24 pixel matrices; ordered layers: up to 16.
114
+ - Overlay dot (.) is transparent; later layers appear above earlier layers.
115
+ - Motion tracks: bounded deterministic `drift` and `bob`, on x or y.
116
+ - Manifest v1 remains backward compatible; its schema is retained.
122
117
  - No arbitrary remote image loading, eval, or executable plugin callbacks.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pixel-daylight-switch-gsvegetable",
3
- "version": "3.0.1",
4
- "description": "A draggable pixel-art day/night switch with layered, animated community scenes.",
3
+ "version": "3.1.1",
4
+ "description": "A pixel-art day/night switch plus a standalone reusable WebGL2 refractive glass engine.",
5
5
  "keywords": [
6
6
  "pixel-art",
7
7
  "day-night",
@@ -17,7 +17,11 @@
17
17
  "scene-plugin",
18
18
  "pixel-world",
19
19
  "scene-manifest",
20
- "pixel-art-scenes"
20
+ "pixel-art-scenes",
21
+ "refractive-glass",
22
+ "glass-engine",
23
+ "webgl2-glass",
24
+ "lens-shader"
21
25
  ],
22
26
  "author": "GSvegetable",
23
27
  "license": "Unlicense",
@@ -44,6 +48,11 @@
44
48
  "./scene-plugin": {
45
49
  "import": "./scene-plugin.js",
46
50
  "require": "./scene-plugin.js"
51
+ },
52
+ "./glass-engine": {
53
+ "types": "./index.d.ts",
54
+ "import": "./glass-engine.mjs",
55
+ "require": "./glass-engine.js"
47
56
  }
48
57
  },
49
58
  "files": [
@@ -53,13 +62,17 @@
53
62
  "README.md",
54
63
  "LICENSE",
55
64
  "llms.txt",
56
- "INTEGRATIONS.md",
57
- "SCENE_PLUGINS.md",
65
+ "docs/INTEGRATIONS.md",
66
+ "docs/SCENE_PLUGINS.md",
58
67
  "scene-plugin.js",
59
68
  "schemas",
60
69
  "examples",
61
70
  "browser.mjs",
62
- "index.d.ts"
71
+ "index.d.ts",
72
+ "docs/CHANGELOG.md",
73
+ "glass-engine.js",
74
+ "docs/GLASS_ENGINE.md",
75
+ "glass-engine.mjs"
63
76
  ],
64
77
  "sideEffects": [
65
78
  "./app.js"
@@ -71,8 +84,8 @@
71
84
  "access": "public"
72
85
  },
73
86
  "scripts": {
74
- "test": "node tests/package-smoke.cjs && node tests/scene-plugin-smoke.cjs && node tests/mobile-smoke.cjs && node tests/embed-smoke.cjs",
75
- "test:browser": "playwright test",
87
+ "test": "node tests/package-smoke.cjs && node tests/scene-plugin-smoke.cjs && node tests/mobile-smoke.cjs && node tests/embed-smoke.cjs && node tests/glass-engine-smoke.cjs",
88
+ "test:browser": "playwright test --config config/playwright.config.cjs",
76
89
  "mobile:prepare": "node scripts/prepare-mobile.cjs",
77
90
  "mobile:android": "npm run mobile:prepare && cap sync android && node scripts/set-android-version.cjs android/app/build.gradle",
78
91
  "mobile:ios": "npm run mobile:prepare && cap sync ios",