pixel-daylight-switch-gsvegetable 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,591 +1,75 @@
1
1
  # Pixel Daylight Switch
2
2
 
3
- <p align="center">
4
- <strong>A tiny pixel world inside a draggable glass day/night switch.</strong>
5
- </p>
3
+ [![Quality checks](https://github.com/GSvegetable/pixel-daylight-switch/actions/workflows/ci.yml/badge.svg)](https://github.com/GSvegetable/pixel-daylight-switch/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/pixel-daylight-switch-gsvegetable)](https://www.npmjs.com/package/pixel-daylight-switch-gsvegetable)
5
+ [![License: Unlicense](https://img.shields.io/badge/license-Unlicense-blue.svg)](LICENSE)
6
6
 
7
- <p align="center">
8
- <a href="https://gsvegetable.github.io/pixel-daylight-switch/">Live Demo</a> ·
9
- <a href="https://gsvegetable.github.io/pixel-daylight-switch/playground.html">Interactive Playground</a> ·
10
- <a href="https://github.com/GSvegetable/pixel-daylight-switch/releases">Releases</a> ·
11
- <a href="https://www.npmjs.com/package/pixel-daylight-switch-gsvegetable">NPM</a>
12
- </p>
7
+ **Languages:** [English](README.md) · [简体中文](README.zh-CN.md) · [日本語](README.ja.md)
13
8
 
14
- <p align="center">
15
- <img src="https://github.com/user-attachments/assets/f7f248a5-c39c-432c-92f6-73237abdfe6b" width="49%" alt="Pixel Daylight Switch in daytime" />
16
- <img src="https://github.com/user-attachments/assets/b2e24e65-a378-4ac6-be7b-efd3070e4da8" width="49%" alt="Pixel Daylight Switch at night" />
17
- </p>
9
+ ![Five animated pixel-world scenes](assets/scene-gallery.png)
18
10
 
19
- <p align="center">
20
- <em>Drag the glass control. Watch a small world move from day to dusk to night.</em>
21
- </p>
11
+ *A tiny animated world, held inside a refractive glass switch.*
22
12
 
23
- **中文文档在前。** [English documentation](#english)
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.
24
14
 
25
- ---
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)**
26
16
 
27
- ## 目录
17
+ ## Start here
28
18
 
29
- - [项目简介](#项目简介)
30
- - [快速开始](#快速开始)
31
- - [Android 与 iOS 应用](#android-与-ios-应用)
32
- - [NPM 安装](#npm-安装)
33
- - [框架集成](#框架集成)
34
- - [参数实验室 Playground](#参数实验室-playground)
35
- - [配置参数](#配置参数)
36
- - [交互与事件](#交互与事件)
37
- - [自定义主题](#自定义主题)
38
- - [动态挂载与卸载](#动态挂载与卸载)
39
- - [渲染与兼容性](#渲染与兼容性)
40
- - [项目目录结构](#项目目录结构)
41
- - [本地开发与测试](#本地开发与测试)
42
- - [版本记录](#版本记录)
43
- - [许可证](#许可证)
44
-
45
- ## 项目简介
46
-
47
- **Pixel Daylight Switch** 是一个可拖动的昼夜切换组件。它把一个持续变化的像素世界放进玻璃开关中:拖动滑块,场景会经过白天、黄昏并进入夜晚;点击或使用键盘也可以切换昼夜。
48
-
49
- ### 特性
50
-
51
- - **像素世界**:场景中有持续活动的像素角色与环境细节。
52
- - **玻璃质感**:使用 WebGL2 绘制折射玻璃效果。
53
- - **五种场景**:乡村小屋、日本街道、现代公寓、海边灯塔、山间木屋。
54
- - **昼夜过渡**:不是简单的二态换色,而是连续的昼夜变化。
55
- - **可配置**:通过 HTML `data-*` 属性配置运动、玻璃、天气和场景。
56
- - **多实例**:同一页面可以放置多个独立开关。
57
- - **轻量接入**:原生脚本无需构建工具或运行时依赖。
58
- - **无障碍支持**:提供 switch 语义、键盘操作,并尊重减少动态效果偏好。
59
-
60
- ### 五种场景一览
61
-
62
- <p align="center">
63
- <img src="./assets/scene-gallery.png" alt="Pixel Daylight Switch — five pixel-art scenes: countryside, Japanese street, apartment, seaside, and mountain cabin" width="100%" />
64
- </p>
65
-
66
- ## 快速开始
67
-
68
- 不使用 NPM,也不需要安装依赖。把容器和脚本放进 HTML:
69
-
70
- ```html
71
- <div data-ld-part="toggle-daylight"></div>
72
-
73
- <script
74
- defer
75
- src="https://gsvegetable.github.io/pixel-daylight-switch/app.js"
76
- ></script>
77
- ```
78
-
79
- 脚本会自动为每个匹配的容器创建 Canvas、交互按钮和状态播报元素。容器宽度会自适应父级,组件保持约 **1.6439:1** 的宽高比。
80
-
81
- 如果需要自行托管,可以下载仓库中的 `app.js`,将脚本地址替换为自己的静态资源地址。
82
-
83
- ## Android 与 iOS 应用
84
-
85
- 从 **v2.0.0** 开始,项目提供 Capacitor 原生应用构建路径。Web 版本继续独立运行;Android 与 iOS 使用同一份核心渲染器,不复制或重写场景、WebGL shader 和交互物理逻辑。
86
-
87
- ### 技术结构
88
-
89
- - **共享核心**:根目录的 `app.js` 与 `style.css` 是唯一渲染实现。
90
- - **原生容器**:Capacitor 8 将同一套 Web 资源装入 Android WebView 和 iOS WKWebView。
91
- - **移动外壳**:`mobile/` 只负责全屏布局、安全区域和移动端 viewport,不包含第二套场景引擎。
92
- - **构建产物**:`mobile/www/` 由脚本生成并被 Git 忽略,避免提交重复的渲染资源。
93
-
94
- ### 环境要求
95
-
96
- - Node.js 22+
97
- - Android:Android Studio、Android SDK、JDK 21
98
- - iOS:macOS、Xcode 26+;需要在 Mac 上构建和签名发布包
99
-
100
- Capacitor 8 的平台最低版本为 Android 7.0(API 24)和 iOS 15。具体要求以 [Capacitor 环境文档](https://capacitorjs.com/docs/getting-started/environment-setup) 为准。
101
-
102
- ### 初始化原生工程
103
-
104
- 先安装依赖并生成 Web 资源:
105
-
106
- ```bash
107
- npm install
108
- npm run mobile:prepare
109
- ```
110
-
111
- 首次创建 Android / iOS 工程(每个平台只需初始化一次):
112
-
113
- ```bash
114
- npm run mobile:android:init
115
- npm run mobile:ios:init
116
- ```
117
-
118
- 同步后续 Web 资源变更:
119
-
120
- ```bash
121
- npm run mobile:android
122
- npm run mobile:ios
123
- ```
124
-
125
- 在 Android Studio 或 Xcode 中打开工程:
126
-
127
- ```bash
128
- npm run mobile:open:android
129
- npm run mobile:open:ios
130
- ```
131
-
132
- 原生工程目录 `android/` 与 `ios/` 是本地生成目录,不纳入 Git。这样可以避免提交大量 IDE 生成文件;CI 会从锁定的 Capacitor 主版本配置重新生成工程并验证构建。
133
-
134
- ### 自动构建
135
-
136
- `.github/workflows/mobile-build.yml` 提供 Android Debug APK 与 iOS Simulator 构建。可在 GitHub Actions 手动运行;推送 `v2.*` 标签也会触发构建。Android APK 会作为 Actions artifact 保存。iOS Simulator 构建用于验证编译,不等同于已签名、可提交 App Store 的 IPA。
137
-
138
- **发布边界:** App Store / Google Play 上架仍需要开发者账号、应用图标与商店素材、签名证书及隐私合规信息。此仓库的 CI 不会伪造签名或自动代表开发者提交商店。
139
-
140
- ## NPM 安装
141
-
142
- ```bash
143
- npm install pixel-daylight-switch-gsvegetable
144
- ```
145
-
146
- ### 原生 JavaScript / ESM
147
-
148
- ```js
149
- import PixelDaylightSwitch from 'pixel-daylight-switch-gsvegetable';
150
-
151
- const root = document.querySelector(
152
- '[data-ld-part="toggle-daylight"]'
153
- );
154
-
155
- // 页面加载后动态创建的元素需要手动挂载
156
- const destroy = PixelDaylightSwitch.mount(root);
157
-
158
- // 不再使用时,在移除元素前卸载
159
- PixelDaylightSwitch.unmount(root);
160
- ```
161
-
162
- 也可以使用命名导入:
163
-
164
- ```js
165
- import { mount, unmount } from 'pixel-daylight-switch-gsvegetable';
166
-
167
- mount(root);
168
- unmount(root);
169
- ```
170
-
171
- ### CommonJS
172
-
173
- ```js
174
- const PixelDaylightSwitch = require('pixel-daylight-switch-gsvegetable');
175
-
176
- PixelDaylightSwitch.mount(root);
177
- ```
178
-
179
- 包同时提供 ESM 和 CommonJS 入口。模块可以在 SSR 环境中导入;DOM 挂载只会在浏览器中执行。对于服务端渲染项目,请在客户端生命周期中调用 `mount()`。
180
-
181
- ## 框架集成
182
-
183
- ### React
184
-
185
- ```jsx
186
- import { useEffect, useRef } from 'react';
187
- import PixelDaylightSwitch from 'pixel-daylight-switch-gsvegetable';
188
-
189
- export function DaylightSwitch() {
190
- const rootRef = useRef(null);
191
-
192
- useEffect(() => {
193
- const root = rootRef.current;
194
- if (!root) return;
195
-
196
- PixelDaylightSwitch.mount(root);
197
- return () => PixelDaylightSwitch.unmount(root);
198
- }, []);
199
-
200
- return <div ref={rootRef} data-ld-part="toggle-daylight" />;
201
- }
202
- ```
203
-
204
- ### Vue 3
205
-
206
- ```vue
207
- <script setup>
208
- import { ref, onMounted, onBeforeUnmount } from 'vue';
209
- import PixelDaylightSwitch from 'pixel-daylight-switch-gsvegetable';
210
-
211
- const root = ref(null);
212
-
213
- onMounted(() => {
214
- PixelDaylightSwitch.mount(root.value);
215
- });
216
-
217
- onBeforeUnmount(() => {
218
- PixelDaylightSwitch.unmount(root.value);
219
- });
220
- </script>
221
-
222
- <template>
223
- <div ref="root" data-ld-part="toggle-daylight"></div>
224
- </template>
225
- ```
226
-
227
- ### Svelte
228
-
229
- ```svelte
230
- <script>
231
- import { onMount } from 'svelte';
232
- import PixelDaylightSwitch from 'pixel-daylight-switch-gsvegetable';
233
-
234
- let root;
235
-
236
- onMount(() => {
237
- PixelDaylightSwitch.mount(root);
238
- return () => PixelDaylightSwitch.unmount(root);
239
- });
240
- </script>
241
-
242
- <div bind:this={root} data-ld-part="toggle-daylight"></div>
243
- ```
244
-
245
- > **生命周期原则:** 对于框架或路由动态创建的容器,在元素进入 DOM 后调用 `mount(element)`;在元素被移除前调用 `unmount(element)`。不要在同一个容器上重复创建自己的实例。
246
-
247
- ## 参数实验室 Playground
248
-
249
- 打开 [Pixel Daylight Playground](https://gsvegetable.github.io/pixel-daylight-switch/playground.html),可以在浏览器中试验组件配置。
250
-
251
- - 拖动预览区中的开关,直接体验交互。
252
- - 按运动反馈、玻璃光学、世界节奏和场景环境调整参数。
253
- - 在五种像素场景之间切换。
254
- - 选择天气、初始昼夜状态与世界时间倍率。
255
- - 先调整参数,再点击「应用参数」更新预览。
256
- - 复制生成的 HTML 配置,放入自己的项目。
257
-
258
- Playground 是独立页面。它用于探索配置,不是组件运行时的依赖。
259
-
260
- ## 配置参数
261
-
262
- 所有配置都写在组件根元素的 HTML `data-*` 属性上。每个实例可以使用不同参数;未填写的参数使用默认值。
263
-
264
- ### 完整参数表
265
-
266
- | HTML 属性 | 默认值 | 说明 |
267
- | --- | ---: | --- |
268
- | `data-speed` | `9.5` | 开关运动的基础响应速度,必须为正数。 |
269
- | `data-refraction` | `1.24` | 玻璃镜片折射强度,必须为正数。 |
270
- | `data-bezel` | `0.046` | 玻璃边缘厚度系数,必须为正数。 |
271
- | `data-start-night` | `false` | 精确设置为 `true` 时,从夜晚状态开始。 |
272
- | `data-friction` | `0.6` | 松手后的惯性阻尼,范围 `0.1–0.9`;值越低,滑动越持久。 |
273
- | `data-bounce` | `3.2` | 边缘回弹力度,范围 `0–12`。 |
274
- | `data-dispersion` | `0.05` | 玻璃边缘 RGB 色散强度,范围 `0–0.25`;`0` 关闭色散。 |
275
- | `data-shine` | `0.44` | 白天玻璃表面高光强度,范围 `0–1.5`。 |
276
- | `data-time-speed` | `1` | 像素世界时间倍率,范围 `0–8`;`0` 冻结世界动态,`2` 为两倍速度。 |
277
- | `data-weather` | `default` | 天气预设:`default` 或 `snow`。 |
278
- | `data-scene` | `countryside` | 场景:`countryside`、`japanese-street`、`apartment`、`seaside`、`mountain-cabin`。 |
279
-
280
- ### 场景选项
281
-
282
- | 值 | 场景 |
19
+ | Goal | Direct link |
283
20
  | --- | --- |
284
- | `countryside` | 乡村小屋(默认) |
285
- | `japanese-street` | 日本街道 |
286
- | `apartment` | 现代公寓 |
287
- | `seaside` | 海边灯塔 |
288
- | `mountain-cabin` | 山间木屋 |
289
-
290
- ### 完整配置示例
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) |
24
+ | Add it to a website with one script | [Embed loader source](embed.js) |
25
+ | Use it in a JavaScript project | [npm package](https://www.npmjs.com/package/pixel-daylight-switch-gsvegetable) |
26
+ | Read implementation details | [Runtime](app.js) · [Styles](style.css) |
27
+ | 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
+ | Check tests | [CI workflow](https://github.com/GSvegetable/pixel-daylight-switch/actions/workflows/ci.yml) |
29
+ | Review changes | [Changelog](CHANGELOG.md) |
30
+ | Report a bug | [Issue tracker](https://github.com/GSvegetable/pixel-daylight-switch/issues) |
31
+
32
+ ## Features
33
+
34
+ - Five scenes: countryside, Japanese street, modern apartment, seaside lighthouse and mountain cabin.
35
+ - Refractive glass rendering with WebGL2 and a Canvas 2D fallback.
36
+ - Continuous drag interaction, tap/click switching and keyboard operation.
37
+ - Configurable motion, inertia, bounce, refraction, glass details, world speed, weather and scene.
38
+ - Multiple independent instances on one page.
39
+ - Switch semantics, live status announcements and `prefers-reduced-motion` support.
40
+ - `mount()` and `unmount()` lifecycle methods for dynamic applications.
41
+ - Rendering pauses when the document is hidden or the component is outside the viewport.
42
+
43
+ ## Quick start — browser, no build step
44
+
45
+ Place a mount point on your page and load the runtime:
291
46
 
292
47
  ```html
293
- <div
294
- data-ld-part="toggle-daylight"
295
- data-label="昼夜环境切换"
296
- data-speed="12"
297
- data-refraction="1.5"
298
- data-bezel="0.05"
299
- data-start-night="true"
300
- data-friction="0.45"
301
- data-bounce="4.2"
302
- data-dispersion="0.12"
303
- data-shine="0.6"
304
- data-time-speed="1.5"
305
- data-weather="snow"
306
- data-scene="mountain-cabin"
307
- ></div>
48
+ <div data-ld-part="toggle-daylight" style="width: min(100%, 800px);">
49
+ </div>
308
50
 
309
- <script
310
- defer
311
- src="https://gsvegetable.github.io/pixel-daylight-switch/app.js"
312
- ></script>
51
+ <script defer src="https://gsvegetable.github.io/pixel-daylight-switch/app.js"></script>
313
52
  ```
314
53
 
315
- ### 参数校验规则
316
-
317
- - 数值参数为空、不是有限数字或不符合正数要求时,会回退到默认值。
318
- - `friction`、`bounce`、`dispersion`、`shine` 和 `time-speed` 会限制在表格所示范围内。
319
- - 未知的 `weather` 值回退为 `default`。
320
- - 未知的 `scene` 值回退为 `countryside`。
321
- - `data-start-night` 只有值为字符串 `true` 时才启用。
54
+ The browser runtime finds the mount point and creates the component UI. The host element needs a usable width.
322
55
 
323
- ## 交互与事件
56
+ ### Embed with one script
324
57
 
325
- ### 用户交互
326
-
327
- - **拖动**:连续控制昼夜进度。
328
- - **点击**:在白天和夜晚之间切换。
329
- - **键盘**:聚焦开关后可使用方向键操作。
330
- - **无障碍**:交互按钮使用 `role="switch"` 与 `aria-checked`;状态变化通过 live region 播报。
331
- - **减少动态效果**:尊重系统的 `prefers-reduced-motion` 设置。
332
-
333
- ### 读取昼夜进度
334
-
335
- 组件会在根元素上更新 CSS 自定义属性 `--night`:
336
-
337
- - `0`:白天。
338
- - `1`:夜晚。
339
- - `0` 到 `1` 之间:昼夜过渡。
340
-
341
- 可以用它联动页面其他部分:
342
-
343
- ```css
344
- body {
345
- background: color-mix(
346
- in srgb,
347
- #f5ecd9,
348
- #1b2146 calc(var(--night, 0) * 100%)
349
- );
350
- }
351
- ```
352
-
353
- ### 监听状态事件
354
-
355
- 组件在昼夜状态发生变化时,会在组件根元素上派发可冒泡的 `daylight:change` 事件。事件的 `detail.night` 是布尔值。
356
-
357
- ```js
358
- document.addEventListener('daylight:change', (event) => {
359
- document.documentElement.classList.toggle(
360
- 'dark',
361
- event.detail.night
362
- );
363
- });
364
- ```
365
-
366
- 如果页面有多个实例,建议在单个根元素上监听,以区分具体是哪一个开关触发了事件:
367
-
368
- ```js
369
- root.addEventListener('daylight:change', (event) => {
370
- console.log('当前是否为夜晚:', event.detail.night);
371
- });
372
- ```
373
-
374
- ## 自定义主题
375
-
376
- 在组件根元素上覆盖以下 CSS 自定义属性:
377
-
378
- ```css
379
- [data-ld-part="toggle-daylight"] {
380
- --ld-bg: #f5ecd9;
381
- --ld-surface: #8c9a62;
382
- --ld-fg: #1b2146;
383
- --ld-muted: #7f97b3;
384
- --ld-accent: #efa02c;
385
- --ld-radius: 9999px;
386
- --ld-font: system-ui, sans-serif;
387
- }
388
- ```
389
-
390
- | CSS 变量 | 默认值 | 用途 |
391
- | --- | --- | --- |
392
- | `--ld-bg` | `#f5ecd9` | 背景调色输入 |
393
- | `--ld-surface` | `#8c9a62` | 表面与场景调色输入 |
394
- | `--ld-fg` | `#1b2146` | 前景颜色 |
395
- | `--ld-muted` | `#7f97b3` | 次要颜色 |
396
- | `--ld-accent` | `#efa02c` | 强调色与焦点颜色 |
397
- | `--ld-radius` | `9999px` | 交互区域圆角 |
398
- | `--ld-font` | `system-ui, sans-serif` | 组件字体 |
399
- | `--ld-space` | `8px` | 焦点轮廓偏移相关尺寸 |
400
- | `--ld-shadow` | `0 8px 24px rgb(27 33 70 / .2)` | 主题预留变量 |
401
- | `--ld-motion` | `200ms` | 焦点轮廓过渡时长 |
402
-
403
- ## 动态挂载与卸载
404
-
405
- 页面加载时,脚本会自动查找已有的 `[data-ld-part="toggle-daylight"]` 元素并挂载。对于之后动态创建的元素,调用公开 API:
406
-
407
- ```js
408
- const root = document.createElement('div');
409
- root.dataset.ldPart = 'toggle-daylight';
410
- document.querySelector('#app').appendChild(root);
411
-
412
- const destroy = PixelDaylightSwitch.mount(root);
413
-
414
- // 需要移除时:
415
- PixelDaylightSwitch.unmount(root);
416
- root.remove();
417
- ```
418
-
419
- `mount(element)` 对同一元素是幂等的:重复调用不会创建重复实例。成功挂载时会返回清理函数;如果挂载失败则返回 `null`。通常使用 `unmount(element)` 即可完成清理。
420
-
421
- 卸载会停止绘制并释放组件持有的事件监听器和观察器。它不会移除根元素本身。
422
-
423
- ## 渲染与兼容性
424
-
425
- - **WebGL2**:用于绘制玻璃折射效果。
426
- - **Canvas 2D**:WebGL2 不可用时作为渲染回退。
427
- - **响应式布局**:组件保持固定宽高比,并随容器宽度缩放。
428
- - **页面可见性**:页面隐藏或组件离开视口时,会暂停不必要的绘制。
429
- - **减少动态效果**:支持系统减少动态效果偏好。
430
- - **运行时依赖**:原生脚本不需要第三方运行时依赖。
431
-
432
- 组件需要现代浏览器提供 Canvas 2D;WebGL2 用于增强玻璃渲染体验。
433
-
434
- ## 项目目录结构
435
-
436
- 仓库采用轻量的原生 JavaScript 结构,无需前端打包工具。核心组件、演示页面、NPM 入口和自动化测试分别独立维护。
437
-
438
- ```text
439
- pixel-daylight-switch/
440
- ├── assets/
441
- │ └── scene-gallery.png # 五种像素场景总览图
442
- ├── mobile/
443
- │ ├── index.html # Capacitor 原生应用入口
444
- │ └── mobile.css # 移动端安全区域与全屏布局
445
- ├── scripts/
446
- │ └── prepare-mobile.cjs # 生成 Capacitor Web 资源
447
- ├── .github/
448
- │ └── workflows/
449
- │ ├── ci.yml # Node.js 与 Chromium 持续集成
450
- │ └── mobile-build.yml # Android / iOS 构建验证
451
- │ └── npm-publish.yml # 通过 v* 标签触发 NPM 发布
452
- ├── capacitor.config.json # Capacitor 原生应用配置
453
- ├── tests/
454
- │ ├── browser/
455
- │ │ └── component.spec.cjs # Playwright 浏览器回归测试
456
- │ ├── fixture.html # 浏览器测试页面
457
- │ ├── package-smoke.cjs # 包入口与 SSR 冒烟测试
458
- │ └── server.cjs # 本地测试静态服务器
459
- ├── app.js # 核心组件:渲染、交互与生命周期
460
- ├── embed.js # 独立嵌入入口
461
- ├── index.cjs # CommonJS 包入口
462
- ├── index.mjs # ESM 包入口
463
- ├── index.html # 主演示页面
464
- ├── preview.html # 预览页面
465
- ├── playground.html # 交互式参数实验室
466
- ├── style.css # 演示页面样式
467
- ├── package.json # NPM 元数据与脚本
468
- ├── playwright.config.cjs # Playwright 配置
469
- ├── CHANGELOG.md # 版本变更记录
470
- ├── LICENSE # Unlicense 许可证
471
- ├── llms.txt # 面向 AI 工具的项目说明
472
- └── README.md # 项目文档
473
- ```
474
-
475
- ### 关键目录说明
476
-
477
- - **`app.js`**:组件主体,包含昼夜状态、Canvas/WebGL 渲染、输入交互和实例生命周期管理。
478
- - **`playground.html`**:可视化调整参数、切换场景并生成 HTML 配置。
479
- - **`embed.js`**:面向嵌入式使用场景的入口脚本。
480
- - **`tests/`**:包加载、SSR、挂载卸载、多实例、指针/触控和移动端回归测试。
481
- - **`.github/workflows/`**:自动执行持续集成,并在版本标签触发时执行发布流程。
482
-
483
- ## 本地开发与测试
484
-
485
- 克隆项目:
486
-
487
- ```bash
488
- git clone https://github.com/GSvegetable/pixel-daylight-switch.git
489
- cd pixel-daylight-switch
490
- ```
491
-
492
- 直接用浏览器打开 `index.html` 即可查看基础演示。项目无需打包构建。
493
-
494
- ### 自动化测试
495
-
496
- 运行包入口、CommonJS / ESM 和 SSR 安全检查:
497
-
498
- ```bash
499
- npm test
500
- ```
501
-
502
- 浏览器回归测试使用 Playwright。安装测试运行器和 Chromium 后执行:
503
-
504
- ```bash
505
- npm install --no-save @playwright/test@1.56.1
506
- npx playwright install chromium
507
- npm run test:browser
508
- ```
509
-
510
- CI 会在 Node.js 18、20、22 下执行包与 SSR 检查,并在 Chromium 中运行交互、重复挂载/卸载、多实例、事件监听器清理和移动端回归测试。
511
-
512
- ## 版本记录
513
-
514
- ### v1.3.1
515
-
516
- - 修复组件卸载时主动销毁 WebGL context,导致同一 canvas/root 后续重新挂载可能异常的问题。
517
- - 保留组件资源、事件监听器与观察器的清理,同时允许 canvas 在卸载后重新挂载。
518
- - 增加 CommonJS、ESM、SSR、Node.js 18/20/22 和 Chromium 浏览器自动化检查。
519
- - 增加重复挂载、多实例、指针交互、触控、移动端布局与监听器清理测试。
520
- - 将浏览器回归纳入 NPM 发布前置检查。
521
-
522
- ### v1.3.0
523
-
524
- - 新增日本街道、现代公寓、海边灯塔和山间木屋四种像素环境。
525
- - 保留乡村小屋作为默认场景。
526
- - Playground 支持切换场景并组合调整天气、昼夜和玻璃参数。
527
-
528
- ### v1.2.0
529
-
530
- - 新增惯性阻尼、边缘回弹、玻璃色散和表面高光参数。
531
- - 新增世界时间倍率与飘雪天气。
532
- - 新增独立 Playground,支持参数分组、场景预设和 HTML 代码复制。
533
-
534
- ### v1.1.0
535
-
536
- - 新增运动速度、镜片折射、外框厚度和初始夜晚状态配置。
537
-
538
- ### v1.0.0
539
-
540
- - 首个公开版本。
541
-
542
- 详细记录见 [CHANGELOG.md](./CHANGELOG.md)。
543
-
544
- ## 许可证
545
-
546
- 本项目使用 [Unlicense](https://unlicense.org/) 公有领域协议。允许个人和商业用途,无需署名。
547
-
548
- ---
549
-
550
- <a id="english"></a>
551
-
552
- ## English
553
-
554
- **Pixel Daylight Switch** is a draggable day/night UI component with a tiny animated pixel world inside a refractive glass lens. Drag the control to move through daylight, dusk, and night.
555
-
556
- ### Features
557
-
558
- - An animated pixel environment inside a glass switch.
559
- - WebGL2 refraction rendering with a Canvas 2D fallback.
560
- - Five scenes: countryside, Japanese street, modern apartment, seaside lighthouse, and mountain cabin.
561
- - Continuous day/night transitions, pointer dragging, click, and keyboard interaction.
562
- - Per-instance HTML data attributes for motion, optics, weather, and scene settings.
563
- - Multiple independent instances on the same page.
564
- - No runtime dependencies for the standalone script.
565
- - Accessible switch semantics and reduced-motion support.
566
-
567
- ### Quick start
568
-
569
- No package manager or build step is required:
58
+ For a self-inserting embed, add this script where you want the component to appear:
570
59
 
571
60
  ```html
572
- <div data-ld-part="toggle-daylight"></div>
573
-
574
- <script
575
- defer
576
- src="https://gsvegetable.github.io/pixel-daylight-switch/app.js"
577
- ></script>
61
+ <script src="https://gsvegetable.github.io/pixel-daylight-switch/embed.js"></script>
578
62
  ```
579
63
 
580
- The script automatically mounts matching elements. It creates the internal canvas, interaction button, and live status region. The component scales with its container and keeps an aspect ratio of approximately **1.6439:1**.
64
+ The loader inserts its own container, stylesheet and runtime. It currently creates one default instance with a fixed maximum display size; use the mount-point approach above when you need precise layout or custom attributes.
581
65
 
582
- ### NPM
66
+ ## Install from npm
583
67
 
584
68
  ```bash
585
69
  npm install pixel-daylight-switch-gsvegetable
586
70
  ```
587
71
 
588
- ESM:
72
+ **ES modules**
589
73
 
590
74
  ```js
591
75
  import PixelDaylightSwitch from 'pixel-daylight-switch-gsvegetable';
@@ -593,134 +77,82 @@ import PixelDaylightSwitch from 'pixel-daylight-switch-gsvegetable';
593
77
  const root = document.querySelector('[data-ld-part="toggle-daylight"]');
594
78
  PixelDaylightSwitch.mount(root);
595
79
 
596
- // Before removing the element:
80
+ // Call before removing the root from the DOM.
597
81
  PixelDaylightSwitch.unmount(root);
598
82
  ```
599
83
 
600
- CommonJS:
84
+ **CommonJS**
601
85
 
602
86
  ```js
603
87
  const PixelDaylightSwitch = require('pixel-daylight-switch-gsvegetable');
604
88
  ```
605
89
 
606
- The package provides ESM and CommonJS entry points and is safe to import in SSR environments. Call `mount()` only after the element exists in the browser.
90
+ 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).
607
91
 
608
- ### Framework integration
92
+ ## Configuration
609
93
 
610
- For React, Vue 3, and Svelte, mount the component in the client-side lifecycle and call `unmount(element)` during cleanup. See the complete examples in the [Chinese documentation above](#框架集成).
611
-
612
- ### Playground
613
-
614
- Use the [Interactive Playground](https://gsvegetable.github.io/pixel-daylight-switch/playground.html) to adjust motion, glass optics, world speed, weather, and scenes. Apply settings to the preview and copy the generated HTML.
615
-
616
- ### Configuration
617
-
618
- All settings are optional and belong on the component root.
94
+ Set optional attributes on the mount element. Defaults apply when an attribute is omitted or invalid.
619
95
 
620
96
  | Attribute | Default | Description |
621
97
  | --- | ---: | --- |
622
- | `data-speed` | `9.5` | Base motion response speed; positive number. |
623
- | `data-refraction` | `1.24` | Glass lens refraction strength; positive number. |
624
- | `data-bezel` | `0.046` | Glass edge thickness ratio; positive number. |
625
- | `data-start-night` | `false` | Start at night when exactly `true`. |
626
- | `data-friction` | `0.6` | Release inertia damping, clamped to `0.1–0.9`. |
627
- | `data-bounce` | `3.2` | Edge rebound, clamped to `0–12`. |
628
- | `data-dispersion` | `0.05` | RGB edge dispersion, clamped to `0–0.25`. |
629
- | `data-shine` | `0.44` | Daytime highlight intensity, clamped to `0–1.5`. |
630
- | `data-time-speed` | `1` | World simulation speed, clamped to `0–8`; zero freezes world dynamics. |
631
- | `data-weather` | `default` | `default` or `snow`. Unknown values use `default`. |
632
- | `data-scene` | `countryside` | `countryside`, `japanese-street`, `apartment`, `seaside`, or `mountain-cabin`. |
98
+ | `data-speed` | `9.5` | Base response speed; greater than zero. |
99
+ | `data-refraction` | `1.24` | Glass lens refraction strength; greater than zero. |
100
+ | `data-bezel` | `0.046` | Relative glass-edge thickness; greater than zero. |
101
+ | `data-start-night` | `false` | Exact string `"true"` starts in night mode. |
102
+ | `data-friction` | `0.6` | Release inertia damping; clamped to `0.1–0.9`. |
103
+ | `data-bounce` | `3.2` | Edge rebound strength; clamped to `0–12`. |
104
+ | `data-dispersion` | `0.05` | RGB dispersion at the glass edge; clamped to `0–0.25`. |
105
+ | `data-shine` | `0.44` | Daylight glass highlight; clamped to `0–1.5`. |
106
+ | `data-time-speed` | `1` | World simulation speed; clamped to `0–8`. Set to `0` to freeze world motion. |
107
+ | `data-weather` | `default` | `default` or `snow`. Unknown values fall back to `default`. |
108
+ | `data-scene` | `countryside` | `countryside`, `japanese-street`, `apartment`, `seaside` or `mountain-cabin`. |
633
109
 
634
110
  Example:
635
111
 
636
112
  ```html
637
113
  <div
638
114
  data-ld-part="toggle-daylight"
639
- data-label="Day and night switch"
640
- data-speed="12"
641
- data-refraction="1.5"
642
- data-bezel="0.05"
643
115
  data-start-night="true"
644
- data-friction="0.45"
645
- data-bounce="4.2"
646
- data-dispersion="0.12"
647
- data-shine="0.6"
648
- data-time-speed="1.5"
116
+ data-scene="seaside"
649
117
  data-weather="snow"
650
- data-scene="mountain-cabin"
651
- ></div>
118
+ data-time-speed="1.4"
119
+ data-refraction="1.3">
120
+ </div>
652
121
  ```
653
122
 
654
- Invalid numeric values fall back to defaults. Range-limited values are clamped. Unknown weather and scene values fall back to `default` and `countryside`, respectively.
123
+ ## Events and state
655
124
 
656
- ### Events and page theme
125
+ The root element exposes transition progress through the CSS custom property `--night`: `0` is day, `1` is night, and values between them represent the transition.
657
126
 
658
- The component updates `--night` on its root element: `0` means day, `1` means night, and values in between represent the transition.
659
-
660
- It also dispatches a bubbling, composed `daylight:change` event from the component root when the day/night state changes. `event.detail.night` is a boolean:
127
+ When the committed day/night state changes, the root dispatches a bubbling, composed `daylight:change` event. `event.detail.night` is a boolean. It is not emitted on every animation frame.
661
128
 
662
129
  ```js
130
+ const root = document.querySelector('[data-ld-part="toggle-daylight"]');
131
+
663
132
  root.addEventListener('daylight:change', (event) => {
664
- document.documentElement.classList.toggle('dark', event.detail.night);
133
+ console.log(event.detail.night ? 'Night' : 'Day');
665
134
  });
666
135
  ```
667
136
 
668
- ### Theme customization
137
+ ## Rendering, accessibility and performance
669
138
 
670
- Override these CSS variables on the component root:
139
+ - WebGL2 renders the refractive lens; Canvas 2D is used if WebGL2 is unavailable or fails to initialize.
140
+ - The component scales its drawing surface to its container.
141
+ - Rendering pauses while the document is hidden or the component is outside the viewport.
142
+ - Keyboard and switch semantics are provided, with a live status announcement.
143
+ - The runtime respects the operating system's `prefers-reduced-motion` preference.
671
144
 
672
- ```css
673
- [data-ld-part="toggle-daylight"] {
674
- --ld-bg: #f5ecd9;
675
- --ld-surface: #8c9a62;
676
- --ld-fg: #1b2146;
677
- --ld-muted: #7f97b3;
678
- --ld-accent: #efa02c;
679
- --ld-radius: 9999px;
680
- --ld-font: system-ui, sans-serif;
681
- }
682
- ```
683
-
684
- Supported variables: `--ld-bg`, `--ld-surface`, `--ld-fg`, `--ld-muted`, `--ld-accent`, `--ld-radius`, `--ld-font`, `--ld-space`, `--ld-shadow`, and `--ld-motion`.
685
-
686
- ### Mounting and cleanup
687
-
688
- Elements present when the script loads are mounted automatically. For elements created later:
689
-
690
- ```js
691
- const destroy = PixelDaylightSwitch.mount(element);
692
-
693
- // Before removing the root:
694
- PixelDaylightSwitch.unmount(element);
695
- element.remove();
696
- ```
145
+ Performance varies with browser, graphics hardware, device pixel ratio and the number of visible instances.
697
146
 
698
- Mounting the same element more than once is safe and does not create duplicate instances. A successful `mount()` returns a cleanup function; a failed mount returns `null`. `unmount()` stops rendering and removes the component's listeners and observers without removing the root element.
699
-
700
- ### Rendering and browser behavior
701
-
702
- - WebGL2 is used for the refractive glass effect.
703
- - Canvas 2D is the rendering fallback when WebGL2 is unavailable.
704
- - The component respects `prefers-reduced-motion`.
705
- - Unnecessary drawing pauses when the page is hidden or the component is offscreen.
706
- - The standalone runtime has no third-party dependencies.
707
-
708
- ### Development and tests
147
+ ## Development and tests
709
148
 
710
149
  ```bash
711
150
  git clone https://github.com/GSvegetable/pixel-daylight-switch.git
712
151
  cd pixel-daylight-switch
713
- ```
714
-
715
- Open `index.html` in a modern browser. No build step is required.
716
-
717
- Run package and SSR checks:
718
-
719
- ```bash
720
152
  npm test
721
153
  ```
722
154
 
723
- Run browser regression tests after installing Playwright and Chromium:
155
+ Run the Chromium browser regression suite:
724
156
 
725
157
  ```bash
726
158
  npm install --no-save @playwright/test@1.56.1
@@ -728,16 +160,48 @@ npx playwright install chromium
728
160
  npm run test:browser
729
161
  ```
730
162
 
731
- ### Version history
163
+ GitHub Actions runs package and SSR checks on Node.js 18, 20 and 22, plus Chromium interaction and mobile regression tests. See [current workflow runs](https://github.com/GSvegetable/pixel-daylight-switch/actions/workflows/ci.yml).
732
164
 
733
- - **v1.3.1** — lifecycle/remount stability fix and expanded automated regression coverage.
734
- - **v1.3.0** — four additional pixel environments and scene selection in Playground.
735
- - **v1.2.0** — motion, optics, world-speed, weather settings, and the standalone Playground.
736
- - **v1.1.0** — initial HTML configuration options.
737
- - **v1.0.0** — first public release.
165
+ ## Android and iOS
738
166
 
739
- See [CHANGELOG.md](./CHANGELOG.md) for details.
167
+ The repository includes Capacitor 8 build workflows that reuse the web runtime. GitHub Actions produces an Android debug APK and an unsigned iOS Simulator app archive. These are development artifacts, not store-ready releases: the Android APK is not a Play Store release, and the iOS Simulator build cannot be installed on a physical iPhone or iPad. See [mobile build notes](builds/README.md) and the [mobile workflow](https://github.com/GSvegetable/pixel-daylight-switch/actions/workflows/mobile-build.yml).
740
168
 
741
- ### License
169
+ ## Repository map
742
170
 
743
- [Unlicense](https://unlicense.org/) — public-domain dedication. Free for personal and commercial use without attribution.
171
+ ```text
172
+ pixel-daylight-switch/
173
+ ├── .github/workflows/ # CI, native mobile builds, npm publishing
174
+ ├── assets/
175
+ │ └── scene-gallery.png # Five-scene overview image
176
+ ├── builds/
177
+ │ └── README.md # Mobile artifact notes; binaries are workflow outputs
178
+ ├── mobile/
179
+ │ ├── index.html # Capacitor app entry
180
+ │ └── mobile.css # Mobile-only layout
181
+ ├── scripts/
182
+ │ └── prepare-mobile.cjs # Prepare the shared web runtime for Capacitor
183
+ ├── tests/
184
+ │ ├── browser/ # Playwright interaction and mobile regressions
185
+ │ ├── fixture.html
186
+ │ ├── mobile-smoke.cjs
187
+ │ ├── package-smoke.cjs
188
+ │ └── server.cjs
189
+ ├── app.js # Component runtime, scenes, input and rendering
190
+ ├── style.css # Component styles
191
+ ├── embed.js # One-script website embed loader
192
+ ├── index.html # Live demo page
193
+ ├── preview.html # Component preview
194
+ ├── playground.html # Interactive parameter lab
195
+ ├── index.mjs / index.cjs # ESM and CommonJS package entry points
196
+ ├── package.json # Package metadata and scripts
197
+ ├── capacitor.config.json # Capacitor configuration
198
+ ├── playwright.config.cjs # Browser test configuration
199
+ ├── CHANGELOG.md
200
+ ├── llms.txt
201
+ ├── LICENSE
202
+ └── README*.md # English, Simplified Chinese and Japanese docs
203
+ ```
204
+
205
+ ## License
206
+
207
+ Released under [The Unlicense](LICENSE). You may copy, modify, publish, distribute and use the software for personal or commercial purposes without attribution. It is provided without warranty.