@plotdb/lotion 0.4.2 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## v0.5.0
2
+
3
+ - player: captions. `captions: [[t0, t1, text, meta?], ...]` or `{lang: [...]}`, drawn outside the stage in a
4
+ bottom safe area, styled by css variables; readers turn them on / off with the `CC` button, the `c` key or
5
+ `showCaptions(on)`; `captionsBurned: true` starts them off for scenes that already show the narration.
6
+ Not drawn in `?render` unless `burnCaptions` ( or `?render&captions` ).
7
+ - player: events `on` / `off` ( `time`, `chapter`, `caption`, `captions` ) and state `chapter`, `caption`,
8
+ `captionsOn`, `captionLang`
9
+ - chapters may carry a third element, `meta` ( such as `{description, thumb}` ), passed through in `player.chapter`
10
+ - `lotion.vtt.parse` / `stringify`: WebVTT to and from the caption format
11
+ - cli: `lotion video` writes captions as `.vtt` next to the mp4 ( shifted by `--from` ); `lotion captions` exports
12
+ them alone. render protocol: `window.CAPTIONS`
13
+
14
+
1
15
  ## v0.4.2
2
16
 
3
17
  - animate, size: `clip-path: inset()` ( reveals ) becomes one svg clipPath whose rect moves by smil, instead of one
package/README.md CHANGED
@@ -48,7 +48,14 @@ player options:
48
48
  - `width`, `height`: design size of the stage. default `1920` x `1080`. The stage scales to fit the container.
49
49
  - `duration`: total length in seconds.
50
50
  - `seek(t)`: render the frame at time `t`. must depend on `t` only.
51
- - `chapters`: optional `[[t, name], ...]`, shown as ticks on the timeline and used by `←` / `→`.
51
+ - `chapters`: optional `[[t, name, meta?], ...]`, shown as ticks on the timeline and used by `←` / `→`. `meta` is
52
+ an optional object ( such as `{description, thumb}` ) the player passes through in `player.chapter`.
53
+ - `captions`: optional `[[t0, t1, text, meta?], ...]` ( `meta` such as `{speaker}` ), or `{zh: [...], en: [...]}`
54
+ for several languages. See Captions.
55
+ - `captionsBurned`: `true` when the scene already shows the narration as text: captions start off. default
56
+ `false` ( captions start on ). Readers can always turn them on or off.
57
+ - `captionLang`: initial caption language. default the first one.
58
+ - `burnCaptions`: draw captions in `?render` mode ( frame export ). default `false`; `?render&captions` also works.
52
59
  - `start`: initial time. `?t=<sec>` in the url takes precedence.
53
60
  - `autoplay`: default `false`.
54
61
  - `cues`: optional function returning sound cues `[{t, ...}]`, exported by `lotion cues`.
@@ -56,16 +63,45 @@ player options:
56
63
  `false` shows nothing.
57
64
 
58
65
  player methods: `start()`, `seek(t)`, `play(go = true)`, `pause()`, `toggle()`, `fullscreen()`, `encode(opt)`
59
- ( see Online Export ).
66
+ ( see Online Export ), `showCaptions(on = true)`, `setCaptionLang(lang)`, `on(name, cb)`, `off(name, cb)`.
60
67
  `player.ready` is a promise resolved by `start()`.
61
68
 
69
+ player state: `t`, `playing`, `chapter` ( `{index, t, name, meta}` or `null` ), `caption`
70
+ ( `{t0, t1, text, meta, lang}` or `null` ), `captionsOn`, `captionLang`, `captionLangs`.
71
+
72
+ events ( `player.on(name, cb)` ):
73
+
74
+ - `time`: every seek, with `t`.
75
+ - `chapter`: the current chapter changed, with `player.chapter`.
76
+ - `caption`: the shown caption changed ( including to `null` ), with `player.caption`. Fired whether captions
77
+ are on or off, so a page can read them anyway ( search, transcripts ).
78
+ - `captions`: captions turned on / off or switched language, with `{on, lang}`.
79
+
62
80
  Until `start()` is called, the stage is hidden behind the loading screen, the control bar is disabled, and
63
81
  `seek` / `play` do nothing. Scenes often need asynchronous preparation before the first frame is right
64
82
  ( web fonts, measuring the layout to place things ), and a half-built stage should not be seen or scrubbed.
65
83
  So build the scene, finish whatever it waits for, then call `start()`:
66
84
 
67
85
  Promise.all([document.fonts.ready, prepare!]).then -> p.start!
68
- Keys ( when the player is focused ): `space` play / pause, `←` / `→` previous / next chapter, `f` fullscreen.
86
+ Keys ( when the player is focused ): `space` play / pause, `←` / `→` previous / next chapter, `f` fullscreen,
87
+ `c` captions on / off.
88
+
89
+ ### Captions
90
+
91
+ Captions are drawn by the player, outside the stage: they keep their size when the stage scales, sit in a safe
92
+ area at the bottom, and are not part of `encode()` frames. Style them with css variables on `.lotion`:
93
+ `--lotion-caption-size`, `--lotion-caption-bg`, `--lotion-caption-color`, `--lotion-caption-font`.
94
+ The control bar shows a `CC` button when there are captions.
95
+
96
+ `lotion.vtt.parse(text)` reads WebVTT into the caption format ( a `<v name>` voice becomes `{speaker}` ),
97
+ `lotion.vtt.stringify(captions)` writes it back. `lotion video` writes a `.vtt` next to the mp4, and
98
+ `lotion captions` exports it alone.
99
+
100
+ Guidelines for splitting ( common subtitle practice ): Traditional Chinese up to about 16 characters a line, at most
101
+ 2 lines, no faster than about 9 characters a second; English up to about 42 characters a line, 17 - 20 a second.
102
+ Keep each caption on screen 0.83 - 7 s, with at least 2 frames between captions. Break at punctuation or pauses,
103
+ not inside a phrase; merge captions that are too short. With word timings from a speech generator, cut at sentence
104
+ ends and at pauses longer than about 0.3 s, then split or merge by those limits.
69
105
 
70
106
  helpers:
71
107
 
@@ -185,6 +221,8 @@ With `?render` in the url, a page should cover the viewport with its stage and e
185
221
  - `window.seek(t)`: render the frame at `t`.
186
222
  - `window.DURATION`: total length in seconds.
187
223
  - `window.cues()`: optional, list of sound cues.
224
+ - `window.CAPTIONS`: optional, captions as `{lang: [[t0, t1, text, meta?], ...]}` ( `lotion video` writes them as
225
+ `.vtt` ).
188
226
 
189
227
  `lotion.player` does this automatically, and only when `start()` is called: the cli waits for `window.seek` to
190
228
  exist before rendering, so it never captures a scene that is still being prepared. Any other page can follow
@@ -220,8 +258,9 @@ targets under the root ( or use `:scope` ). A block sample is in `web/src/pug/bl
220
258
 
221
259
  lotion frames <src> <outdir> <t...> one png per time
222
260
  lotion sheet <src> <out.png> <t...> the same, tiled into one contact sheet
223
- lotion video <src> <out.mp4> render to mp4
261
+ lotion video <src> <out.mp4> render to mp4, plus <out>.vtt when the page has captions
224
262
  lotion cues <src> <out.json> dump window.cues()
263
+ lotion captions <src> <out.vtt> export captions as WebVTT ( out.<lang>.vtt for several languages )
225
264
  lotion bundle <base-url> <block> <out> pack a block and its dependencies into one file
226
265
  lotion bgm | sfx | mix | beats removed, see Audio
227
266
 
package/cli.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- var fs, path, http, child_process, usage, parse, types, serve, playwright, open, shot, frames, sheet, WARM, encode, video, cues, bundle, moved, argv, ref$, pos, opt, cmd, src, out, rest, times, p, slice$ = [].slice;
2
+ var fs, path, http, child_process, usage, parse, types, serve, playwright, open, shot, frames, sheet, WARM, encode, video, writeVtt, cues, bundle, moved, argv, ref$, pos, opt, cmd, src, out, rest, times, p, slice$ = [].slice;
3
3
  fs = require('fs');
4
4
  path = require('path');
5
5
  http = require('http');
6
6
  child_process = require('child_process');
7
- usage = 'usage:\n lotion <frames|sheet|video|cues> <src> <out> [t...] [options]\n lotion bundle <base-url> <block-name> <out> [options]\n audio generators ( bgm | sfx | mix | beats ) were removed, see README > Audio\nsee README for options.';
7
+ usage = 'usage:\n lotion <frames|sheet|video|cues|captions> <src> <out> [t...] [options]\n lotion bundle <base-url> <block-name> <out> [options]\n audio generators ( bgm | sfx | mix | beats ) were removed, see README > Audio\nsee README for options.';
8
8
  parse = function(argv){
9
9
  var ref$, pos, opt, i, a;
10
10
  ref$ = [[], {}], pos = ref$[0], opt = ref$[1];
@@ -269,7 +269,7 @@ video = function(src, out, opt){
269
269
  workers = Math.max(1, Math.floor(+(opt.workers || 1)));
270
270
  t0 = Date.now();
271
271
  return open(src, opt).then(function(first){
272
- var n, done, tick, finish, k, tmp, parts;
272
+ var n, done, tick, finish, vtt, k, tmp, parts;
273
273
  o.from = +(opt.from || 0);
274
274
  n = Math.round((+(opt.to || first.duration) - o.from) * o.fps);
275
275
  done = 0;
@@ -282,8 +282,11 @@ video = function(src, out, opt){
282
282
  finish = function(){
283
283
  return console.log(out + " ( " + n + " frames, " + ((Date.now() - t0) / 1000).toFixed(0) + "s )");
284
284
  };
285
+ vtt = writeVtt(first.page, out, o.from, +(opt.to || first.duration));
285
286
  if (workers === 1) {
286
- return encode(first.page, 0, n, out, o, tick).then(function(){
287
+ return vtt.then(function(){
288
+ return encode(first.page, 0, n, out, o, tick);
289
+ }).then(function(){
287
290
  return first.close();
288
291
  }).then(finish);
289
292
  }
@@ -302,15 +305,17 @@ video = function(src, out, opt){
302
305
  out: path.join(tmp, "part-" + w + ".mp4")
303
306
  };
304
307
  });
305
- return Promise.all([Promise.resolve(first)].concat((function(){
306
- var i$, to$, results$ = [];
307
- for (i$ = 1, to$ = k; i$ < to$; ++i$) {
308
- results$.push(i$);
309
- }
310
- return results$;
311
- }()).map(function(){
312
- return open(src, opt);
313
- }))).then(function(pages){
308
+ return vtt.then(function(){
309
+ return Promise.all([Promise.resolve(first)].concat((function(){
310
+ var i$, to$, results$ = [];
311
+ for (i$ = 1, to$ = k; i$ < to$; ++i$) {
312
+ results$.push(i$);
313
+ }
314
+ return results$;
315
+ }()).map(function(){
316
+ return open(src, opt);
317
+ })));
318
+ }).then(function(pages){
314
319
  return Promise.all(parts.map(function(p, w){
315
320
  return encode(pages[w].page, p.i0, p.i1, p.out, o, tick);
316
321
  }))['finally'](function(){
@@ -345,6 +350,36 @@ video = function(src, out, opt){
345
350
  }).then(finish);
346
351
  });
347
352
  };
353
+ writeVtt = function(page, out, t0, t1){
354
+ var vtt;
355
+ vtt = require('./index').vtt;
356
+ return page.evaluate(function(){
357
+ return window.CAPTIONS || null;
358
+ }).then(function(tracks){
359
+ var langs, base, i$, len$, lang, list, f, results$ = [];
360
+ if (!tracks) {
361
+ return;
362
+ }
363
+ langs = Object.keys(tracks);
364
+ base = out.replace(/\.(vtt|mp4|webm|mov)$/i, '');
365
+ for (i$ = 0, len$ = langs.length; i$ < len$; ++i$) {
366
+ lang = langs[i$];
367
+ list = tracks[lang].filter(fn$).map(fn1$);
368
+ f = langs.length > 1 && lang
369
+ ? base + "." + lang + ".vtt"
370
+ : base + ".vtt";
371
+ fs.writeFileSync(f, vtt.stringify(list));
372
+ results$.push(console.log(f));
373
+ }
374
+ return results$;
375
+ function fn$(c){
376
+ return c[1] > t0 && c[0] < t1;
377
+ }
378
+ function fn1$(c){
379
+ return [Math.max(0, c[0] - t0), Math.min(t1, c[1]) - t0].concat(c.slice(2));
380
+ }
381
+ });
382
+ };
348
383
  cues = function(src, out, opt){
349
384
  return open(src, opt).then(function(arg$){
350
385
  var page, close;
@@ -449,6 +484,14 @@ if ((ref$ = argv[0]) === 'bgm' || ref$ === 'sfx' || ref$ === 'mix' || ref$ === '
449
484
  return video(src, out, opt);
450
485
  case 'cues':
451
486
  return cues(src, out, opt);
487
+ case 'captions':
488
+ return open(src, opt).then(function(arg$){
489
+ var page, close;
490
+ page = arg$.page, close = arg$.close;
491
+ return writeVtt(page, out, 0, Infinity).then(function(){
492
+ return close();
493
+ });
494
+ });
452
495
  case 'bundle':
453
496
  if (rest[0]) {
454
497
  return bundle(src, out, rest[0], opt);
package/index.css CHANGED
@@ -66,6 +66,52 @@
66
66
  color: var(--lotion-ink);
67
67
  border: 1px solid var(--lotion-line);
68
68
  }
69
+ .lotion .lotion-cc {
70
+ width: auto;
71
+ height: 28px;
72
+ padding: 0 8px;
73
+ border-radius: 6px;
74
+ font: 600 13px/1 sans-serif;
75
+ letter-spacing: 0.04em;
76
+ background: transparent;
77
+ color: var(--lotion-ink);
78
+ border: 1px solid var(--lotion-line);
79
+ }
80
+ .lotion.lotion-captions-on .lotion-cc {
81
+ background: var(--lotion-ink);
82
+ border-color: var(--lotion-ink);
83
+ color: #fff;
84
+ }
85
+ .lotion .lotion-viewport {
86
+ container-type: inline-size;
87
+ }
88
+ .lotion .lotion-captions {
89
+ --lotion-caption-size: clamp(14px, 2.6cqw, 44px);
90
+ position: absolute;
91
+ left: 10%;
92
+ right: 10%;
93
+ bottom: 6%;
94
+ display: none;
95
+ flex-direction: column;
96
+ align-items: center;
97
+ gap: 0.2em;
98
+ pointer-events: none;
99
+ text-align: center;
100
+ font-size: var(--lotion-caption-size);
101
+ line-height: 1.35;
102
+ }
103
+ .lotion.lotion-captions-on .lotion-captions {
104
+ display: flex;
105
+ }
106
+ .lotion .lotion-caption {
107
+ max-width: 100%;
108
+ padding: 0.12em 0.5em;
109
+ border-radius: 0.2em;
110
+ background: var(--lotion-caption-bg, rgba(0,0,0,0.72));
111
+ color: var(--lotion-caption-color, #fff);
112
+ font-family: var(--lotion-caption-font, sans-serif);
113
+ white-space: pre-wrap;
114
+ }
69
115
  .lotion .lotion-time {
70
116
  font: 15px/1 sans-serif;
71
117
  color: var(--lotion-mute);
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  (function(){
2
- var PI, sin, cos, exp, sqrt, round, clamp, lerp, ss, spring, presets, track, vis, bump, typing, hex, mixc, rgb, mk, put, txt, NS, svg, player, ref$, libs, libCache, lib, encode, lotion;
2
+ var PI, sin, cos, exp, sqrt, round, clamp, lerp, ss, spring, presets, track, vis, bump, typing, hex, mixc, rgb, mk, put, txt, NS, svg, player, ref$, libs, libCache, lib, encode, vttTime, vttParseTime, vtt, lotion;
3
3
  PI = Math.PI, sin = Math.sin, cos = Math.cos, exp = Math.exp, sqrt = Math.sqrt, round = Math.round;
4
4
  clamp = function(v, a, b){
5
5
  a == null && (a = 0);
@@ -179,7 +179,7 @@
179
179
  return e;
180
180
  };
181
181
  player = function(opt){
182
- var this$ = this;
182
+ var c, tracks, k, v, ref$, this$ = this;
183
183
  opt == null && (opt = {});
184
184
  this.opt = opt;
185
185
  this.root = typeof opt.root === 'string'
@@ -190,6 +190,9 @@
190
190
  this.duration = opt.duration || 0;
191
191
  this.chapters = opt.chapters || [];
192
192
  this.t = 0;
193
+ this.chapter = null;
194
+ this.caption = null;
195
+ this.handlers = {};
193
196
  this.playing = false;
194
197
  this.last = null;
195
198
  this.started = false;
@@ -197,8 +200,29 @@
197
200
  return this$.readyRes = res;
198
201
  });
199
202
  this.renderMode = /[?&]render\b/.test(location.search);
203
+ c = opt.captions;
204
+ tracks = !c
205
+ ? {}
206
+ : Array.isArray(c) ? {
207
+ '': c
208
+ } : c;
209
+ this.captionTracks = {};
210
+ for (k in tracks) {
211
+ v = tracks[k];
212
+ this.captionTracks[k] = v.slice().sort(fn$);
213
+ }
214
+ this.captionLangs = Object.keys(this.captionTracks);
215
+ this.captionLang = in$(opt.captionLang, this.captionLangs)
216
+ ? opt.captionLang
217
+ : (ref$ = this.captionLangs[0]) != null ? ref$ : null;
218
+ this.captionsOn = this.renderMode
219
+ ? !!(opt.burnCaptions || /[?&]captions\b/.test(location.search))
220
+ : !opt.captionsBurned;
200
221
  this.init();
201
222
  return this;
223
+ function fn$(a, b){
224
+ return a[0] - b[0];
225
+ }
202
226
  };
203
227
  player.prototype = (ref$ = Object.create(Object.prototype), ref$.constructor = player, ref$.init = function(){
204
228
  var r, ref$, html, q, this$ = this;
@@ -218,7 +242,7 @@
218
242
  html = typeof this.opt.loading === 'string' ? this.opt.loading : '<div class="lotion-spinner"></div>';
219
243
  this.loading = mk('lotion-loading-screen', html, this.viewport);
220
244
  }
221
- this.bar = mk('lotion-bar', '<div class="lotion-btn lotion-play"></div>\n<div class="lotion-track"><div class="lotion-rail"></div><div class="lotion-fill"></div>\n<div class="lotion-chapters"></div><div class="lotion-knob"></div></div>\n<div class="lotion-time"></div>\n<div class="lotion-btn lotion-fs" title="全螢幕"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"\nstroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M2 6V2h4M10 2h4v4M14 10v4h-4M6 14H2v-4"/></svg></div>', r);
245
+ this.bar = mk('lotion-bar', '<div class="lotion-btn lotion-play"></div>\n<div class="lotion-track"><div class="lotion-rail"></div><div class="lotion-fill"></div>\n<div class="lotion-chapters"></div><div class="lotion-knob"></div></div>\n<div class="lotion-time"></div>\n<div class="lotion-btn lotion-cc" title="字幕 ( c )">CC</div>\n<div class="lotion-btn lotion-fs" title="全螢幕"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"\nstroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M2 6V2h4M10 2h4v4M14 10v4h-4M6 14H2v-4"/></svg></div>', r);
222
246
  q = function(n){
223
247
  return this$.bar.querySelector(".lotion-" + n);
224
248
  };
@@ -229,15 +253,21 @@
229
253
  time: q('time'),
230
254
  track: q('track'),
231
255
  chapters: q('chapters'),
232
- fs: q('fs')
256
+ fs: q('fs'),
257
+ cc: q('cc')
233
258
  };
234
259
  this.chapters.map(function(arg$){
235
- var t, name, e;
236
- t = arg$[0], name = arg$[1];
260
+ var t, name, meta, e;
261
+ t = arg$[0], name = arg$[1], meta = arg$[2];
237
262
  e = mk('lotion-ch', '', this$.el.chapters);
238
- e.title = name;
263
+ e.title = name + (meta && meta.description ? "\n" + meta.description : '');
239
264
  return e.style.left = 100 * t / this$.duration + "%";
240
265
  });
266
+ this.el.captions = mk('lotion-captions', '', this.viewport);
267
+ if (!this.captionLangs.length) {
268
+ this.el.cc.remove();
269
+ }
270
+ this.root.classList.toggle('lotion-captions-on', this.captionsOn);
241
271
  this.bind();
242
272
  if (this.renderMode) {
243
273
  r.classList.add('lotion-render');
@@ -267,6 +297,9 @@
267
297
  if (this.opt.cues) {
268
298
  window.cues = this.opt.cues;
269
299
  }
300
+ if (this.captionLangs.length) {
301
+ window.CAPTIONS = this.captionTracks;
302
+ }
270
303
  }
271
304
  this.readyRes(this);
272
305
  m = /[?&]t=([\d.]+)/.exec(location.search);
@@ -291,6 +324,10 @@
291
324
  e.stopPropagation();
292
325
  return this$.fullscreen();
293
326
  });
327
+ this.el.cc.addEventListener('click', function(e){
328
+ e.stopPropagation();
329
+ return this$.showCaptions(!this$.captionsOn);
330
+ });
294
331
  this.viewport.addEventListener('click', function(){
295
332
  this$.root.focus();
296
333
  return this$.toggle();
@@ -324,6 +361,8 @@
324
361
  return this$.toggle();
325
362
  } else if (e.key === 'f') {
326
363
  return this$.fullscreen();
364
+ } else if (e.key === 'c' && this$.captionLangs.length) {
365
+ return this$.showCaptions(!this$.captionsOn);
327
366
  } else if (e.key === 'ArrowRight') {
328
367
  return this$.seek((ref$ = starts.find(function(s){
329
368
  return s > this$.t + 0.05;
@@ -351,7 +390,7 @@
351
390
  }, ref$.fmt = function(t){
352
391
  return Math.floor(t / 60) + ":" + ("0" + Math.floor(t % 60)).slice(-2);
353
392
  }, ref$.seek = function(t){
354
- var p, ch;
393
+ var p, ci, i$, ref$, len$, i, c, ch;
355
394
  if (!this.started || this.encoding) {
356
395
  return;
357
396
  }
@@ -360,11 +399,109 @@
360
399
  p = 100 * t / this.duration + "%";
361
400
  this.el.fill.style.width = p;
362
401
  this.el.knob.style.left = p;
363
- ch = this.chapters.filter(function(c){
364
- return c[0] <= t;
365
- }).pop();
402
+ ci = -1;
403
+ for (i$ = 0, len$ = (ref$ = this.chapters).length; i$ < len$; ++i$) {
404
+ i = i$;
405
+ c = ref$[i$];
406
+ if (c[0] <= t) {
407
+ ci = i;
408
+ }
409
+ }
410
+ ch = this.chapters[ci];
366
411
  this.el.time.textContent = (ch ? ch[1] + " · " : '') + (this.fmt(t) + " / " + this.fmt(this.duration));
367
- return this.root.classList.toggle('lotion-playing', this.playing);
412
+ this.root.classList.toggle('lotion-playing', this.playing);
413
+ if ((ch
414
+ ? ci
415
+ : -1) !== (this.chapter
416
+ ? this.chapter.index
417
+ : -1)) {
418
+ this.chapter = ch ? {
419
+ index: ci,
420
+ t: ch[0],
421
+ name: ch[1],
422
+ meta: ch[2] || null
423
+ } : null;
424
+ this.fire('chapter', this.chapter);
425
+ }
426
+ this.updateCaption();
427
+ return this.fire('time', t);
428
+ }, ref$.updateCaption = function(){
429
+ var t, list, act, cap, key, i$, ref$, len$, line;
430
+ t = this.t;
431
+ list = (this.captionLang != null && this.captionTracks[this.captionLang]) || [];
432
+ act = list.filter(function(c){
433
+ return c[0] <= t && t < c[1];
434
+ });
435
+ cap = !act.length
436
+ ? null
437
+ : {
438
+ t0: act[0][0],
439
+ t1: act[act.length - 1][1],
440
+ text: act.map(function(c){
441
+ return c[2];
442
+ }).join('\n'),
443
+ meta: act[0][3] || null,
444
+ lang: this.captionLang
445
+ };
446
+ key = function(c){
447
+ if (c) {
448
+ return c.lang + "|" + c.t0 + "|" + c.text;
449
+ } else {
450
+ return '';
451
+ }
452
+ };
453
+ if (key(cap) === key(this.caption)) {
454
+ return;
455
+ }
456
+ this.caption = cap;
457
+ this.el.captions.innerHTML = '';
458
+ if (cap) {
459
+ for (i$ = 0, len$ = (ref$ = cap.text.split('\n')).length; i$ < len$; ++i$) {
460
+ line = ref$[i$];
461
+ mk('lotion-caption', '', this.el.captions).textContent = line;
462
+ }
463
+ }
464
+ return this.fire('caption', cap);
465
+ }, ref$.showCaptions = function(v){
466
+ v == null && (v = true);
467
+ this.captionsOn = !!v;
468
+ this.root.classList.toggle('lotion-captions-on', this.captionsOn);
469
+ this.fire('captions', {
470
+ on: this.captionsOn,
471
+ lang: this.captionLang
472
+ });
473
+ return this;
474
+ }, ref$.setCaptionLang = function(lang){
475
+ if (!in$(lang, this.captionLangs)) {
476
+ return this;
477
+ }
478
+ this.captionLang = lang;
479
+ this.updateCaption();
480
+ this.fire('captions', {
481
+ on: this.captionsOn,
482
+ lang: lang
483
+ });
484
+ return this;
485
+ }, ref$['on'] = function(name, cb){
486
+ var ref$, ref1$;
487
+ ((ref1$ = (ref$ = this.handlers)[name]) != null
488
+ ? ref1$
489
+ : ref$[name] = []).push(cb);
490
+ return this;
491
+ }, ref$['off'] = function(name, cb){
492
+ if (this.handlers[name]) {
493
+ this.handlers[name] = this.handlers[name].filter(function(h){
494
+ return h !== cb;
495
+ });
496
+ }
497
+ return this;
498
+ }, ref$.fire = function(name, v){
499
+ var i$, ref$, len$, h, results$ = [];
500
+ for (i$ = 0, len$ = (ref$ = (this.handlers[name] || []).slice()).length; i$ < len$; ++i$) {
501
+ h = ref$[i$];
502
+ results$.push(h(v));
503
+ }
504
+ return results$;
368
505
  }, ref$.tick = function(now){
369
506
  var this$ = this;
370
507
  if (!this.playing) {
@@ -583,7 +720,79 @@
583
720
  throw e;
584
721
  });
585
722
  };
723
+ vttTime = function(t){
724
+ var ms, h, m, s, pad;
725
+ ms = Math.round(t * 1000);
726
+ h = Math.floor(ms / 3600000);
727
+ m = Math.floor(ms / 60000) % 60;
728
+ s = Math.floor(ms / 1000) % 60;
729
+ pad = function(v, n){
730
+ n == null && (n = 2);
731
+ return (v + "").padStart(n, '0');
732
+ };
733
+ return pad(h) + ":" + pad(m) + ":" + pad(s) + "." + pad(ms % 1000, 3);
734
+ };
735
+ vttParseTime = function(s){
736
+ var p;
737
+ p = s.trim().split(':');
738
+ return p.reduce(function(a, v){
739
+ return a * 60 + parseFloat(v);
740
+ }, 0);
741
+ };
742
+ vtt = {
743
+ parse: function(text){
744
+ var ret, blocks, i$, len$, b, lines, i, ref$, a, z, body, meta, m, cue;
745
+ ret = [];
746
+ blocks = text.replace(/\r\n?/g, '\n').split(/\n{2,}/);
747
+ for (i$ = 0, len$ = blocks.length; i$ < len$; ++i$) {
748
+ b = blocks[i$];
749
+ lines = b.split('\n').filter(fn$);
750
+ i = lines.findIndex(fn1$);
751
+ if (i < 0) {
752
+ continue;
753
+ }
754
+ ref$ = lines[i].split('-->'), a = ref$[0], z = ref$[1];
755
+ body = lines.slice(i + 1).join('\n');
756
+ meta = null;
757
+ if (m = /^<v(?:\.[^\s>]*)?\s+([^>]+)>/.exec(body)) {
758
+ meta = {
759
+ speaker: m[1].trim()
760
+ };
761
+ }
762
+ body = body.replace(/<\/?[^>]+>/g, '').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
763
+ cue = [vttParseTime(a), vttParseTime(z.trim().split(/\s+/)[0]), body];
764
+ if (meta) {
765
+ cue.push(meta);
766
+ }
767
+ ret.push(cue);
768
+ }
769
+ return ret;
770
+ function fn$(l){
771
+ return l.trim();
772
+ }
773
+ function fn1$(l){
774
+ return /-->/.test(l);
775
+ }
776
+ },
777
+ stringify: function(captions){
778
+ var esc, cues;
779
+ esc = function(s){
780
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
781
+ };
782
+ cues = captions.map(function(arg$){
783
+ var t0, t1, text, meta, body;
784
+ t0 = arg$[0], t1 = arg$[1], text = arg$[2], meta = arg$[3];
785
+ body = esc(text);
786
+ if (meta && meta.speaker) {
787
+ body = "<v " + esc(meta.speaker) + ">" + body;
788
+ }
789
+ return vttTime(t0) + " --> " + vttTime(t1) + "\n" + body;
790
+ });
791
+ return "WEBVTT\n\n" + cues.join('\n\n') + '\n';
792
+ }
793
+ };
586
794
  lotion = {
795
+ vtt: vtt,
587
796
  clamp: clamp,
588
797
  lerp: lerp,
589
798
  ss: ss,
@@ -610,6 +819,11 @@
610
819
  } else if (typeof window != 'undefined' && window !== null) {
611
820
  window.lotion = lotion;
612
821
  }
822
+ function in$(x, xs){
823
+ var i = -1, l = xs.length >>> 0;
824
+ while (++i < l) if (x === xs[i]) return true;
825
+ return false;
826
+ }
613
827
  function import$(obj, src){
614
828
  var own = {}.hasOwnProperty;
615
829
  for (var key in src) if (own.call(src, key)) obj[key] = src[key];
package/index.min.css CHANGED
@@ -1 +1 @@
1
- .lotion{--lotion-bg:#eceae5;--lotion-paper:#fbfaf7;--lotion-ink:#18181a;--lotion-mute:#8a877f;--lotion-line:#d9d6cf;position:relative;border:1px solid var(--lotion-line);border-radius:12px;overflow:hidden;outline:none}.lotion .lotion-viewport{position:relative;width:100%;overflow:hidden;background:var(--lotion-bg)}.lotion .lotion-stage{position:absolute;left:0;top:0;transform-origin:0 0;background:var(--lotion-bg);overflow:hidden;line-height:normal}.lotion .lotion-bar{display:flex;align-items:center;gap:18px;height:64px;padding:0 24px;background:var(--lotion-paper);border-top:1px solid var(--lotion-line);user-select:none}.lotion .lotion-btn{flex:none;width:40px;height:40px;border-radius:50%;background:var(--lotion-ink);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer}.lotion .lotion-play:before{content:"";margin-left:3px;border-style:solid;border-width:7px 0 7px 12px;border-color:transparent transparent transparent #fff}.lotion.lotion-playing .lotion-play:before{margin-left:0;width:4px;height:14px;border-width:0 4px;border-color:#fff}.lotion .lotion-fs{background:transparent;color:var(--lotion-ink);border:1px solid var(--lotion-line)}.lotion .lotion-time{font:15px/1 sans-serif;color:var(--lotion-mute);font-variant-numeric:tabular-nums;white-space:nowrap}.lotion .lotion-track{position:relative;flex:1;height:40px;cursor:pointer}.lotion .lotion-rail,.lotion .lotion-fill{position:absolute;left:0;top:18px;height:4px;border-radius:2px}.lotion .lotion-rail{right:0;background:var(--lotion-line)}.lotion .lotion-fill{background:var(--lotion-ink)}.lotion .lotion-knob{position:absolute;top:13px;width:14px;height:14px;margin-left:-7px;border-radius:50%;background:var(--lotion-ink)}.lotion .lotion-chapters{position:absolute;inset:0;pointer-events:none}.lotion .lotion-ch{position:absolute;top:14px;width:2px;height:12px;margin-left:-1px;border-radius:1px;background:var(--lotion-mute);opacity:.5}.lotion.lotion-loading .lotion-stage{visibility:hidden}.lotion.lotion-loading .lotion-bar{opacity:.5;pointer-events:none}.lotion.lotion-encoding .lotion-bar{opacity:.5;pointer-events:none}.lotion .lotion-loading-screen,.lotion .lotion-encoding-screen{position:absolute;inset:0;display:flex;flex-direction:column;gap:12px;align-items:center;justify-content:center;background:var(--lotion-bg);color:var(--lotion-mute)}.lotion .lotion-progress{font-size:14px;font-variant-numeric:tabular-nums}.lotion .lotion-spinner{width:36px;height:36px;border-radius:50%;border:3px solid var(--lotion-line);border-top-color:var(--lotion-ink);animation:lotion-spin .8s linear infinite}.lotion:fullscreen{display:flex;flex-direction:column;border:0;border-radius:0}.lotion:fullscreen .lotion-viewport{flex:1;aspect-ratio:auto !important}.lotion:fullscreen .lotion-bar{flex:none}.lotion.lotion-render{position:fixed;inset:0;z-index:99999;border:0;border-radius:0}.lotion.lotion-render .lotion-viewport{width:100vw;height:100vh;aspect-ratio:auto !important}.lotion.lotion-render .lotion-bar{display:none}@-moz-keyframes lotion-spin{to{transform:rotate(360deg)}}@-webkit-keyframes lotion-spin{to{transform:rotate(360deg)}}@-o-keyframes lotion-spin{to{transform:rotate(360deg)}}@keyframes lotion-spin{to{transform:rotate(360deg)}}
1
+ .lotion{--lotion-bg:#eceae5;--lotion-paper:#fbfaf7;--lotion-ink:#18181a;--lotion-mute:#8a877f;--lotion-line:#d9d6cf;position:relative;border:1px solid var(--lotion-line);border-radius:12px;overflow:hidden;outline:none}.lotion .lotion-viewport{position:relative;width:100%;overflow:hidden;background:var(--lotion-bg)}.lotion .lotion-stage{position:absolute;left:0;top:0;transform-origin:0 0;background:var(--lotion-bg);overflow:hidden;line-height:normal}.lotion .lotion-bar{display:flex;align-items:center;gap:18px;height:64px;padding:0 24px;background:var(--lotion-paper);border-top:1px solid var(--lotion-line);user-select:none}.lotion .lotion-btn{flex:none;width:40px;height:40px;border-radius:50%;background:var(--lotion-ink);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer}.lotion .lotion-play:before{content:"";margin-left:3px;border-style:solid;border-width:7px 0 7px 12px;border-color:transparent transparent transparent #fff}.lotion.lotion-playing .lotion-play:before{margin-left:0;width:4px;height:14px;border-width:0 4px;border-color:#fff}.lotion .lotion-fs{background:transparent;color:var(--lotion-ink);border:1px solid var(--lotion-line)}.lotion .lotion-cc{width:auto;height:28px;padding:0 8px;border-radius:6px;font:600 13px/1 sans-serif;letter-spacing:.04em;background:transparent;color:var(--lotion-ink);border:1px solid var(--lotion-line)}.lotion.lotion-captions-on .lotion-cc{background:var(--lotion-ink);border-color:var(--lotion-ink);color:#fff}.lotion .lotion-viewport{container-type:inline-size}.lotion .lotion-captions{--lotion-caption-size:clamp(14px,2.6cqw,44px);position:absolute;left:10%;right:10%;bottom:6%;display:none;flex-direction:column;align-items:center;gap:.2em;pointer-events:none;text-align:center;font-size:var(--lotion-caption-size);line-height:1.35}.lotion.lotion-captions-on .lotion-captions{display:flex}.lotion .lotion-caption{max-width:100%;padding:.12em .5em;border-radius:.2em;background:var(--lotion-caption-bg,rgba(0,0,0,0.72));color:var(--lotion-caption-color,#fff);font-family:var(--lotion-caption-font,sans-serif);white-space:pre-wrap}.lotion .lotion-time{font:15px/1 sans-serif;color:var(--lotion-mute);font-variant-numeric:tabular-nums;white-space:nowrap}.lotion .lotion-track{position:relative;flex:1;height:40px;cursor:pointer}.lotion .lotion-rail,.lotion .lotion-fill{position:absolute;left:0;top:18px;height:4px;border-radius:2px}.lotion .lotion-rail{right:0;background:var(--lotion-line)}.lotion .lotion-fill{background:var(--lotion-ink)}.lotion .lotion-knob{position:absolute;top:13px;width:14px;height:14px;margin-left:-7px;border-radius:50%;background:var(--lotion-ink)}.lotion .lotion-chapters{position:absolute;inset:0;pointer-events:none}.lotion .lotion-ch{position:absolute;top:14px;width:2px;height:12px;margin-left:-1px;border-radius:1px;background:var(--lotion-mute);opacity:.5}.lotion.lotion-loading .lotion-stage{visibility:hidden}.lotion.lotion-loading .lotion-bar{opacity:.5;pointer-events:none}.lotion.lotion-encoding .lotion-bar{opacity:.5;pointer-events:none}.lotion .lotion-loading-screen,.lotion .lotion-encoding-screen{position:absolute;inset:0;display:flex;flex-direction:column;gap:12px;align-items:center;justify-content:center;background:var(--lotion-bg);color:var(--lotion-mute)}.lotion .lotion-progress{font-size:14px;font-variant-numeric:tabular-nums}.lotion .lotion-spinner{width:36px;height:36px;border-radius:50%;border:3px solid var(--lotion-line);border-top-color:var(--lotion-ink);animation:lotion-spin .8s linear infinite}.lotion:fullscreen{display:flex;flex-direction:column;border:0;border-radius:0}.lotion:fullscreen .lotion-viewport{flex:1;aspect-ratio:auto !important}.lotion:fullscreen .lotion-bar{flex:none}.lotion.lotion-render{position:fixed;inset:0;z-index:99999;border:0;border-radius:0}.lotion.lotion-render .lotion-viewport{width:100vw;height:100vh;aspect-ratio:auto !important}.lotion.lotion-render .lotion-bar{display:none}@-moz-keyframes lotion-spin{to{transform:rotate(360deg)}}@-webkit-keyframes lotion-spin{to{transform:rotate(360deg)}}@-o-keyframes lotion-spin{to{transform:rotate(360deg)}}@keyframes lotion-spin{to{transform:rotate(360deg)}}
package/index.min.js CHANGED
@@ -1 +1 @@
1
- !function(){var i,r,o,s,l,n,w,a,u,h,e,t,d,c,f,p,v,g,m,y,b,k,x,M,E,L,C,F;i=Math.PI,r=Math.sin,o=Math.cos,s=Math.exp,l=Math.sqrt,n=Math.round,w=function(t,e,n){return null==n&&(n=1),t<(e=null==e?0:e)?e:n<t?n:t},a=function(t,e,n){return t+(e-t)*n},u=function(t){return(t=w(t))*t*(3-2*t)},h=function(t,e,n){var i;return null==e&&(e=14),t<=0?0:1<=(n=null==n?.8:n)?1-s(-e*t)*(1+e*t):(i=e*l(1-n*n),1-s(-n*e*t)*(o(i*t)+n*e/i*r(i*t)))},e={fast:{w:22,z:.86},std:{w:14,z:.8},soft:{w:9,z:.9},move:{w:11,z:.85},ease:{w:10,z:1}},t=function(l,t){var a,u=(a=function(t){return"string"==typeof t?e[t]:t})(t=null==t?"std":t);return function(t){for(var e,n,i,r=l[0][1],o=1,s=l.length;o<s;++o)i=(n=l[e=o])[2]?a(n[2]):u,r+=(n[1]-l[e-1][1])*h(t-n[0],i.w,i.z);return r}},d=function(t,e,n,i,r){return u((t-e)/(i=null==i?.4:i))*(1-u((t-(n=null==n?1e9:n))/(r=null==r?.35:r)))},c=function(t,e,n){return null==n&&(n=.6),t<e||e+n<t?0:Math.pow(r(i*(t-e)/n),2)},f=function(t,e,n,i){return null==i&&(i=.06),(n=Array.from(n)).slice(0,w(Math.floor((t-e)/i)+1,0,n.length)).join("")},p=function(e){return[1,3,5].map(function(t){return parseInt(e.slice(t,t+2),16)})},v=function(e,n,i){return[0,1,2].map(function(t){return a(e[t],n[t],i)})},g=function(t,e){return null!=e?"rgba("+t.map(function(t){return n(t)}).join(",")+","+e+")":"rgb("+t.map(function(t){return n(t)}).join(",")+")"},m=function(t,e,n,i){var r;return null==t&&(t=""),null==e&&(e=""),null==i&&(i=""),(r=document.createElement("div")).className=t.split(" ").filter(function(t){return t}).map(function(t){return i+t}).join(" "),r.innerHTML=e,n&&n.appendChild(r),r},y=function(t,e,n,i,r,o){var s;if(null==i&&(i=1),null==r&&(r=1),!((s=null==(s=(o=null==o?{}:o).hide)||s)&&r<=.002))return s&&!0!==t._lv&&(t.style.visibility="visible",t._lv=!0),t.style.transform="translate("+e.toFixed(2)+"px,"+n.toFixed(2)+"px) scale("+i.toFixed(4)+")",t.style.opacity=r.toFixed(3),null!=o.blur?t.style.filter=.05<o.blur?"blur("+o.blur.toFixed(2)+"px)":"":void 0;!1!==t._lv&&(t.style.visibility="hidden",t._lv=!1)},b=function(t,e){if(t._lt!==e)return t.innerHTML=e,t._lt=e},k=function(t,e,n){var i,r,o;for(r in null==e&&(e={}),i=document.createElementNS("http://www.w3.org/2000/svg",t),e)o=e[r],i.setAttribute(r,o);return n&&n.appendChild(i),i},(x=function(t){var e=this;return this.opt=t=null==t?{}:t,this.root="string"==typeof t.root?document.querySelector(t.root):t.root,this.width=t.width||1920,this.height=t.height||1080,this.duration=t.duration||0,this.chapters=t.chapters||[],this.t=0,this.playing=!1,this.last=null,this.started=!1,this.ready=new Promise(function(t){return e.readyRes=t}),this.renderMode=/[?&]render\b/.test(location.search),this.init(),this}).prototype=((F=Object.create(Object.prototype)).constructor=x,F.init=function(){var t,i=this,e=this.root;return e.classList.add("lotion"),e.hasAttribute("tabindex")||e.setAttribute("tabindex",0),this.viewport=m("lotion-viewport","",e),this.stage=m("lotion-stage","",this.viewport),(t=this.stage.style).width=this.width+"px",t.height=this.height+"px",this.viewport.style.aspectRatio=this.width+" / "+this.height,e.classList.add("lotion-loading"),!1!==this.opt.loading&&(t="string"==typeof this.opt.loading?this.opt.loading:'<div class="lotion-spinner"></div>',this.loading=m("lotion-loading-screen",t,this.viewport)),this.bar=m("lotion-bar",'<div class="lotion-btn lotion-play"></div>\n<div class="lotion-track"><div class="lotion-rail"></div><div class="lotion-fill"></div>\n<div class="lotion-chapters"></div><div class="lotion-knob"></div></div>\n<div class="lotion-time"></div>\n<div class="lotion-btn lotion-fs" title="全螢幕"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"\nstroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M2 6V2h4M10 2h4v4M14 10v4h-4M6 14H2v-4"/></svg></div>',e),this.el={play:(t=function(t){return i.bar.querySelector(".lotion-"+t)})("play"),fill:t("fill"),knob:t("knob"),time:t("time"),track:t("track"),chapters:t("chapters"),fs:t("fs")},this.chapters.map(function(t){var e=t[0],t=t[1],n=m("lotion-ch","",i.el.chapters);return n.title=t,n.style.left=100*e/i.duration+"%"}),this.bind(),this.renderMode&&e.classList.add("lotion-render"),this.fit(),"undefined"!=typeof ResizeObserver?new ResizeObserver(function(){return i.fit()}).observe(this.viewport):window.addEventListener("resize",function(){return i.fit()})},F.start=function(){var t,e=this;return this.started=!0,this.root.classList.remove("lotion-loading"),this.loading&&this.loading.remove(),this.renderMode&&(window.seek=function(t){return e.opt.seek(t)},window.DURATION=this.duration,this.opt.cues)&&(window.cues=this.opt.cues),this.readyRes(this),t=/[?&]t=([\d.]+)/.exec(location.search),this.seek(t?+t[1]:!this.renderMode&&this.opt.start||0),this.opt.autoplay&&!this.renderMode&&document.fonts.ready.then(function(){return e.play(!0)}),this},F.bind=function(){var e,n,i=this;return this.el.play.addEventListener("click",function(t){return t.stopPropagation(),i.toggle()}),this.el.fs.addEventListener("click",function(t){return t.stopPropagation(),i.fullscreen()}),this.viewport.addEventListener("click",function(){return i.root.focus(),i.toggle()}),e=!1,n=function(t){var e=i.el.track.getBoundingClientRect();return i.seek(i.duration*w((t.clientX-e.left)/e.width))},this.el.track.addEventListener("pointerdown",function(t){return e=!0,i.el.track.setPointerCapture(t.pointerId),n(t)}),this.el.track.addEventListener("pointermove",function(t){if(e)return n(t)}),this.el.track.addEventListener("pointerup",function(){return e=!1}),this.root.addEventListener("keydown",function(t){var e,n=i.chapters.map(function(t){return t[0]});return"Space"===t.code?(t.preventDefault(),i.toggle()):"f"===t.key?i.fullscreen():"ArrowRight"===t.key?i.seek(null!=(e=n.find(function(t){return t>i.t+.05}))?e:i.duration):"ArrowLeft"===t.key?i.seek(null!=(e=n.filter(function(t){return t<i.t-.5}).pop())?e:0):void 0})},F.fit=function(){var t,e,n;if(!this.encoding&&(t=this.viewport.clientWidth,e=this.viewport.clientHeight,t)&&e)return n=Math.min(t/this.width,e/this.height),this.stage.style.transform="translate("+(t-this.width*n)/2+"px,"+(e-this.height*n)/2+"px) scale("+n+")"},F.fmt=function(t){return Math.floor(t/60)+":"+("0"+Math.floor(t%60)).slice(-2)},F.seek=function(e){var t;if(this.started&&!this.encoding)return this.t=e=w(e,0,this.duration),this.opt.seek(e),t=100*e/this.duration+"%",this.el.fill.style.width=t,this.el.knob.style.left=t,t=this.chapters.filter(function(t){return t[0]<=e}).pop(),this.el.time.textContent=(t?t[1]+" · ":"")+this.fmt(e)+" / "+this.fmt(this.duration),this.root.classList.toggle("lotion-playing",this.playing)},F.tick=function(t){var e=this;if(this.playing){if(null!=this.last&&this.seek(this.t+(t-this.last)/1e3),this.last=t,!(this.t>=this.duration))return requestAnimationFrame(function(t){return e.tick(t)});this.playing=!1,this.seek(this.t)}},F.play=function(t){var e=this;if(null==t&&(t=!0),this.started&&!this.encoding)return t&&this.t>=this.duration&&(this.t=0),this.playing=t,this.last=null,this.seek(this.t),t?requestAnimationFrame(function(t){return e.tick(t)}):void 0},F.pause=function(){return this.play(!1)},F.toggle=function(){return this.play(!this.playing)},F.fullscreen=function(){var t=document;return t.fullscreenElement||t.webkitFullscreenElement?(t.exitFullscreen||t.webkitExitFullscreen).call(t):(this.root.requestFullscreen||this.root.webkitRequestFullscreen).call(this.root),this.root.focus()},F.encode=function(e){var t,n,i,r,o,s,l,a=this;return null==e&&(e={}),this.started?this.encoding?Promise.reject(new Error("[lotion] already encoding")):(this.pause(),t=this.t,this.encoding=!0,this.root.classList.add("lotion-encoding"),n=this.stage.style.transform,this.stage.style.transform="none",i=m("lotion-encoding-screen",'<div class="lotion-spinner"></div><div class="lotion-progress"></div>',this.viewport),r=i.querySelector(".lotion-progress"),o=function(){return i.remove(),a.stage.style.transform=n,a.encoding=!1,a.root.classList.remove("lotion-encoding"),a.fit(),a.seek(t)},s=function(t){if(r.textContent=Math.round(100*t)+"%",e.progress)return e.progress(t)},C(((l=((t,e)=>{var n,i={}.hasOwnProperty;for(n in e)i.call(e,n)&&(t[n]=e[n]);return t})({el:this.stage,seek:this.opt.seek,duration:this.duration,width:this.width,height:this.height},e)).progress=s,l)).then(function(t){return o(),t}).catch(function(t){throw o(),t})):Promise.reject(new Error("[lotion] encode() before start()"))},F),E={},F={clamp:w,lerp:a,ss:u,spring:h,presets:e,track:t,vis:d,bump:c,typing:f,hex:p,mixc:v,rgb:g,mk:m,put:y,txt:b,svg:k,player:x,encode:C=function(a){var u,h,d,c,f,p,v,g,m,r,t;return null==a&&(a={}),"undefined"==typeof VideoEncoder?Promise.reject(new Error("[lotion] WebCodecs ( VideoEncoder ) is not supported in this browser")):(u=(t=function(t){return 2*Math.round(t/2)})(a.width||1920),h=t(a.height||1080),d=a.fps||30,c=Math.max(1,Math.round(a.sub||1)),f=(null!=(t=a.shutter)?t:.5)/d/c,p=a.from||0,t=null!=(t=a.to)?t:a.duration,v=Math.max(1,Math.round((t-p)*d)),(g=a.background||getComputedStyle(a.el).backgroundColor)&&"rgba(0, 0, 0, 0)"!==g&&"transparent"!==g||(g="#000"),m=function(){if(a.signal&&a.signal.aborted)throw new DOMException("export aborted","AbortError")},r=null,Promise.all([(t=a.snapdom)?Promise.resolve(t):L("snapdom"),(t=a.mediabunny)?Promise.resolve(t):L("mediabunny")]).then(function(t){var s,e=t[0],n=t[1],l=e.snapdom||e,i=document.createElement("canvas");return i.width=u,i.height=h,s=i.getContext("2d"),n.getFirstEncodableVideoCodec(["avc","vp9","av1"],{width:u,height:h}).then(function(t){var e;if(t)return r=new n.Output({format:new n.Mp4OutputFormat({fastStart:"in-memory"}),target:new n.BufferTarget}),e=new n.CanvasSource(i,{codec:t,bitrate:a.bitrate||n.QUALITY_HIGH}),r.addVideoTrack(e,{frameRate:d}),r.start().then(function(){return e});throw new Error("[lotion] no encodable video codec for "+u+"x"+h)}).then(function(i){var r=Promise.resolve(),o=function(e,n){var t;return m(),v<=e?r:c<=n?r.then(function(){return m(),r=i.add(e/d,1/d),a.progress&&a.progress((e+1)/v),o(e+1,0)}):(t=w(p+e/d+(1<c?(n-(c-1)/2)*f:0),0,a.duration),a.seek(t),l.toCanvas(a.el,{width:u,height:h,dpr:1,backgroundColor:g}).then(function(t){return s.globalAlpha=1/(n+1),s.drawImage(t,0,0,u,h),o(e,n+1)}))};return o(0,0)})}).then(function(){return r.finalize()}).then(function(){return new Blob([r.target.buffer],{type:"video/mp4"})}).catch(function(t){var e;throw!r||"started"!==(e=r.state)&&"pending"!==e||r.cancel(),t}))},libs:M={snapdom:"https://cdn.jsdelivr.net/npm/@zumer/snapdom@3.3.0/dist/snapdom.mjs",mediabunny:"https://cdn.jsdelivr.net/npm/mediabunny@1.61.3/dist/bundles/mediabunny.min.mjs"},lib:L=function(t){var e;return null!=(e=E[t])?e:E[t]=import(M[t])}},"undefined"!=typeof module&&null!==module?module.exports=F:"undefined"!=typeof window&&null!==window&&(window.lotion=F)}.call(this);
1
+ !function(){var e,r,o,s,a,i,w,l,u,c,n,t,h,d,p,f,g,v,m,y,b,k,x,L,M,C,E,O,j,A;function F(t,n){for(var i=-1,e=n.length>>>0;++i<e;)if(t===n[i])return 1}e=Math.PI,r=Math.sin,o=Math.cos,s=Math.exp,a=Math.sqrt,i=Math.round,w=function(t,n,i){return null==i&&(i=1),t<(n=null==n?0:n)?n:i<t?i:t},l=function(t,n,i){return t+(n-t)*i},u=function(t){return(t=w(t))*t*(3-2*t)},c=function(t,n,i){var e;return null==n&&(n=14),t<=0?0:1<=(i=null==i?.8:i)?1-s(-n*t)*(1+n*t):(e=n*a(1-i*i),1-s(-i*n*t)*(o(e*t)+i*n/e*r(e*t)))},n={fast:{w:22,z:.86},std:{w:14,z:.8},soft:{w:9,z:.9},move:{w:11,z:.85},ease:{w:10,z:1}},t=function(a,t){var l,u=(l=function(t){return"string"==typeof t?n[t]:t})(t=null==t?"std":t);return function(t){for(var n,i,e,r=a[0][1],o=1,s=a.length;o<s;++o)e=(i=a[n=o])[2]?l(i[2]):u,r+=(i[1]-a[n-1][1])*c(t-i[0],e.w,e.z);return r}},h=function(t,n,i,e,r){return u((t-n)/(e=null==e?.4:e))*(1-u((t-(i=null==i?1e9:i))/(r=null==r?.35:r)))},d=function(t,n,i){return null==i&&(i=.6),t<n||n+i<t?0:Math.pow(r(e*(t-n)/i),2)},p=function(t,n,i,e){return null==e&&(e=.06),(i=Array.from(i)).slice(0,w(Math.floor((t-n)/e)+1,0,i.length)).join("")},f=function(n){return[1,3,5].map(function(t){return parseInt(n.slice(t,t+2),16)})},g=function(n,i,e){return[0,1,2].map(function(t){return l(n[t],i[t],e)})},v=function(t,n){return null!=n?"rgba("+t.map(function(t){return i(t)}).join(",")+","+n+")":"rgb("+t.map(function(t){return i(t)}).join(",")+")"},m=function(t,n,i,e){var r;return null==t&&(t=""),null==n&&(n=""),null==e&&(e=""),(r=document.createElement("div")).className=t.split(" ").filter(function(t){return t}).map(function(t){return e+t}).join(" "),r.innerHTML=n,i&&i.appendChild(r),r},y=function(t,n,i,e,r,o){var s;if(null==e&&(e=1),null==r&&(r=1),!((s=null==(s=(o=null==o?{}:o).hide)||s)&&r<=.002))return s&&!0!==t._lv&&(t.style.visibility="visible",t._lv=!0),t.style.transform="translate("+n.toFixed(2)+"px,"+i.toFixed(2)+"px) scale("+e.toFixed(4)+")",t.style.opacity=r.toFixed(3),null!=o.blur?t.style.filter=.05<o.blur?"blur("+o.blur.toFixed(2)+"px)":"":void 0;!1!==t._lv&&(t.style.visibility="hidden",t._lv=!1)},b=function(t,n){if(t._lt!==n)return t.innerHTML=n,t._lt=n},k=function(t,n,i){var e,r,o;for(r in null==n&&(n={}),e=document.createElementNS("http://www.w3.org/2000/svg",t),n)o=n[r],e.setAttribute(r,o);return i&&i.appendChild(e),e},(x=function(t){var n,i,e,r,o=this;for(i in this.opt=t=null==t?{}:t,this.root="string"==typeof t.root?document.querySelector(t.root):t.root,this.width=t.width||1920,this.height=t.height||1080,this.duration=t.duration||0,this.chapters=t.chapters||[],this.t=0,this.chapter=null,this.caption=null,this.handlers={},this.playing=!1,this.last=null,this.started=!1,this.ready=new Promise(function(t){return o.readyRes=t}),this.renderMode=/[?&]render\b/.test(location.search),n=(r=t.captions)?Array.isArray(r)?{"":r}:r:{},this.captionTracks={},n)e=n[i],this.captionTracks[i]=e.slice().sort(s);return this.captionLangs=Object.keys(this.captionTracks),this.captionLang=F(t.captionLang,this.captionLangs)?t.captionLang:null!=(r=this.captionLangs[0])?r:null,this.captionsOn=this.renderMode?!(!t.burnCaptions&&!/[?&]captions\b/.test(location.search)):!t.captionsBurned,this.init(),this;function s(t,n){return t[0]-n[0]}}).prototype=((A=Object.create(Object.prototype)).constructor=x,A.init=function(){var t,r=this,n=this.root;return n.classList.add("lotion"),n.hasAttribute("tabindex")||n.setAttribute("tabindex",0),this.viewport=m("lotion-viewport","",n),this.stage=m("lotion-stage","",this.viewport),(t=this.stage.style).width=this.width+"px",t.height=this.height+"px",this.viewport.style.aspectRatio=this.width+" / "+this.height,n.classList.add("lotion-loading"),!1!==this.opt.loading&&(t="string"==typeof this.opt.loading?this.opt.loading:'<div class="lotion-spinner"></div>',this.loading=m("lotion-loading-screen",t,this.viewport)),this.bar=m("lotion-bar",'<div class="lotion-btn lotion-play"></div>\n<div class="lotion-track"><div class="lotion-rail"></div><div class="lotion-fill"></div>\n<div class="lotion-chapters"></div><div class="lotion-knob"></div></div>\n<div class="lotion-time"></div>\n<div class="lotion-btn lotion-cc" title="字幕 ( c )">CC</div>\n<div class="lotion-btn lotion-fs" title="全螢幕"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"\nstroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M2 6V2h4M10 2h4v4M14 10v4h-4M6 14H2v-4"/></svg></div>',n),this.el={play:(t=function(t){return r.bar.querySelector(".lotion-"+t)})("play"),fill:t("fill"),knob:t("knob"),time:t("time"),track:t("track"),chapters:t("chapters"),fs:t("fs"),cc:t("cc")},this.chapters.map(function(t){var n=t[0],i=t[1],t=t[2],e=m("lotion-ch","",r.el.chapters);return e.title=i+(t&&t.description?"\n"+t.description:""),e.style.left=100*n/r.duration+"%"}),this.el.captions=m("lotion-captions","",this.viewport),this.captionLangs.length||this.el.cc.remove(),this.root.classList.toggle("lotion-captions-on",this.captionsOn),this.bind(),this.renderMode&&n.classList.add("lotion-render"),this.fit(),"undefined"!=typeof ResizeObserver?new ResizeObserver(function(){return r.fit()}).observe(this.viewport):window.addEventListener("resize",function(){return r.fit()})},A.start=function(){var t,n=this;return this.started=!0,this.root.classList.remove("lotion-loading"),this.loading&&this.loading.remove(),this.renderMode&&(window.seek=function(t){return n.opt.seek(t)},window.DURATION=this.duration,this.opt.cues&&(window.cues=this.opt.cues),this.captionLangs.length)&&(window.CAPTIONS=this.captionTracks),this.readyRes(this),t=/[?&]t=([\d.]+)/.exec(location.search),this.seek(t?+t[1]:!this.renderMode&&this.opt.start||0),this.opt.autoplay&&!this.renderMode&&document.fonts.ready.then(function(){return n.play(!0)}),this},A.bind=function(){var n,i,e=this;return this.el.play.addEventListener("click",function(t){return t.stopPropagation(),e.toggle()}),this.el.fs.addEventListener("click",function(t){return t.stopPropagation(),e.fullscreen()}),this.el.cc.addEventListener("click",function(t){return t.stopPropagation(),e.showCaptions(!e.captionsOn)}),this.viewport.addEventListener("click",function(){return e.root.focus(),e.toggle()}),n=!1,i=function(t){var n=e.el.track.getBoundingClientRect();return e.seek(e.duration*w((t.clientX-n.left)/n.width))},this.el.track.addEventListener("pointerdown",function(t){return n=!0,e.el.track.setPointerCapture(t.pointerId),i(t)}),this.el.track.addEventListener("pointermove",function(t){if(n)return i(t)}),this.el.track.addEventListener("pointerup",function(){return n=!1}),this.root.addEventListener("keydown",function(t){var n,i=e.chapters.map(function(t){return t[0]});return"Space"===t.code?(t.preventDefault(),e.toggle()):"f"===t.key?e.fullscreen():"c"===t.key&&e.captionLangs.length?e.showCaptions(!e.captionsOn):"ArrowRight"===t.key?e.seek(null!=(n=i.find(function(t){return t>e.t+.05}))?n:e.duration):"ArrowLeft"===t.key?e.seek(null!=(n=i.filter(function(t){return t<e.t-.5}).pop())?n:0):void 0})},A.fit=function(){var t,n,i;if(!this.encoding&&(t=this.viewport.clientWidth,n=this.viewport.clientHeight,t)&&n)return i=Math.min(t/this.width,n/this.height),this.stage.style.transform="translate("+(t-this.width*i)/2+"px,"+(n-this.height*i)/2+"px) scale("+i+")"},A.fmt=function(t){return Math.floor(t/60)+":"+("0"+Math.floor(t%60)).slice(-2)},A.seek=function(t){var n,i,e,r,o,s;if(this.started&&!this.encoding){for(this.t=t=w(t,0,this.duration),this.opt.seek(t),s=100*t/this.duration+"%",this.el.fill.style.width=s,this.el.knob.style.left=s,n=-1,i=0,r=(e=this.chapters).length;i<r;++i)e[o=i][0]<=t&&(n=o);return s=this.chapters[n],this.el.time.textContent=(s?s[1]+" · ":"")+this.fmt(t)+" / "+this.fmt(this.duration),this.root.classList.toggle("lotion-playing",this.playing),(s?n:-1)!==(this.chapter?this.chapter.index:-1)&&(this.chapter=s?{index:n,t:s[0],name:s[1],meta:s[2]||null}:null,this.fire("chapter",this.chapter)),this.updateCaption(),this.fire("time",t)}},A.updateCaption=function(){var t,n,i,e,r=this.t,o=(null!=this.captionLang&&this.captionTracks[this.captionLang]||[]).filter(function(t){return t[0]<=r&&r<t[1]}),o=o.length?{t0:o[0][0],t1:o[o.length-1][1],text:o.map(function(t){return t[2]}).join("\n"),meta:o[0][3]||null,lang:this.captionLang}:null,s=function(t){return t?t.lang+"|"+t.t0+"|"+t.text:""};if(s(o)!==s(this.caption)){if(this.caption=o,this.el.captions.innerHTML="",o)for(t=0,i=(n=o.text.split("\n")).length;t<i;++t)e=n[t],m("lotion-caption","",this.el.captions).textContent=e;return this.fire("caption",o)}},A.showCaptions=function(t){return this.captionsOn=!!(t=null==t?!0:t),this.root.classList.toggle("lotion-captions-on",this.captionsOn),this.fire("captions",{on:this.captionsOn,lang:this.captionLang}),this},A.setCaptionLang=function(t){return F(t,this.captionLangs)&&(this.captionLang=t,this.updateCaption(),this.fire("captions",{on:this.captionsOn,lang:t})),this},A.on=function(t,n){var i,e;return(null!=(e=(i=this.handlers)[t])?e:i[t]=[]).push(n),this},A.off=function(t,n){return this.handlers[t]&&(this.handlers[t]=this.handlers[t].filter(function(t){return t!==n})),this},A.fire=function(t,n){for(var i,e,r=[],o=0,s=(i=(this.handlers[t]||[]).slice()).length;o<s;++o)e=i[o],r.push(e(n));return r},A.tick=function(t){var n=this;if(this.playing){if(null!=this.last&&this.seek(this.t+(t-this.last)/1e3),this.last=t,!(this.t>=this.duration))return requestAnimationFrame(function(t){return n.tick(t)});this.playing=!1,this.seek(this.t)}},A.play=function(t){var n=this;if(null==t&&(t=!0),this.started&&!this.encoding)return t&&this.t>=this.duration&&(this.t=0),this.playing=t,this.last=null,this.seek(this.t),t?requestAnimationFrame(function(t){return n.tick(t)}):void 0},A.pause=function(){return this.play(!1)},A.toggle=function(){return this.play(!this.playing)},A.fullscreen=function(){var t=document;return t.fullscreenElement||t.webkitFullscreenElement?(t.exitFullscreen||t.webkitExitFullscreen).call(t):(this.root.requestFullscreen||this.root.webkitRequestFullscreen).call(this.root),this.root.focus()},A.encode=function(n){var t,i,e,r,o,s,a,l=this;return null==n&&(n={}),this.started?this.encoding?Promise.reject(new Error("[lotion] already encoding")):(this.pause(),t=this.t,this.encoding=!0,this.root.classList.add("lotion-encoding"),i=this.stage.style.transform,this.stage.style.transform="none",e=m("lotion-encoding-screen",'<div class="lotion-spinner"></div><div class="lotion-progress"></div>',this.viewport),r=e.querySelector(".lotion-progress"),o=function(){return e.remove(),l.stage.style.transform=i,l.encoding=!1,l.root.classList.remove("lotion-encoding"),l.fit(),l.seek(t)},s=function(t){if(r.textContent=Math.round(100*t)+"%",n.progress)return n.progress(t)},E(((a=((t,n)=>{var i,e={}.hasOwnProperty;for(i in n)e.call(n,i)&&(t[i]=n[i]);return t})({el:this.stage,seek:this.opt.seek,duration:this.duration,width:this.width,height:this.height},n)).progress=s,a)).then(function(t){return o(),t}).catch(function(t){throw o(),t})):Promise.reject(new Error("[lotion] encode() before start()"))},A),M={},O=function(t){var t=Math.round(1e3*t),n=Math.floor(t/36e5),i=Math.floor(t/6e4)%60,e=Math.floor(t/1e3)%60,r=function(t,n){return(t+"").padStart(n=null==n?2:n,"0")};return r(n)+":"+r(i)+":"+r(e)+"."+r(t%1e3,3)},j=function(t){return t.trim().split(":").reduce(function(t,n){return 60*t+parseFloat(n)},0)},A={vtt:{parse:function(t){for(var n,i,e,r,o,s=[],a=t.replace(/\r\n?/g,"\n").split(/\n{2,}/),l=0,u=a.length;l<u;++l)(r=(e=a[l].split("\n").filter(c)).findIndex(h))<0||(n=(i=e[r].split("--\x3e"))[0],i=i[1],e=e.slice(r+1).join("\n"),r=null,(o=/^<v(?:\.[^\s>]*)?\s+([^>]+)>/.exec(e))&&(r={speaker:o[1].trim()}),e=e.replace(/<\/?[^>]+>/g,"").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&amp;/g,"&"),o=[j(n),j(i.trim().split(/\s+/)[0]),e],r&&o.push(r),s.push(o));return s;function c(t){return t.trim()}function h(t){return/-->/.test(t)}},stringify:function(t){var r=function(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")};return"WEBVTT\n\n"+t.map(function(t){var n=t[0],i=t[1],e=t[3],t=r(t[2]);return e&&e.speaker&&(t="<v "+r(e.speaker)+">"+t),O(n)+" --\x3e "+O(i)+"\n"+t}).join("\n\n")+"\n"}},clamp:w,lerp:l,ss:u,spring:c,presets:n,track:t,vis:h,bump:d,typing:p,hex:f,mixc:g,rgb:v,mk:m,put:y,txt:b,svg:k,player:x,encode:E=function(l){var u,c,h,d,p,f,g,v,m,r,t;return null==l&&(l={}),"undefined"==typeof VideoEncoder?Promise.reject(new Error("[lotion] WebCodecs ( VideoEncoder ) is not supported in this browser")):(u=(t=function(t){return 2*Math.round(t/2)})(l.width||1920),c=t(l.height||1080),h=l.fps||30,d=Math.max(1,Math.round(l.sub||1)),p=(null!=(t=l.shutter)?t:.5)/h/d,f=l.from||0,t=null!=(t=l.to)?t:l.duration,g=Math.max(1,Math.round((t-f)*h)),(v=l.background||getComputedStyle(l.el).backgroundColor)&&"rgba(0, 0, 0, 0)"!==v&&"transparent"!==v||(v="#000"),m=function(){if(l.signal&&l.signal.aborted)throw new DOMException("export aborted","AbortError")},r=null,Promise.all([(t=l.snapdom)?Promise.resolve(t):C("snapdom"),(t=l.mediabunny)?Promise.resolve(t):C("mediabunny")]).then(function(t){var s,n=t[0],i=t[1],a=n.snapdom||n,e=document.createElement("canvas");return e.width=u,e.height=c,s=e.getContext("2d"),i.getFirstEncodableVideoCodec(["avc","vp9","av1"],{width:u,height:c}).then(function(t){var n;if(t)return r=new i.Output({format:new i.Mp4OutputFormat({fastStart:"in-memory"}),target:new i.BufferTarget}),n=new i.CanvasSource(e,{codec:t,bitrate:l.bitrate||i.QUALITY_HIGH}),r.addVideoTrack(n,{frameRate:h}),r.start().then(function(){return n});throw new Error("[lotion] no encodable video codec for "+u+"x"+c)}).then(function(e){var r=Promise.resolve(),o=function(n,i){var t;return m(),g<=n?r:d<=i?r.then(function(){return m(),r=e.add(n/h,1/h),l.progress&&l.progress((n+1)/g),o(n+1,0)}):(t=w(f+n/h+(1<d?(i-(d-1)/2)*p:0),0,l.duration),l.seek(t),a.toCanvas(l.el,{width:u,height:c,dpr:1,backgroundColor:v}).then(function(t){return s.globalAlpha=1/(i+1),s.drawImage(t,0,0,u,c),o(n,i+1)}))};return o(0,0)})}).then(function(){return r.finalize()}).then(function(){return new Blob([r.target.buffer],{type:"video/mp4"})}).catch(function(t){var n;throw!r||"started"!==(n=r.state)&&"pending"!==n||r.cancel(),t}))},libs:L={snapdom:"https://cdn.jsdelivr.net/npm/@zumer/snapdom@3.3.0/dist/snapdom.mjs",mediabunny:"https://cdn.jsdelivr.net/npm/mediabunny@1.61.3/dist/bundles/mediabunny.min.mjs"},lib:C=function(t){var n;return null!=(n=M[t])?n:M[t]=import(L[t])}},"undefined"!=typeof module&&null!==module?module.exports=A:"undefined"!=typeof window&&null!==window&&(window.lotion=A)}.call(this);
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"zbryikt","name":"@plotdb/lotion","version":"0.4.2","description":"deterministic motion toolkit: animation as a pure function of time, with player, frame renderer and block packaging","license":"MIT","main":"index.min.js","browser":"index.min.js","bin":{"lotion":"./cli.js"},"homepage":"https://github.com/plotdb/lotion","repository":{"type":"git","url":"git+https://github.com/plotdb/lotion.git"},"scripts":{"start":"npx server -r web -o true"},"engines":{"node":">=14"},"keywords":["motion","animation","video","playwright","block"],"dependencies":{"@plotdb/block":"^5.7.3","@plotdb/csscope":"^5.0.4","@plotdb/rescope":"^5.1.0","@plotdb/semver":"^0.0.4","proxise":"^1.0.1"},"peerDependencies":{"playwright":">=1.40"},"peerDependenciesMeta":{"playwright":{"optional":true}},"devDependencies":{"@plotdb/guides":"github:plotdb/guides","@zbryikt/template":"^2.5.0","@zumer/snapdom":"^3.3.0","fedep":"^1.9.0","livescript":"^1.6.0","mediabunny":"^1.61.3","playwright":"^1.63.0","stylus":"^0.64.0","uglify-js":"^3.19.3"},"frontendDependencies":{"root":"web/static/assets/lib","modules":["@zumer/snapdom","mediabunny"]}}
1
+ {"author":"zbryikt","name":"@plotdb/lotion","version":"0.5.0","description":"deterministic motion toolkit: animation as a pure function of time, with player, frame renderer and block packaging","license":"MIT","main":"index.min.js","browser":"index.min.js","bin":{"lotion":"./cli.js"},"homepage":"https://github.com/plotdb/lotion","repository":{"type":"git","url":"git+https://github.com/plotdb/lotion.git"},"scripts":{"start":"npx server -r web -o true"},"engines":{"node":">=14"},"keywords":["motion","animation","video","playwright","block"],"dependencies":{"@plotdb/block":"^5.7.3","@plotdb/csscope":"^5.0.4","@plotdb/rescope":"^5.1.0","@plotdb/semver":"^0.0.4","proxise":"^1.0.1"},"peerDependencies":{"playwright":">=1.40"},"peerDependenciesMeta":{"playwright":{"optional":true}},"devDependencies":{"@plotdb/guides":"github:plotdb/guides","@zbryikt/template":"^2.5.0","@zumer/snapdom":"^3.3.0","fedep":"^1.9.0","livescript":"^1.6.0","mediabunny":"^1.61.3","playwright":"^1.63.0","stylus":"^0.64.0","uglify-js":"^3.19.3"},"frontendDependencies":{"root":"web/static/assets/lib","modules":["@zumer/snapdom","mediabunny"]}}