@lolannn/dsh-effort-slider 0.2.5 → 0.2.7
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 +19 -0
- package/client.js +2589 -2244
- package/package.json +60 -60
package/client.js
CHANGED
|
@@ -1,2244 +1,2589 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-effort-slider — browser half.
|
|
3
|
-
*
|
|
4
|
-
* Two registrations:
|
|
5
|
-
* conversation.input.right : a compact pill that shows the session's current
|
|
6
|
-
* reasoning effort and opens the panel.
|
|
7
|
-
* shell.overlay : the frame-wide panel, with a starfield, a live
|
|
8
|
-
* rail whose particles/ribbons/stars/galaxy scale
|
|
9
|
-
* with the chosen level, and light-blade bursts.
|
|
10
|
-
*
|
|
11
|
-
* The levels are read at runtime from the session model catalogue, so the
|
|
12
|
-
* slider always matches the efforts the current model actually declares.
|
|
13
|
-
*/
|
|
14
|
-
window.__ModuleLoader__.load({
|
|
15
|
-
id: "@lolannn/dsh-effort-slider",
|
|
16
|
-
factory: (require) => {
|
|
17
|
-
var module = { exports: {} };
|
|
18
|
-
var exports = module.exports;
|
|
19
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
20
|
-
|
|
21
|
-
const react = require("react");
|
|
22
|
-
const reactDom = require("react-dom");
|
|
23
|
-
const h = react.createElement;
|
|
24
|
-
|
|
25
|
-
const NS = "effort-slider";
|
|
26
|
-
const CSS_TAG = "dsh-effort-slider/client";
|
|
27
|
-
const EDGE = 31;
|
|
28
|
-
const SELECT_TIMEOUT_MS = 15000;
|
|
29
|
-
const COMMIT_DEBOUNCE_MS = 260;
|
|
30
|
-
|
|
31
|
-
/* ------------------------------------------------------------------ *
|
|
32
|
-
* dictionaries
|
|
33
|
-
* ------------------------------------------------------------------ */
|
|
34
|
-
|
|
35
|
-
const zh = {
|
|
36
|
-
pill: "思考强度",
|
|
37
|
-
pillHint: "模型与思考强度",
|
|
38
|
-
panelTitle: "思考强度",
|
|
39
|
-
modelLabel: "模型",
|
|
40
|
-
effortLabel: "思考强度",
|
|
41
|
-
noModels: "没有可用的模型",
|
|
42
|
-
triggerHint: "选择模型与思考强度",
|
|
43
|
-
hint: "拖动滑块,或用 ← / → / Home / End 切换档位",
|
|
44
|
-
reset: "回到默认档位",
|
|
45
|
-
close: "关闭",
|
|
46
|
-
switching: "正在切换…",
|
|
47
|
-
failed: "切换失败:{message}",
|
|
48
|
-
noSession: "当前没有活动会话",
|
|
49
|
-
noReasoning: "当前模型没有可调的思考强度档位",
|
|
50
|
-
clamped: "该模型没有「{from}」档,已自动切到最接近的「{to}」",
|
|
51
|
-
noEffortModel: "「{model}」没有思考强度档位,已按模型默认运行",
|
|
52
|
-
noLevelsTag: "无档位",
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
".es-trigger-
|
|
90
|
-
".es-trigger
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
".es-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
"
|
|
97
|
-
".es-
|
|
98
|
-
".es-
|
|
99
|
-
".es-
|
|
100
|
-
".es-
|
|
101
|
-
".es-
|
|
102
|
-
|
|
103
|
-
".es-menu-
|
|
104
|
-
".es-menu-
|
|
105
|
-
|
|
106
|
-
".es-
|
|
107
|
-
".es-
|
|
108
|
-
".es-
|
|
109
|
-
".es-
|
|
110
|
-
|
|
111
|
-
".es-
|
|
112
|
-
".es-
|
|
113
|
-
".es-menu-
|
|
114
|
-
".es-
|
|
115
|
-
".es-
|
|
116
|
-
".es-
|
|
117
|
-
".es-
|
|
118
|
-
|
|
119
|
-
".es-
|
|
120
|
-
".es-
|
|
121
|
-
".es-menu
|
|
122
|
-
".es-
|
|
123
|
-
".es-
|
|
124
|
-
".es-
|
|
125
|
-
".es-
|
|
126
|
-
".es-
|
|
127
|
-
".es-
|
|
128
|
-
|
|
129
|
-
".es-
|
|
130
|
-
".es-
|
|
131
|
-
".es-
|
|
132
|
-
|
|
133
|
-
".es-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
".es-
|
|
138
|
-
".es-
|
|
139
|
-
".es-
|
|
140
|
-
|
|
141
|
-
"
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
"
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
return
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
c.
|
|
358
|
-
c.
|
|
359
|
-
c.
|
|
360
|
-
c.
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
c.
|
|
370
|
-
|
|
371
|
-
c.
|
|
372
|
-
c.
|
|
373
|
-
|
|
374
|
-
c.
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function
|
|
378
|
-
|
|
379
|
-
c.
|
|
380
|
-
c
|
|
381
|
-
c.
|
|
382
|
-
|
|
383
|
-
c.
|
|
384
|
-
c.
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
S.
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
S.
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
if (
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
c
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
const
|
|
666
|
-
if (
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
if (
|
|
704
|
-
S.
|
|
705
|
-
continue;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
c.
|
|
729
|
-
c.
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
const
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
c.
|
|
761
|
-
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
c.
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
const
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
c.
|
|
832
|
-
c
|
|
833
|
-
c
|
|
834
|
-
c.
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
c.
|
|
865
|
-
|
|
866
|
-
c.
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
c.
|
|
878
|
-
c.
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
S.
|
|
1009
|
-
if (S.
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
if (
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
S.
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
ev.
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
S.
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
S.
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
S.
|
|
1147
|
-
S.
|
|
1148
|
-
S.
|
|
1149
|
-
S.
|
|
1150
|
-
S.
|
|
1151
|
-
S.
|
|
1152
|
-
S.
|
|
1153
|
-
S.
|
|
1154
|
-
S.
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
window.addEventListener("
|
|
1176
|
-
window.addEventListener("
|
|
1177
|
-
|
|
1178
|
-
S.
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
window.
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
S.
|
|
1200
|
-
S.
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
h("path", {
|
|
1277
|
-
d: "
|
|
1278
|
-
fill: "none",
|
|
1279
|
-
stroke: "
|
|
1280
|
-
strokeWidth:
|
|
1281
|
-
|
|
1282
|
-
})
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
"
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
return void 0;
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
const
|
|
1504
|
-
if (
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
if (
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
}
|
|
1580
|
-
return
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
const
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
if (
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
this.
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
this.store
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
this.
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
}
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
if (
|
|
1904
|
-
|
|
1905
|
-
return;
|
|
1906
|
-
}
|
|
1907
|
-
const
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
*/
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
)
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
)
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
1
|
+
/**
|
|
2
|
+
* dsh-effort-slider — browser half.
|
|
3
|
+
*
|
|
4
|
+
* Two registrations:
|
|
5
|
+
* conversation.input.right : a compact pill that shows the session's current
|
|
6
|
+
* reasoning effort and opens the panel.
|
|
7
|
+
* shell.overlay : the frame-wide panel, with a starfield, a live
|
|
8
|
+
* rail whose particles/ribbons/stars/galaxy scale
|
|
9
|
+
* with the chosen level, and light-blade bursts.
|
|
10
|
+
*
|
|
11
|
+
* The levels are read at runtime from the session model catalogue, so the
|
|
12
|
+
* slider always matches the efforts the current model actually declares.
|
|
13
|
+
*/
|
|
14
|
+
window.__ModuleLoader__.load({
|
|
15
|
+
id: "@lolannn/dsh-effort-slider",
|
|
16
|
+
factory: (require) => {
|
|
17
|
+
var module = { exports: {} };
|
|
18
|
+
var exports = module.exports;
|
|
19
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
20
|
+
|
|
21
|
+
const react = require("react");
|
|
22
|
+
const reactDom = require("react-dom");
|
|
23
|
+
const h = react.createElement;
|
|
24
|
+
|
|
25
|
+
const NS = "effort-slider";
|
|
26
|
+
const CSS_TAG = "dsh-effort-slider/client";
|
|
27
|
+
const EDGE = 31;
|
|
28
|
+
const SELECT_TIMEOUT_MS = 15000;
|
|
29
|
+
const COMMIT_DEBOUNCE_MS = 260;
|
|
30
|
+
|
|
31
|
+
/* ------------------------------------------------------------------ *
|
|
32
|
+
* dictionaries
|
|
33
|
+
* ------------------------------------------------------------------ */
|
|
34
|
+
|
|
35
|
+
const zh = {
|
|
36
|
+
pill: "思考强度",
|
|
37
|
+
pillHint: "模型与思考强度",
|
|
38
|
+
panelTitle: "思考强度",
|
|
39
|
+
modelLabel: "模型",
|
|
40
|
+
effortLabel: "思考强度",
|
|
41
|
+
noModels: "没有可用的模型",
|
|
42
|
+
triggerHint: "选择模型与思考强度",
|
|
43
|
+
hint: "拖动滑块,或用 ← / → / Home / End 切换档位",
|
|
44
|
+
reset: "回到默认档位",
|
|
45
|
+
close: "关闭",
|
|
46
|
+
switching: "正在切换…",
|
|
47
|
+
failed: "切换失败:{message}",
|
|
48
|
+
noSession: "当前没有活动会话",
|
|
49
|
+
noReasoning: "当前模型没有可调的思考强度档位",
|
|
50
|
+
clamped: "该模型没有「{from}」档,已自动切到最接近的「{to}」",
|
|
51
|
+
noEffortModel: "「{model}」没有思考强度档位,已按模型默认运行",
|
|
52
|
+
noLevelsTag: "无档位",
|
|
53
|
+
haloLabel: "外圈光环",
|
|
54
|
+
haloTitle: "在输入框外面加一圈随档位变化的光环;关闭后只在档位按钮周围发光",
|
|
55
|
+
boom: "宇宙毁灭",
|
|
56
|
+
afterBoom: "星际探索"
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const en = {
|
|
60
|
+
pill: "Effort",
|
|
61
|
+
pillHint: "Model and reasoning effort",
|
|
62
|
+
panelTitle: "Reasoning effort",
|
|
63
|
+
modelLabel: "Model",
|
|
64
|
+
effortLabel: "Reasoning effort",
|
|
65
|
+
noModels: "No model is available",
|
|
66
|
+
triggerHint: "Choose model and reasoning effort",
|
|
67
|
+
hint: "Drag the rail, or press ← / → / Home / End",
|
|
68
|
+
reset: "Back to the default level",
|
|
69
|
+
close: "Close",
|
|
70
|
+
switching: "Switching…",
|
|
71
|
+
failed: "Switch failed: {message}",
|
|
72
|
+
noSession: "No active session",
|
|
73
|
+
noReasoning: "This model exposes no reasoning-effort levels",
|
|
74
|
+
clamped: "This model has no \"{from}\" level — switched to the nearest one, \"{to}\"",
|
|
75
|
+
noEffortModel: "\"{model}\" exposes no effort levels — running at its default",
|
|
76
|
+
noLevelsTag: "no levels",
|
|
77
|
+
haloLabel: "Composer halo",
|
|
78
|
+
haloTitle: "Ring the input box with a glow that follows the effort level; off keeps the glow on the pill only",
|
|
79
|
+
boom: "SINGULARITY",
|
|
80
|
+
afterBoom: "DEEP SPACE"
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/* ------------------------------------------------------------------ *
|
|
84
|
+
* stylesheet
|
|
85
|
+
* ------------------------------------------------------------------ */
|
|
86
|
+
|
|
87
|
+
const CSS = [
|
|
88
|
+
".es-anchor{display:inline-flex;align-items:center;min-width:0;max-width:100%}",
|
|
89
|
+
".es-trigger{--es-hue:214;--es-e:0;position:relative;display:inline-flex;align-items:center;gap:6px;height:28px;max-width:min(340px,44vw);padding:0 9px;margin:0;border-radius:999px;border:1px solid transparent;background:transparent;color:#c3cbdb;font:inherit;font-size:12.5px;font-weight:500;line-height:1;cursor:pointer;white-space:nowrap;transition:background .2s ease,border-color .25s ease,color .2s ease,box-shadow .35s ease}",
|
|
90
|
+
".es-trigger:hover{background:rgba(255,255,255,.055);border-color:rgba(255,255,255,.1);color:#eef2fb}",
|
|
91
|
+
'.es-trigger[data-open="1"]{background:rgba(255,255,255,.09);border-color:hsl(var(--es-hue) 100% 76% / .34);color:#fff}',
|
|
92
|
+
'.es-trigger[data-locked="1"]{opacity:.5;cursor:default}',
|
|
93
|
+
".es-halo{position:fixed;z-index:2147482600;pointer-events:none;opacity:0;border-radius:20px;border:1px solid hsl(var(--es-hue) 100% 74% / var(--es-a0,.12));box-shadow:0 0 0 1px hsl(var(--es-hue) 100% 74% / var(--es-a1,.05)),0 0 var(--es-b1,10px) hsl(var(--es-hue) 100% 64% / var(--es-a2,.1)),0 0 var(--es-b2,34px) hsl(var(--es-hue) 100% 58% / var(--es-a3,.05));transition:opacity .5s ease,box-shadow .45s ease,border-color .45s ease;will-change:opacity,box-shadow}",
|
|
94
|
+
'.es-halo[data-on="1"]{opacity:1}',
|
|
95
|
+
'.es-halo[data-hot="1"]{animation:es-halo-breathe 3.6s ease-in-out infinite}',
|
|
96
|
+
"@keyframes es-halo-breathe{0%,100%{opacity:.62}50%{opacity:1}}",
|
|
97
|
+
".es-trigger-bolt{display:var(--dsh-composer-model-icon-display,none);width:14px;height:14px;flex:none;align-items:center;justify-content:center;color:hsl(var(--es-hue) 100% 78%)}",
|
|
98
|
+
".es-trigger-text{display:var(--dsh-composer-model-text-display,inline);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px}",
|
|
99
|
+
".es-trigger-effort{display:var(--dsh-composer-model-text-display,inline);font-weight:600;color:hsl(var(--es-hue) 100% 84%);text-shadow:0 0 10px hsl(var(--es-hue) 100% 70% / .45)}",
|
|
100
|
+
".es-trigger-sep{display:var(--dsh-composer-model-text-display,inline);opacity:.38;margin:0 1px}",
|
|
101
|
+
".es-trigger-chev{display:inline-flex;width:10px;height:10px;flex:none;opacity:.55;margin-left:1px}",
|
|
102
|
+
|
|
103
|
+
".es-menu{--es-hue:214;--es-t:0;--es-e:0;position:fixed;z-index:2147483000;display:flex;flex-direction:column;width:min(400px,calc(100vw - 24px));max-height:calc(100vh - 24px);border-radius:20px;background:rgba(11,14,21,.93);border:1px solid rgba(255,255,255,.1);backdrop-filter:blur(24px) saturate(150%);-webkit-backdrop-filter:blur(24px) saturate(150%);box-shadow:0 26px 70px rgba(0,0,0,.62),inset 0 1px 0 rgba(255,255,255,.06);overflow:hidden;color:#e8ecf7;isolation:isolate;animation:es-pop-menu .2s cubic-bezier(.2,1.1,.3,1) both}",
|
|
104
|
+
".es-menu.es-quake{animation:es-quake-menu .52s cubic-bezier(.36,.07,.19,.97) both}",
|
|
105
|
+
".es-menu-bg{position:absolute;inset:0;width:100%;height:100%;display:block;pointer-events:none;opacity:.9}",
|
|
106
|
+
".es-menu-vig{position:absolute;inset:0;pointer-events:none;background:radial-gradient(120% 100% at 50% 40%,transparent 42%,rgba(0,0,0,.55) 100%)}",
|
|
107
|
+
".es-menu-body{position:relative;display:flex;flex-direction:column;flex:1 1 auto;min-height:0;overflow:hidden;padding:9px 11px 11px}",
|
|
108
|
+
".es-menu-row{display:flex;align-items:center;gap:8px;width:100%;padding:8px 10px;border:0;border-radius:12px;background:transparent;color:#e8ecf7;font:inherit;font-size:13px;cursor:pointer;text-align:left;transition:background .18s ease}",
|
|
109
|
+
".es-menu-row:hover{background:rgba(255,255,255,.06)}",
|
|
110
|
+
".es-menu-row-label{flex:none;color:#8b93a6;font-size:12px}",
|
|
111
|
+
".es-menu-row-value{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600;color:#dfe6f5}",
|
|
112
|
+
".es-menu-chev{display:inline-flex;width:11px;height:11px;flex:none;opacity:.55;transition:transform .22s ease}",
|
|
113
|
+
'.es-menu-row[data-open="1"] .es-menu-chev{transform:rotate(180deg)}',
|
|
114
|
+
".es-models{display:flex;flex-direction:column;gap:1px;flex:0 1 auto;min-height:0;max-height:216px;overflow:auto;margin:2px 0 2px;padding:4px 5px 5px;border-radius:12px;background:rgba(6,8,13,.66);box-shadow:inset 0 1px 0 rgba(255,255,255,.05);scrollbar-width:thin;overscroll-behavior:contain}",
|
|
115
|
+
".es-models-group{padding:7px 6px 3px;font-size:10.5px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:#8d96aa}",
|
|
116
|
+
".es-model-item{display:flex;align-items:center;gap:8px;padding:7px 10px;border:0;border-radius:9px;background:transparent;color:#ccd5e6;font:inherit;font-size:12.5px;cursor:pointer;text-align:left;transition:background .16s ease,color .16s ease}",
|
|
117
|
+
".es-model-item:hover{background:rgba(255,255,255,.07);color:#fff}",
|
|
118
|
+
'.es-model-item[data-on="1"]{color:#fff;background:hsl(var(--es-hue) 100% 62% / .2);box-shadow:inset 2px 0 0 hsl(var(--es-hue) 100% 72%)}',
|
|
119
|
+
".es-model-item-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
120
|
+
".es-model-item-tag{flex:none;font-size:10px;color:#7f8798}",
|
|
121
|
+
".es-menu-sep{height:1px;margin:6px 2px 0;background:linear-gradient(90deg,transparent,rgba(255,255,255,.14),transparent)}",
|
|
122
|
+
".es-effort-head{display:flex;align-items:center;gap:7px;padding:9px 10px 7px;font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:#8b93a6}",
|
|
123
|
+
".es-effort-head .es-head-glyph{display:inline-flex;width:13px;height:13px;flex:none}",
|
|
124
|
+
".es-effort-head .es-tag{margin-left:auto;text-transform:none}",
|
|
125
|
+
".es-tag{font-size:11px;font-weight:700;letter-spacing:.06em;padding:2px 7px;border-radius:999px;border:1px solid hsl(var(--es-hue) 100% 72% / .38);color:hsl(var(--es-hue) 100% 84%);background:hsl(var(--es-hue) 100% 62% / .12);white-space:nowrap}",
|
|
126
|
+
".es-menu .es-track{height:52px;margin:0 2px}",
|
|
127
|
+
".es-menu .es-scale{margin:7px 2px 0}",
|
|
128
|
+
".es-menu .es-foot{padding:8px 2px 0}",
|
|
129
|
+
".es-menu .es-big{top:44%;font-size:clamp(19px,6vw,30px);letter-spacing:.12em}",
|
|
130
|
+
".es-track{position:relative;height:62px;border-radius:999px;touch-action:none;cursor:pointer;user-select:none;-webkit-user-select:none;background:radial-gradient(120% 180% at 50% 50%,rgba(255,255,255,.045),rgba(255,255,255,.012) 60%,rgba(0,0,0,.25));border:1px solid rgba(255,255,255,.07);box-shadow:inset 0 2px 14px rgba(0,0,0,.7),0 0 0 1px rgba(0,0,0,.35)}",
|
|
131
|
+
".es-rail{position:absolute;inset:0;width:100%;height:100%;border-radius:999px;display:block;pointer-events:none}",
|
|
132
|
+
".es-thumb{position:absolute;top:50%;left:calc(31px + (100% - 62px) * var(--es-t,0));width:40px;height:40px;border-radius:50%;transform:translate(-50%,-50%);background:radial-gradient(circle at 38% 34%,#fff 0%,#fff 42%,#dbe6ff 62%,#9fb6ff 100%);box-shadow:0 0 18px rgba(255,255,255,.95),0 0 46px hsl(var(--es-hue) 100% 72% / .85),0 0 90px hsl(var(--es-hue) 100% 62% / .55),inset 0 -3px 10px rgba(120,150,255,.5);pointer-events:none;transition:box-shadow .35s ease}",
|
|
133
|
+
".es-thumb::after{content:'';position:absolute;inset:-6px;border-radius:50%;background:radial-gradient(circle,hsl(var(--es-hue) 100% 75% / .35),transparent 68%);filter:blur(3px)}",
|
|
134
|
+
".es-ticks{position:absolute;inset:0;pointer-events:none}",
|
|
135
|
+
".es-tick{position:absolute;top:50%;width:3px;height:3px;border-radius:50%;transform:translate(-50%,-50%);background:rgba(255,255,255,.22);left:calc(31px + (100% - 62px) * var(--i,0))}",
|
|
136
|
+
'.es-tick[data-on="1"]{background:#fff;box-shadow:0 0 8px #fff,0 0 18px hsl(var(--es-hue) 100% 70% / .9)}',
|
|
137
|
+
".es-scale{position:relative;height:15px;margin:9px 0 0;padding:0;font-size:11px;color:#6d7588}",
|
|
138
|
+
".es-scale-item{position:absolute;top:0;transform:translateX(-50%);padding:0 5px;border:0;background:none;font:inherit;color:inherit;cursor:pointer;white-space:nowrap;transition:color .2s ease,text-shadow .3s ease}",
|
|
139
|
+
".es-scale-item:hover{color:#cfd8ea}",
|
|
140
|
+
'.es-scale-item[data-on="1"]{color:#fff;text-shadow:0 0 12px hsl(var(--es-hue) 100% 72% / .9)}',
|
|
141
|
+
".es-foot{display:flex;align-items:center;gap:8px;padding:10px 2px 0;font-size:11.5px;color:#7f8798;min-height:18px;line-height:1.4}",
|
|
142
|
+
'.es-foot[data-err="1"]{color:#ff9a9a}',
|
|
143
|
+
'.es-foot[data-note="1"]{color:hsl(var(--es-hue) 100% 84%)}',
|
|
144
|
+
".es-opt{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 2px 0;font-size:11.5px;color:#8b93a6}",
|
|
145
|
+
".es-switch{flex:none;width:34px;height:19px;padding:0;border-radius:999px;border:1px solid rgba(255,255,255,.14);background:rgba(255,255,255,.06);position:relative;cursor:pointer;transition:background .2s ease,border-color .2s ease}",
|
|
146
|
+
'.es-switch[data-on="1"]{background:hsl(var(--es-hue) 100% 62% / .38);border-color:hsl(var(--es-hue) 100% 70% / .55)}',
|
|
147
|
+
".es-switch-knob{position:absolute;top:1.5px;left:1.5px;width:14px;height:14px;border-radius:50%;background:#cfd6e6;transition:transform .18s ease,background .18s ease}",
|
|
148
|
+
'.es-switch[data-on="1"] .es-switch-knob{transform:translateX(15px);background:#fff}',
|
|
149
|
+
'.es-trigger[data-halo="0"][data-open="1"]{box-shadow:0 0 14px hsl(var(--es-hue) 100% 66% / .28)}',
|
|
150
|
+
".es-fx{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;mix-blend-mode:screen;display:block}",
|
|
151
|
+
".es-flash{position:absolute;inset:0;pointer-events:none;opacity:0;background:radial-gradient(60% 60% at 50% 50%,#fff,rgba(190,215,255,.6) 45%,transparent 72%)}",
|
|
152
|
+
".es-flash.go{animation:es-flash 1.1s ease-out both}",
|
|
153
|
+
".es-big{position:absolute;left:50%;top:38%;transform:translate(-50%,-50%);pointer-events:none;opacity:0;font-size:clamp(34px,11vw,86px);font-weight:900;letter-spacing:.16em;color:#fff;white-space:nowrap;text-shadow:0 0 22px rgba(255,255,255,.85),0 0 70px hsl(var(--es-hue) 100% 65% / .9),0 0 140px hsl(var(--es-hue) 100% 60% / .6)}",
|
|
154
|
+
".es-big.go{animation:es-big 3.2s cubic-bezier(.2,.7,.3,1) both}",
|
|
155
|
+
"@keyframes es-pop-menu{0%{opacity:0;transform:translateY(7px) scale(.97)}100%{opacity:1;transform:translateY(0) scale(1)}}",
|
|
156
|
+
"@keyframes es-flash{0%{opacity:0}6%{opacity:.95}22%{opacity:.55}100%{opacity:0}}",
|
|
157
|
+
"@keyframes es-big{0%{opacity:0;transform:translate(-50%,-50%) scale(.7);filter:blur(10px)}18%{opacity:1;transform:translate(-50%,-50%) scale(1.04);filter:blur(0)}62%{opacity:1;transform:translate(-50%,-50%) scale(1)}100%{opacity:0;transform:translate(-50%,-50%) scale(1.1);filter:blur(6px)}}",
|
|
158
|
+
"@keyframes es-quake-menu{0%{transform:translate(0,0) rotate(0)}12%{transform:translate(-6px,3px) rotate(-.4deg)}24%{transform:translate(5px,-4px) rotate(.36deg)}38%{transform:translate(-4px,-2px) rotate(-.24deg)}52%{transform:translate(3px,3px) rotate(.24deg)}68%{transform:translate(-2px,1px) rotate(-.14deg)}84%{transform:translate(1px,-1px) rotate(.08deg)}100%{transform:translate(0,0) rotate(0)}}",
|
|
159
|
+
"@media (prefers-reduced-motion:reduce){.es-menu{animation:none!important}.es-menu.es-quake{animation:none!important}.es-flash.go,.es-big.go{animation-duration:.4s}.es-thumb{transition:none}}",
|
|
160
|
+
"@media (max-height:660px){.es-menu .es-track{height:46px}.es-models{max-height:150px}}"
|
|
161
|
+
].join("\n");
|
|
162
|
+
|
|
163
|
+
function injectCss() {
|
|
164
|
+
if (typeof document === "undefined") return;
|
|
165
|
+
const existing = document.querySelector("style[data-plugin-css=" + JSON.stringify(CSS_TAG) + "]");
|
|
166
|
+
if (existing !== null) return;
|
|
167
|
+
const tag = document.createElement("style");
|
|
168
|
+
tag.dataset.plugin = "@lolannn/dsh-effort-slider";
|
|
169
|
+
tag.dataset.pluginCss = CSS_TAG;
|
|
170
|
+
tag.textContent = CSS;
|
|
171
|
+
document.head.appendChild(tag);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* ------------------------------------------------------------------ *
|
|
175
|
+
* small helpers
|
|
176
|
+
* ------------------------------------------------------------------ */
|
|
177
|
+
|
|
178
|
+
function readService(read) {
|
|
179
|
+
try {
|
|
180
|
+
return read();
|
|
181
|
+
} catch (err) {
|
|
182
|
+
return void 0;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/*
|
|
187
|
+
* Never let a label rendered inside a <button> look like a filesystem path.
|
|
188
|
+
*
|
|
189
|
+
* The pocket client installs a document-level click guard that inspects
|
|
190
|
+
* event.target.closest("button, a").textContent; when it matches
|
|
191
|
+
* /[\w.\-]+\/[\w.\-]+\.\w{1,12}/ (or a leading "/", "~/", "C:\") it calls
|
|
192
|
+
* preventDefault() + stopImmediatePropagation() and shows a "cannot open
|
|
193
|
+
* computer files on your phone" toast. A model identifier such as
|
|
194
|
+
* "workbuddy-ai/deepseek-v4.1-flash" matches that pattern, so on a phone the
|
|
195
|
+
* trigger silently became unclickable. The slash is display-only: the real
|
|
196
|
+
* identifier stays in aria-label/title and in the request we send.
|
|
197
|
+
*/
|
|
198
|
+
function safeLabel(text) {
|
|
199
|
+
if (text === void 0 || text === null) return "";
|
|
200
|
+
return String(text).replace(/[\\/]+/g, " · ");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function clamp01(v) {
|
|
204
|
+
return v < 0 ? 0 : v > 1 ? 1 : v;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function clamp(v, lo, hi) {
|
|
208
|
+
return v < lo ? lo : v > hi ? hi : v;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function easeOutBack(p) {
|
|
212
|
+
const c1 = 1.15;
|
|
213
|
+
const c3 = c1 + 1;
|
|
214
|
+
const q = p - 1;
|
|
215
|
+
return 1 + c3 * q * q * q + c1 * q * q;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function messageOf(err) {
|
|
219
|
+
if (!err) return "";
|
|
220
|
+
if (typeof err === "string") return err;
|
|
221
|
+
if (typeof err.message === "string") return err.message;
|
|
222
|
+
try {
|
|
223
|
+
return JSON.stringify(err);
|
|
224
|
+
} catch (e) {
|
|
225
|
+
return String(err);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function withTimeout(work, ms) {
|
|
230
|
+
return new Promise((resolve, reject) => {
|
|
231
|
+
const timer = setTimeout(() => reject(new Error("timed out after " + ms + "ms")), ms);
|
|
232
|
+
Promise.resolve(work).then(
|
|
233
|
+
(value) => {
|
|
234
|
+
clearTimeout(timer);
|
|
235
|
+
resolve(value);
|
|
236
|
+
},
|
|
237
|
+
(err) => {
|
|
238
|
+
clearTimeout(timer);
|
|
239
|
+
reject(err);
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function mainViewSessionId(byId) {
|
|
246
|
+
if (byId === void 0 || byId === null) return void 0;
|
|
247
|
+
const rows = Object.values(byId);
|
|
248
|
+
for (const row of rows) {
|
|
249
|
+
if (row !== void 0 && (row.retainedBy && row.retainedBy.mainView ? row.retainedBy.mainView : 0) > 0) return row.id;
|
|
250
|
+
}
|
|
251
|
+
return void 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function createStore(initial) {
|
|
255
|
+
let state = initial;
|
|
256
|
+
const listeners = new Set();
|
|
257
|
+
return {
|
|
258
|
+
getSnapshot() {
|
|
259
|
+
return state;
|
|
260
|
+
},
|
|
261
|
+
set(patch) {
|
|
262
|
+
const next = Object.assign({}, state, typeof patch === "function" ? patch(state) : patch);
|
|
263
|
+
state = next;
|
|
264
|
+
listeners.forEach((fn) => {
|
|
265
|
+
try {
|
|
266
|
+
fn();
|
|
267
|
+
} catch (err) {
|
|
268
|
+
/* a broken subscriber must not break the store */
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
},
|
|
272
|
+
subscribe(fn) {
|
|
273
|
+
listeners.add(fn);
|
|
274
|
+
return () => {
|
|
275
|
+
listeners.delete(fn);
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* ------------------------------------------------------------------ *
|
|
282
|
+
* level -> visual spec
|
|
283
|
+
* ------------------------------------------------------------------ */
|
|
284
|
+
|
|
285
|
+
function topIndexOf(count) {
|
|
286
|
+
return Math.max(0, count - 1);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function levelT(index, count) {
|
|
290
|
+
const top = topIndexOf(count);
|
|
291
|
+
return top > 0 ? clamp(index, 0, top) / top : 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function hueAtT(t) {
|
|
295
|
+
return t >= 0.92 ? 188 : 200 + 106 * t;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function kindAtT(t) {
|
|
299
|
+
if (t <= 0.001) return "none";
|
|
300
|
+
if (t < 0.3) return "dot";
|
|
301
|
+
if (t < 0.5) return "trail";
|
|
302
|
+
if (t < 0.75) return "star";
|
|
303
|
+
if (t < 0.92) return "starHeavy";
|
|
304
|
+
return "galaxy";
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function countAtT(kind, t) {
|
|
308
|
+
if (kind === "dot") return 10 + Math.round(8 * t);
|
|
309
|
+
if (kind === "trail") return 18 + Math.round(10 * t);
|
|
310
|
+
if (kind === "star") return 28;
|
|
311
|
+
if (kind === "starHeavy") return 40;
|
|
312
|
+
if (kind === "galaxy") return 38;
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function specAt(t) {
|
|
317
|
+
const kind = kindAtT(t);
|
|
318
|
+
return {
|
|
319
|
+
t,
|
|
320
|
+
hue: hueAtT(t),
|
|
321
|
+
kind,
|
|
322
|
+
ribbon: kind !== "none" && kind !== "galaxy" && t >= 0.55,
|
|
323
|
+
n: countAtT(kind, t)
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* ------------------------------------------------------------------ *
|
|
328
|
+
* deterministic randomness for rail particles
|
|
329
|
+
* ------------------------------------------------------------------ */
|
|
330
|
+
|
|
331
|
+
function hashStr(s) {
|
|
332
|
+
let h1 = 2166136261;
|
|
333
|
+
for (let i = 0; i < s.length; i++) {
|
|
334
|
+
h1 ^= s.charCodeAt(i);
|
|
335
|
+
h1 = Math.imul(h1, 16777619);
|
|
336
|
+
}
|
|
337
|
+
return h1 >>> 0;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function mulberry32(a) {
|
|
341
|
+
let seed = a >>> 0;
|
|
342
|
+
return function () {
|
|
343
|
+
seed = (seed + 0x6d2b79f5) >>> 0;
|
|
344
|
+
let t = seed;
|
|
345
|
+
t = Math.imul(t ^ (t >>> 15), 1 | t);
|
|
346
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
347
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* ------------------------------------------------------------------ *
|
|
352
|
+
* canvas primitives
|
|
353
|
+
* ------------------------------------------------------------------ */
|
|
354
|
+
|
|
355
|
+
function roundRect(c, x, y, w, hh, r) {
|
|
356
|
+
const rr = Math.min(r, w / 2, hh / 2);
|
|
357
|
+
c.beginPath();
|
|
358
|
+
c.moveTo(x + rr, y);
|
|
359
|
+
c.arcTo(x + w, y, x + w, y + hh, rr);
|
|
360
|
+
c.arcTo(x + w, y + hh, x, y + hh, rr);
|
|
361
|
+
c.arcTo(x, y + hh, x, y, rr);
|
|
362
|
+
c.arcTo(x, y, x + w, y, rr);
|
|
363
|
+
c.closePath();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function starPath(c, x, y, r) {
|
|
367
|
+
const k = r * 0.07;
|
|
368
|
+
c.beginPath();
|
|
369
|
+
c.moveTo(x, y - r);
|
|
370
|
+
c.quadraticCurveTo(x + k, y - k, x + r, y);
|
|
371
|
+
c.quadraticCurveTo(x + k, y + k, x, y + r);
|
|
372
|
+
c.quadraticCurveTo(x - k, y + k, x - r, y);
|
|
373
|
+
c.quadraticCurveTo(x - k, y - k, x, y - r);
|
|
374
|
+
c.closePath();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function star4(c, x, y, r, alpha, hue, irid) {
|
|
378
|
+
if (r <= 0.06 || alpha <= 0.004) return;
|
|
379
|
+
c.fillStyle = "hsla(" + hue + ",100%,92%," + alpha + ")";
|
|
380
|
+
starPath(c, x, y, r);
|
|
381
|
+
c.fill();
|
|
382
|
+
if (!irid) return;
|
|
383
|
+
c.fillStyle = "hsla(" + (hue + 155) + ",100%,70%," + alpha * 0.55 + ")";
|
|
384
|
+
starPath(c, x, y, r * 0.78);
|
|
385
|
+
c.fill();
|
|
386
|
+
c.fillStyle = "hsla(" + (hue + 285) + ",100%,74%," + alpha * 0.42 + ")";
|
|
387
|
+
starPath(c, x, y, r * 0.5);
|
|
388
|
+
c.fill();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function starBeam(c, cx, cy, rx, ry) {
|
|
392
|
+
const bow = 0.07;
|
|
393
|
+
c.beginPath();
|
|
394
|
+
c.moveTo(cx - rx, cy);
|
|
395
|
+
c.quadraticCurveTo(cx, cy - ry * bow, cx + rx, cy);
|
|
396
|
+
c.quadraticCurveTo(cx, cy + ry * bow, cx - rx, cy);
|
|
397
|
+
c.closePath();
|
|
398
|
+
c.fill();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function nebulaSprite(h1, h2) {
|
|
402
|
+
const size = 256;
|
|
403
|
+
const cv = document.createElement("canvas");
|
|
404
|
+
cv.width = size;
|
|
405
|
+
cv.height = size;
|
|
406
|
+
const c = cv.getContext("2d");
|
|
407
|
+
if (!c) return cv;
|
|
408
|
+
const rnd = mulberry32(hashStr("neb:" + h1 + ":" + h2));
|
|
409
|
+
for (let i = 0; i < 26; i++) {
|
|
410
|
+
const x = size * (0.15 + rnd() * 0.7);
|
|
411
|
+
const y = size * (0.15 + rnd() * 0.7);
|
|
412
|
+
const r = size * (0.08 + rnd() * 0.26);
|
|
413
|
+
const hue = i % 2 === 0 ? h1 : h2;
|
|
414
|
+
const g = c.createRadialGradient(x, y, 0, x, y, r);
|
|
415
|
+
g.addColorStop(0, "hsla(" + hue + ",95%,62%,.20)");
|
|
416
|
+
g.addColorStop(0.45, "hsla(" + (hue + 26) + ",95%,52%,.07)");
|
|
417
|
+
g.addColorStop(1, "hsla(" + hue + ",95%,50%,0)");
|
|
418
|
+
c.fillStyle = g;
|
|
419
|
+
c.beginPath();
|
|
420
|
+
c.arc(x, y, r, 0, Math.PI * 2);
|
|
421
|
+
c.fill();
|
|
422
|
+
}
|
|
423
|
+
return cv;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/* ------------------------------------------------------------------ *
|
|
427
|
+
* engine
|
|
428
|
+
* ------------------------------------------------------------------ */
|
|
429
|
+
|
|
430
|
+
function createEngine() {
|
|
431
|
+
const S = {
|
|
432
|
+
mounted: false,
|
|
433
|
+
raf: 0,
|
|
434
|
+
last: 0,
|
|
435
|
+
time: 0,
|
|
436
|
+
frameNo: 0,
|
|
437
|
+
t: 0,
|
|
438
|
+
e: 0,
|
|
439
|
+
idx: 0,
|
|
440
|
+
count: 1,
|
|
441
|
+
anim: null,
|
|
442
|
+
dragging: false,
|
|
443
|
+
warp: 0,
|
|
444
|
+
topFired: false,
|
|
445
|
+
reduce: false,
|
|
446
|
+
els: null,
|
|
447
|
+
bgc: null,
|
|
448
|
+
rxc: null,
|
|
449
|
+
fxc: null,
|
|
450
|
+
W: 0,
|
|
451
|
+
H: 0,
|
|
452
|
+
dpr: 1,
|
|
453
|
+
rw: 0,
|
|
454
|
+
rh: 0,
|
|
455
|
+
rdpr: 1,
|
|
456
|
+
stars: [],
|
|
457
|
+
waves: [],
|
|
458
|
+
debris: [],
|
|
459
|
+
streaks: [],
|
|
460
|
+
astra: [],
|
|
461
|
+
neb: null,
|
|
462
|
+
parts: [],
|
|
463
|
+
partKey: "",
|
|
464
|
+
partKind: "",
|
|
465
|
+
partFade: 1,
|
|
466
|
+
prevParts: [],
|
|
467
|
+
prevKind: "",
|
|
468
|
+
prevFade: 0,
|
|
469
|
+
texts: { boom: "宇宙毁灭", after: "星际探索" },
|
|
470
|
+
onLevel: null,
|
|
471
|
+
onRelease: null,
|
|
472
|
+
bigTimer: 0,
|
|
473
|
+
warpTimer: 0
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
function topIndex() {
|
|
477
|
+
return topIndexOf(S.count);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/* ---------------- sizing ---------------- */
|
|
481
|
+
|
|
482
|
+
function fit() {
|
|
483
|
+
const els = S.els;
|
|
484
|
+
if (!els || !els.bg || !els.track) return;
|
|
485
|
+
S.dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
486
|
+
/* Both canvases live inside the popup, so they must match the popup's
|
|
487
|
+
own box — sizing them to the viewport would put the starfield centre
|
|
488
|
+
far outside the card (and draw a full screen of pixels per frame). */
|
|
489
|
+
let box = null;
|
|
490
|
+
if (els.stage && typeof els.stage.getBoundingClientRect === "function") {
|
|
491
|
+
const r = els.stage.getBoundingClientRect();
|
|
492
|
+
if (r.width > 0.5 && r.height > 0.5) box = r;
|
|
493
|
+
}
|
|
494
|
+
S.W = Math.max(1, box ? box.width : window.innerWidth);
|
|
495
|
+
S.H = Math.max(1, box ? box.height : window.innerHeight);
|
|
496
|
+
const prep = (cv) => {
|
|
497
|
+
if (!cv) return null;
|
|
498
|
+
cv.width = Math.max(1, Math.round(S.W * S.dpr));
|
|
499
|
+
cv.height = Math.max(1, Math.round(S.H * S.dpr));
|
|
500
|
+
cv.style.width = S.W + "px";
|
|
501
|
+
cv.style.height = S.H + "px";
|
|
502
|
+
const c = cv.getContext("2d");
|
|
503
|
+
if (c) c.setTransform(S.dpr, 0, 0, S.dpr, 0, 0);
|
|
504
|
+
return c;
|
|
505
|
+
};
|
|
506
|
+
S.bgc = prep(els.bg);
|
|
507
|
+
S.fxc = prep(els.fx);
|
|
508
|
+
const r = els.track.getBoundingClientRect();
|
|
509
|
+
S.rw = Math.max(1, r.width);
|
|
510
|
+
S.rh = Math.max(1, r.height);
|
|
511
|
+
S.rdpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
512
|
+
if (els.rail) {
|
|
513
|
+
els.rail.width = Math.max(1, Math.round(S.rw * S.rdpr));
|
|
514
|
+
els.rail.height = Math.max(1, Math.round(S.rh * S.rdpr));
|
|
515
|
+
const rc = els.rail.getContext("2d");
|
|
516
|
+
if (rc) rc.setTransform(S.rdpr, 0, 0, S.rdpr, 0, 0);
|
|
517
|
+
S.rxc = rc;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/* ---------------- starfield state ---------------- */
|
|
522
|
+
|
|
523
|
+
function respawnStar(s) {
|
|
524
|
+
s.x = Math.random() * 2 - 1;
|
|
525
|
+
s.y = Math.random() * 2 - 1;
|
|
526
|
+
s.z = 0.98;
|
|
527
|
+
s.born = S.time;
|
|
528
|
+
s.life = S.idx === topIndex() ? 6 + Math.random() * 5.5 : 4.2 + Math.random() * 4.2;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function initStars() {
|
|
532
|
+
S.stars = [];
|
|
533
|
+
for (let i = 0; i < 260; i++) {
|
|
534
|
+
const s = {
|
|
535
|
+
x: Math.random() * 2 - 1,
|
|
536
|
+
y: Math.random() * 2 - 1,
|
|
537
|
+
z: 0.08 + Math.random() * 0.92,
|
|
538
|
+
c: Math.random() < 0.22 ? 1 : 0,
|
|
539
|
+
born: 0,
|
|
540
|
+
life: 4.2 + Math.random() * 4.2
|
|
541
|
+
};
|
|
542
|
+
S.stars.push(s);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/* ---------------- rail particles ---------------- */
|
|
547
|
+
|
|
548
|
+
function buildParticles(n, kind) {
|
|
549
|
+
const key = kind + ":" + n;
|
|
550
|
+
if (key === S.partKey) return;
|
|
551
|
+
S.prevParts = S.parts;
|
|
552
|
+
S.prevKind = S.partKind;
|
|
553
|
+
S.prevFade = S.partFade;
|
|
554
|
+
S.partKey = key;
|
|
555
|
+
S.partKind = kind;
|
|
556
|
+
S.partFade = 0;
|
|
557
|
+
S.parts = [];
|
|
558
|
+
if (n <= 0) return;
|
|
559
|
+
const rnd = mulberry32(hashStr(key));
|
|
560
|
+
for (let i = 0; i < n; i++) {
|
|
561
|
+
S.parts.push({
|
|
562
|
+
p0: rnd(),
|
|
563
|
+
y: 0.1 + rnd() * 0.8,
|
|
564
|
+
sp: 0.016 + rnd() * 0.058,
|
|
565
|
+
size: 0.55 + rnd() * 1.5,
|
|
566
|
+
hueOff: rnd() * 44 - 22,
|
|
567
|
+
tw: rnd() * Math.PI * 2
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function ensureParticles() {
|
|
573
|
+
const spec = specAt(S.t);
|
|
574
|
+
buildParticles(spec.n, spec.kind);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function drawParticleSet(c, list, kind, mul, hue, e, pad, reach, ih) {
|
|
578
|
+
if (!list || !list.length || mul <= 0.002) return;
|
|
579
|
+
const starMode = kind === "star" || kind === "starHeavy" || kind === "galaxy";
|
|
580
|
+
const trailMode = starMode || kind === "trail";
|
|
581
|
+
const speedK = 0.55 + e * 1.7;
|
|
582
|
+
const base = kind === "dot" ? 0.9 : kind === "trail" ? 1.6 : 2.3;
|
|
583
|
+
const copies = starMode ? (kind === "star" ? 2 : 4) : 4;
|
|
584
|
+
const len = starMode ? (kind === "star" ? 15 : 10) : 10;
|
|
585
|
+
for (let i = 0; i < list.length; i++) {
|
|
586
|
+
const q = list[i];
|
|
587
|
+
let p = (q.p0 - S.time * q.sp * speedK) % 1;
|
|
588
|
+
if (p < 0) p += 1;
|
|
589
|
+
const x = pad + reach * p;
|
|
590
|
+
let y = pad + ih * q.y;
|
|
591
|
+
if (kind === "dot") y += Math.sin(q.tw + S.time * 1.3) * 1.2;
|
|
592
|
+
const flick = 0.62 + 0.38 * Math.sin(q.tw + S.time * 2.6);
|
|
593
|
+
const a = (0.2 + e * 0.62) * flick * mul;
|
|
594
|
+
if (a <= 0.004) continue;
|
|
595
|
+
const sz = base * (0.7 + q.size * 0.55);
|
|
596
|
+
if (trailMode) {
|
|
597
|
+
for (let k = 1; k <= copies; k++) {
|
|
598
|
+
const tx = x + len * k;
|
|
599
|
+
if (tx > pad + reach) break;
|
|
600
|
+
const ak = a * (1 - k / (copies + 1)) * 0.55;
|
|
601
|
+
if (starMode) star4(c, tx, y, sz * (1 - k * 0.08), ak * 0.75, hue + q.hueOff + 20, false);
|
|
602
|
+
else {
|
|
603
|
+
c.fillStyle = "hsla(" + (hue + q.hueOff) + ",100%,78%," + ak + ")";
|
|
604
|
+
c.beginPath();
|
|
605
|
+
c.arc(tx, y, Math.max(0.3, sz * 0.8), 0, Math.PI * 2);
|
|
606
|
+
c.fill();
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
if (starMode) star4(c, x, y, sz * (kind === "starHeavy" ? 1.4 : 1), a, hue + q.hueOff + 30, true);
|
|
611
|
+
else {
|
|
612
|
+
c.fillStyle = "hsla(" + (hue + q.hueOff) + ",100%,80%," + a + ")";
|
|
613
|
+
c.beginPath();
|
|
614
|
+
c.arc(x, y, Math.max(0.3, sz * (0.7 + e * 0.8)), 0, Math.PI * 2);
|
|
615
|
+
c.fill();
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/* ---------------- background ---------------- */
|
|
621
|
+
|
|
622
|
+
function drawBG(dt) {
|
|
623
|
+
const c = S.bgc;
|
|
624
|
+
if (!c) return;
|
|
625
|
+
const W = S.W;
|
|
626
|
+
const H = S.H;
|
|
627
|
+
const e = S.e;
|
|
628
|
+
const hue = hueAtT(S.t);
|
|
629
|
+
c.setTransform(S.dpr, 0, 0, S.dpr, 0, 0);
|
|
630
|
+
c.globalCompositeOperation = "source-over";
|
|
631
|
+
c.clearRect(0, 0, W, H);
|
|
632
|
+
const g = c.createRadialGradient(W * 0.5, H * 0.45, 0, W * 0.5, H * 0.45, Math.max(W, H) * 0.85);
|
|
633
|
+
g.addColorStop(0, "hsla(" + (222 + e * 60) + "," + (46 + e * 26) + "%," + (5 + e * 4) + "%,.94)");
|
|
634
|
+
g.addColorStop(1, "rgba(3,4,10,.97)");
|
|
635
|
+
c.fillStyle = g;
|
|
636
|
+
c.fillRect(0, 0, W, H);
|
|
637
|
+
|
|
638
|
+
c.globalCompositeOperation = "lighter";
|
|
639
|
+
|
|
640
|
+
if (S.neb) {
|
|
641
|
+
const sway = Math.sin(S.time * 0.09) * 40;
|
|
642
|
+
const layers = [
|
|
643
|
+
[S.neb.a, 0.12 + e * 0.3, 0.3, sway],
|
|
644
|
+
[S.neb.b, 0.1 + e * 0.26, 0.72, -sway * 0.7],
|
|
645
|
+
[S.neb.c, 0.08 + e * 0.22, 0.5, sway * 0.45]
|
|
646
|
+
];
|
|
647
|
+
for (let i = 0; i < layers.length; i++) {
|
|
648
|
+
const spr = layers[i][0];
|
|
649
|
+
if (!spr) continue;
|
|
650
|
+
const w = W * 1.5;
|
|
651
|
+
const hh = H * 1.5;
|
|
652
|
+
c.globalAlpha = Math.min(1, layers[i][1]);
|
|
653
|
+
c.drawImage(spr, W * layers[i][2] - w / 2 + layers[i][3], H * 0.5 - hh / 2 + layers[i][3] * 0.4, w, hh);
|
|
654
|
+
}
|
|
655
|
+
c.globalAlpha = 1;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const speed = 0.045 + e * 0.55 + S.warp * 2.4;
|
|
659
|
+
const fov = Math.min(W, H) * 0.62;
|
|
660
|
+
const cx = W * 0.5;
|
|
661
|
+
const cy = H * 0.46;
|
|
662
|
+
for (let i = 0; i < S.stars.length; i++) {
|
|
663
|
+
const s = S.stars[i];
|
|
664
|
+
s.z -= dt * speed * (0.35 + (1 - s.z) * 1.6);
|
|
665
|
+
const age = S.time - s.born;
|
|
666
|
+
if (s.z <= 0.012 || age > s.life) {
|
|
667
|
+
respawnStar(s);
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
const k = fov / s.z;
|
|
671
|
+
const near = 1 - s.z;
|
|
672
|
+
const x = cx + s.x * k;
|
|
673
|
+
const y = cy + s.y * k;
|
|
674
|
+
if (x < -80 || x > W + 80 || y < -80 || y > H + 80) {
|
|
675
|
+
respawnStar(s);
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
const lifeA = Math.min(1, age / 0.7) * Math.max(0, 1 - age / s.life);
|
|
679
|
+
const a = Math.min(1, near * 1.5) * (0.35 + e * 0.6) * lifeA;
|
|
680
|
+
if (a <= 0.004) continue;
|
|
681
|
+
const tint = s.c ? 268 : 196;
|
|
682
|
+
if (S.warp > 0.04 && near > 0.22) {
|
|
683
|
+
const pz = Math.max(0.02, s.z + dt * speed * 1.6);
|
|
684
|
+
const pk = fov / pz;
|
|
685
|
+
c.strokeStyle = "hsla(" + tint + ",100%,82%," + a * 0.85 + ")";
|
|
686
|
+
c.lineWidth = Math.min(3.2, 0.5 + near * 2.4);
|
|
687
|
+
c.beginPath();
|
|
688
|
+
c.moveTo(x, y);
|
|
689
|
+
c.lineTo(cx + s.x * pk, cy + s.y * pk);
|
|
690
|
+
c.stroke();
|
|
691
|
+
} else {
|
|
692
|
+
c.fillStyle = "hsla(" + tint + ",100%,88%," + a + ")";
|
|
693
|
+
c.beginPath();
|
|
694
|
+
c.arc(x, y, Math.min(2.4, 0.35 + near * 1.9), 0, Math.PI * 2);
|
|
695
|
+
c.fill();
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
for (let i = S.waves.length - 1; i >= 0; i--) {
|
|
700
|
+
const w = S.waves[i];
|
|
701
|
+
w.r += dt * (900 + 900 * w.a);
|
|
702
|
+
w.a *= Math.pow(0.18, dt);
|
|
703
|
+
if (w.a < 0.012) {
|
|
704
|
+
S.waves.splice(i, 1);
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
c.strokeStyle = "hsla(" + w.hue + ",100%,76%," + w.a + ")";
|
|
708
|
+
c.lineWidth = w.w * Math.max(0.2, w.a);
|
|
709
|
+
c.beginPath();
|
|
710
|
+
c.arc(cx, cy, w.r, 0, Math.PI * 2);
|
|
711
|
+
c.stroke();
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
for (let i = S.debris.length - 1; i >= 0; i--) {
|
|
715
|
+
const d = S.debris[i];
|
|
716
|
+
d.life -= dt * 0.42;
|
|
717
|
+
if (d.life <= 0) {
|
|
718
|
+
S.debris.splice(i, 1);
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
d.vy += 120 * dt;
|
|
722
|
+
d.vx *= Math.pow(0.55, dt);
|
|
723
|
+
d.x += d.vx * dt;
|
|
724
|
+
d.y += d.vy * dt;
|
|
725
|
+
const a = Math.min(1, d.life) * 0.9;
|
|
726
|
+
c.fillStyle = "hsla(" + d.hue + ",100%,82%," + a + ")";
|
|
727
|
+
c.beginPath();
|
|
728
|
+
c.arc(d.x, d.y, d.size * Math.max(0.3, d.life), 0, Math.PI * 2);
|
|
729
|
+
c.fill();
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
for (let i = S.streaks.length - 1; i >= 0; i--) {
|
|
733
|
+
const s = S.streaks[i];
|
|
734
|
+
s.life -= dt * 1.9;
|
|
735
|
+
if (s.life <= 0) {
|
|
736
|
+
S.streaks.splice(i, 1);
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
s.x += s.vx * dt;
|
|
740
|
+
s.y += s.vy * dt;
|
|
741
|
+
const a = Math.min(1, s.life) * 0.75;
|
|
742
|
+
c.strokeStyle = "hsla(" + s.hue + ",100%,86%," + a + ")";
|
|
743
|
+
c.lineWidth = 1.4;
|
|
744
|
+
c.beginPath();
|
|
745
|
+
c.moveTo(s.x, s.y);
|
|
746
|
+
c.lineTo(s.x - s.vx * 0.055, s.y - s.vy * 0.055);
|
|
747
|
+
c.stroke();
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
c.globalCompositeOperation = "source-over";
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/* ---------------- the rail ---------------- */
|
|
754
|
+
|
|
755
|
+
function drawRail() {
|
|
756
|
+
const c = S.rxc;
|
|
757
|
+
if (!c) return;
|
|
758
|
+
const w = S.rw;
|
|
759
|
+
const hh = S.rh;
|
|
760
|
+
c.setTransform(S.rdpr, 0, 0, S.rdpr, 0, 0);
|
|
761
|
+
c.clearRect(0, 0, w, hh);
|
|
762
|
+
const spec = specAt(S.t);
|
|
763
|
+
const e = S.e;
|
|
764
|
+
const hue = hueAtT(S.t);
|
|
765
|
+
const pad = 11;
|
|
766
|
+
const iw = Math.max(1, w - pad * 2);
|
|
767
|
+
const ih = Math.max(1, hh - pad * 2);
|
|
768
|
+
const tx = EDGE + (w - EDGE * 2) * S.t;
|
|
769
|
+
const reach = Math.max(1, tx - pad);
|
|
770
|
+
const cyy = hh * 0.5;
|
|
771
|
+
|
|
772
|
+
c.save();
|
|
773
|
+
roundRect(c, pad, pad, iw, ih, ih / 2);
|
|
774
|
+
c.clip();
|
|
775
|
+
|
|
776
|
+
const bgGrad = c.createLinearGradient(pad, 0, pad + iw, 0);
|
|
777
|
+
bgGrad.addColorStop(0, "hsla(" + (hue - 42) + ",72%," + (8 + e * 4) + "%,.96)");
|
|
778
|
+
bgGrad.addColorStop(0.5, "hsla(" + hue + ",72%," + (10 + e * 6) + "%,.96)");
|
|
779
|
+
bgGrad.addColorStop(1, "hsla(" + (hue + 46) + ",72%," + (9 + e * 5) + "%,.96)");
|
|
780
|
+
c.fillStyle = bgGrad;
|
|
781
|
+
c.fillRect(pad, pad, iw, ih);
|
|
782
|
+
|
|
783
|
+
const fillGrad = c.createLinearGradient(pad, 0, pad + Math.max(1, reach), 0);
|
|
784
|
+
fillGrad.addColorStop(0, "hsla(" + (hue - 34) + ",100%,62%,.55)");
|
|
785
|
+
fillGrad.addColorStop(0.45, "hsla(" + hue + ",100%,66%,.72)");
|
|
786
|
+
fillGrad.addColorStop(0.8, "hsla(" + (hue + 40) + ",100%,68%,.82)");
|
|
787
|
+
fillGrad.addColorStop(1, "hsla(" + (hue + 64) + ",100%,74%,.92)");
|
|
788
|
+
c.fillStyle = fillGrad;
|
|
789
|
+
c.fillRect(pad, pad, reach, ih);
|
|
790
|
+
|
|
791
|
+
c.globalCompositeOperation = "lighter";
|
|
792
|
+
|
|
793
|
+
if (spec.ribbon) {
|
|
794
|
+
const slow = S.time * (0.28 + e * 0.5);
|
|
795
|
+
for (let k = 0; k < 3; k++) {
|
|
796
|
+
const amp = (3 + k * 3.4) * (0.35 + e);
|
|
797
|
+
c.beginPath();
|
|
798
|
+
for (let i = 0; i <= 60; i++) {
|
|
799
|
+
const p = i / 60;
|
|
800
|
+
const x = pad + iw * p;
|
|
801
|
+
const y =
|
|
802
|
+
cyy +
|
|
803
|
+
Math.sin(p * (5.2 + k * 1.7) + slow * (1 + k * 0.35) + k) * amp +
|
|
804
|
+
Math.sin(p * 12 + slow * 1.7) * amp * 0.28;
|
|
805
|
+
if (i === 0) c.moveTo(x, y);
|
|
806
|
+
else c.lineTo(x, y);
|
|
807
|
+
}
|
|
808
|
+
c.strokeStyle = "hsla(" + (hue + 30 + k * 18) + ",100%,78%," + (0.1 + e * 0.26) + ")";
|
|
809
|
+
c.lineWidth = 1.1 + e * 1.5;
|
|
810
|
+
c.stroke();
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
c.save();
|
|
815
|
+
c.beginPath();
|
|
816
|
+
c.rect(pad, pad, reach, ih);
|
|
817
|
+
c.clip();
|
|
818
|
+
if (S.prevParts.length && S.prevFade > 0.01) drawParticleSet(c, S.prevParts, S.prevKind, S.prevFade, hue, e, pad, reach, ih);
|
|
819
|
+
drawParticleSet(c, S.parts, S.partKind, S.partFade, hue, e, pad, reach, ih);
|
|
820
|
+
|
|
821
|
+
if (spec.kind === "starHeavy") {
|
|
822
|
+
const sc = 0.6 + e * 0.7;
|
|
823
|
+
const br = 0.5 + 0.5 * Math.sin(S.time * 2.4);
|
|
824
|
+
const cxx = pad + iw * 0.5;
|
|
825
|
+
const halo = c.createRadialGradient(cxx, cyy, 0, cxx, cyy, 26 * sc);
|
|
826
|
+
halo.addColorStop(0, "hsla(" + hue + ",100%,90%," + (0.22 + br * 0.3) + ")");
|
|
827
|
+
halo.addColorStop(1, "hsla(" + hue + ",100%,70%,0)");
|
|
828
|
+
c.fillStyle = halo;
|
|
829
|
+
c.beginPath();
|
|
830
|
+
c.arc(cxx, cyy, 26 * sc, 0, Math.PI * 2);
|
|
831
|
+
c.fill();
|
|
832
|
+
star4(c, cxx, cyy, (13 + br * 3) * sc, 0.75 + br * 0.2, hue, true);
|
|
833
|
+
star4(c, cxx - iw * 0.17, cyy, 7 * sc, 0.42, hue + 40, false);
|
|
834
|
+
star4(c, cxx + iw * 0.17, cyy, 7 * sc, 0.42, hue - 30, false);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (spec.kind === "galaxy") {
|
|
838
|
+
const ga = clamp01((e - 0.9) / 0.1);
|
|
839
|
+
const cxx = pad + iw * 0.5;
|
|
840
|
+
const core = c.createRadialGradient(cxx, cyy, 0, cxx, cyy, iw * 0.24);
|
|
841
|
+
core.addColorStop(0, "rgba(255,255,255,.95)");
|
|
842
|
+
core.addColorStop(0.35, "hsla(196,100%,82%,.75)");
|
|
843
|
+
core.addColorStop(0.72, "hsla(258,100%,62%,.34)");
|
|
844
|
+
core.addColorStop(1, "hsla(258,100%,60%,0)");
|
|
845
|
+
c.fillStyle = core;
|
|
846
|
+
c.beginPath();
|
|
847
|
+
c.arc(cxx, cyy, iw * 0.24, 0, Math.PI * 2);
|
|
848
|
+
c.fill();
|
|
849
|
+
for (let a = 0; a < 3; a++) {
|
|
850
|
+
for (let i = 0; i < 130; i++) {
|
|
851
|
+
const f = i / 130;
|
|
852
|
+
const ang = a * ((Math.PI * 2) / 3) + f * 5.4 + S.time * 0.55;
|
|
853
|
+
const rad = f * iw * 0.46;
|
|
854
|
+
const x = cxx + Math.cos(ang) * rad;
|
|
855
|
+
const y = cyy + Math.sin(ang) * rad * 0.42;
|
|
856
|
+
const alpha = (1 - f) * 0.5 * (0.4 + ga * 0.6);
|
|
857
|
+
c.fillStyle = "hsla(" + (196 + a * 32) + ",100%," + (86 - f * 18) + "%," + alpha + ")";
|
|
858
|
+
c.beginPath();
|
|
859
|
+
c.arc(x, y, 0.5 + (1 - f) * 1.5, 0, Math.PI * 2);
|
|
860
|
+
c.fill();
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
c.restore();
|
|
865
|
+
|
|
866
|
+
const gloss = c.createLinearGradient(0, pad, 0, pad + ih);
|
|
867
|
+
gloss.addColorStop(0, "rgba(255,255,255,.16)");
|
|
868
|
+
gloss.addColorStop(0.35, "rgba(255,255,255,.05)");
|
|
869
|
+
gloss.addColorStop(0.55, "rgba(255,255,255,0)");
|
|
870
|
+
gloss.addColorStop(1, "rgba(0,0,0,.18)");
|
|
871
|
+
c.fillStyle = gloss;
|
|
872
|
+
c.fillRect(pad, pad, iw, ih);
|
|
873
|
+
|
|
874
|
+
c.globalCompositeOperation = "source-over";
|
|
875
|
+
c.restore();
|
|
876
|
+
|
|
877
|
+
c.strokeStyle = "hsla(" + hue + ",100%,72%," + (0.18 + e * 0.5) + ")";
|
|
878
|
+
c.lineWidth = 1.2;
|
|
879
|
+
c.shadowColor = "hsla(" + hue + ",100%,68%,.9)";
|
|
880
|
+
c.shadowBlur = 10 + e * 26;
|
|
881
|
+
roundRect(c, pad + 0.5, pad + 0.5, iw - 1, ih - 1, ih / 2);
|
|
882
|
+
c.stroke();
|
|
883
|
+
c.shadowBlur = 0;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/* ---------------- light blades ---------------- */
|
|
887
|
+
|
|
888
|
+
function drawFx(dt) {
|
|
889
|
+
const c = S.fxc;
|
|
890
|
+
if (!c) return;
|
|
891
|
+
c.setTransform(S.dpr, 0, 0, S.dpr, 0, 0);
|
|
892
|
+
c.clearRect(0, 0, S.W, S.H);
|
|
893
|
+
if (!S.astra.length || !S.els || !S.els.track) return;
|
|
894
|
+
const r = S.els.track.getBoundingClientRect();
|
|
895
|
+
const cx = r.left + r.width / 2;
|
|
896
|
+
const cy = r.top + r.height / 2;
|
|
897
|
+
const k = Math.min(1.25, 0.35 + (r.width / 900) * 0.9);
|
|
898
|
+
for (let i = S.astra.length - 1; i >= 0; i--) {
|
|
899
|
+
const f = S.astra[i];
|
|
900
|
+
f.age += dt;
|
|
901
|
+
const p = f.age / f.life;
|
|
902
|
+
if (p >= 1) {
|
|
903
|
+
S.astra.splice(i, 1);
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
const p2 = p * p;
|
|
907
|
+
const th = 13 * (1 - p2) * (1 - p2 * 0.35);
|
|
908
|
+
const baseRy = r.height * 0.55;
|
|
909
|
+
const halfL = r.width * 0.62;
|
|
910
|
+
for (let L = 0; L < 14; L++) {
|
|
911
|
+
const lt = L / 13;
|
|
912
|
+
const a = (0.022 + 0.95 * Math.pow(lt, 2.3)) * (1 - p2) * 0.8;
|
|
913
|
+
if (a <= 0.004) continue;
|
|
914
|
+
const col = Math.round(90 + 165 * Math.pow(lt, 0.7)) + "," + Math.round(150 + 105 * Math.pow(lt, 0.5)) + ",255";
|
|
915
|
+
c.fillStyle = "rgba(" + col + "," + a + ")";
|
|
916
|
+
const ry = Math.max(0.5, th * (0.4 + L * 0.06));
|
|
917
|
+
starBeam(c, cx, cy, halfL * (0.55 + L * 0.03), ry);
|
|
918
|
+
c.save();
|
|
919
|
+
c.translate(cx, cy);
|
|
920
|
+
c.rotate(Math.PI / 2);
|
|
921
|
+
c.translate(-cx, -cy);
|
|
922
|
+
starBeam(c, cx, cy, baseRy * (0.35 + L * 0.05), Math.max(0.4, ry * 0.45));
|
|
923
|
+
c.restore();
|
|
924
|
+
}
|
|
925
|
+
const cr = Math.max(1, (8 + 34 * (1 - p2)) * k);
|
|
926
|
+
const g = c.createRadialGradient(cx, cy, 0, cx, cy, cr);
|
|
927
|
+
g.addColorStop(0, "rgba(255,255,255," + 0.85 * (1 - p2) + ")");
|
|
928
|
+
g.addColorStop(0.5, "rgba(190,215,255," + 0.35 * (1 - p2) + ")");
|
|
929
|
+
g.addColorStop(1, "rgba(120,160,255,0)");
|
|
930
|
+
c.fillStyle = g;
|
|
931
|
+
c.beginPath();
|
|
932
|
+
c.arc(cx, cy, cr, 0, Math.PI * 2);
|
|
933
|
+
c.fill();
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/* ---------------- celebration ---------------- */
|
|
938
|
+
|
|
939
|
+
function spawnStreaks() {
|
|
940
|
+
for (let i = 0; i < 24; i++) {
|
|
941
|
+
const ang = Math.random() * Math.PI * 2;
|
|
942
|
+
const sp = 380 + Math.random() * 980;
|
|
943
|
+
S.streaks.push({
|
|
944
|
+
x: S.W * 0.5,
|
|
945
|
+
y: S.H * 0.5,
|
|
946
|
+
vx: Math.cos(ang) * sp,
|
|
947
|
+
vy: Math.sin(ang) * sp,
|
|
948
|
+
life: 1,
|
|
949
|
+
hue: 180 + Math.random() * 150
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function boom() {
|
|
955
|
+
const els = S.els;
|
|
956
|
+
if (!els) return;
|
|
957
|
+
S.astra.push({ age: 0, life: 0.9 });
|
|
958
|
+
if (S.reduce) return;
|
|
959
|
+
if (els.stage && els.stage.classList) {
|
|
960
|
+
els.stage.classList.remove("es-quake");
|
|
961
|
+
void els.stage.offsetWidth;
|
|
962
|
+
els.stage.classList.add("es-quake");
|
|
963
|
+
}
|
|
964
|
+
if (els.flash && els.flash.classList) {
|
|
965
|
+
els.flash.classList.remove("go");
|
|
966
|
+
void els.flash.offsetWidth;
|
|
967
|
+
els.flash.classList.add("go");
|
|
968
|
+
}
|
|
969
|
+
if (els.big && els.big.classList) {
|
|
970
|
+
els.big.textContent = S.texts.boom;
|
|
971
|
+
els.big.classList.remove("go");
|
|
972
|
+
void els.big.offsetWidth;
|
|
973
|
+
els.big.classList.add("go");
|
|
974
|
+
clearTimeout(S.bigTimer);
|
|
975
|
+
S.bigTimer = setTimeout(() => {
|
|
976
|
+
if (els.big) els.big.textContent = S.texts.after;
|
|
977
|
+
}, 2900);
|
|
978
|
+
}
|
|
979
|
+
const alphas = [0.95, 0.7, 0.5, 0.35];
|
|
980
|
+
const widths = [6, 14, 26, 40];
|
|
981
|
+
const hues = [190, 300, 46, 210];
|
|
982
|
+
for (let i = 0; i < 4; i++) S.waves.push({ r: 0, a: alphas[i], w: widths[i], hue: hues[i] });
|
|
983
|
+
for (let i = 0; i < 220; i++) {
|
|
984
|
+
const ang = Math.random() * Math.PI * 2;
|
|
985
|
+
const sp = 60 + Math.random() * 620;
|
|
986
|
+
S.debris.push({
|
|
987
|
+
x: S.W * 0.5,
|
|
988
|
+
y: S.H * 0.5,
|
|
989
|
+
vx: Math.cos(ang) * sp,
|
|
990
|
+
vy: Math.sin(ang) * sp,
|
|
991
|
+
life: 1,
|
|
992
|
+
hue: [190, 300, 45][i % 3],
|
|
993
|
+
size: 0.6 + Math.random() * 2.2
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
clearTimeout(S.warpTimer);
|
|
997
|
+
S.warpTimer = setTimeout(() => {
|
|
998
|
+
S.warp = 1;
|
|
999
|
+
}, 220);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/* ---------------- loop ---------------- */
|
|
1003
|
+
|
|
1004
|
+
function syncLevel() {
|
|
1005
|
+
const top = topIndex();
|
|
1006
|
+
const i = top > 0 ? Math.round(S.t * top) : 0;
|
|
1007
|
+
if (i !== S.idx) {
|
|
1008
|
+
S.idx = i;
|
|
1009
|
+
if (S.onLevel) {
|
|
1010
|
+
try {
|
|
1011
|
+
S.onLevel(i);
|
|
1012
|
+
} catch (err) {
|
|
1013
|
+
/* a listener error must not stop the animation */
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
const isTop = top > 0 && i === top;
|
|
1018
|
+
if (isTop && !S.topFired) {
|
|
1019
|
+
S.topFired = true;
|
|
1020
|
+
boom();
|
|
1021
|
+
} else if (!isTop) {
|
|
1022
|
+
S.topFired = false;
|
|
1023
|
+
if (S.warp > 0.01) S.warp = 0;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
function paintDom() {
|
|
1028
|
+
const el = S.els && S.els.stage;
|
|
1029
|
+
if (!el || !el.style) return;
|
|
1030
|
+
el.style.setProperty("--es-t", S.t.toFixed(4));
|
|
1031
|
+
el.style.setProperty("--es-hue", String(Math.round(hueAtT(S.t))));
|
|
1032
|
+
el.style.setProperty("--es-e", S.e.toFixed(3));
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
function loop(now) {
|
|
1036
|
+
if (!S.mounted) return;
|
|
1037
|
+
S.raf = requestAnimationFrame(loop);
|
|
1038
|
+
const dt = Math.min(0.05, S.last ? (now - S.last) / 1000 : 0.016);
|
|
1039
|
+
S.last = now;
|
|
1040
|
+
S.time += dt;
|
|
1041
|
+
S.frameNo++;
|
|
1042
|
+
|
|
1043
|
+
if (S.anim) {
|
|
1044
|
+
const p = clamp01((now - S.anim.start) / S.anim.dur);
|
|
1045
|
+
S.t = clamp01(S.anim.from + (S.anim.to - S.anim.from) * easeOutBack(p));
|
|
1046
|
+
if (p >= 1) {
|
|
1047
|
+
S.t = S.anim.to;
|
|
1048
|
+
S.anim = null;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
S.e += (S.t - S.e) * Math.min(1, dt * (S.dragging ? 14 : 6));
|
|
1053
|
+
S.partFade = Math.min(1, S.partFade + dt / 0.28);
|
|
1054
|
+
if (S.prevFade > 0) S.prevFade = Math.max(0, S.prevFade - dt / 0.24);
|
|
1055
|
+
|
|
1056
|
+
syncLevel();
|
|
1057
|
+
ensureParticles();
|
|
1058
|
+
|
|
1059
|
+
const top = topIndex();
|
|
1060
|
+
if (!S.reduce && top > 0 && S.idx === top && S.frameNo % 10 === 0) spawnStreaks();
|
|
1061
|
+
|
|
1062
|
+
drawBG(dt);
|
|
1063
|
+
drawRail();
|
|
1064
|
+
drawFx(dt);
|
|
1065
|
+
paintDom();
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/* ---------------- pointer / keyboard ---------------- */
|
|
1069
|
+
|
|
1070
|
+
function tFromEvent(ev) {
|
|
1071
|
+
const el = S.els && S.els.track;
|
|
1072
|
+
if (!el) return S.t;
|
|
1073
|
+
const r = el.getBoundingClientRect();
|
|
1074
|
+
const span = r.width - EDGE * 2;
|
|
1075
|
+
if (span <= 1) return 0;
|
|
1076
|
+
const x = clamp(ev.clientX - r.left, EDGE, r.width - EDGE);
|
|
1077
|
+
return clamp01((x - EDGE) / span);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function beginDrag(ev) {
|
|
1081
|
+
if (!S.mounted) return;
|
|
1082
|
+
S.dragging = true;
|
|
1083
|
+
S.anim = null;
|
|
1084
|
+
S.t = tFromEvent(ev);
|
|
1085
|
+
try {
|
|
1086
|
+
if (ev.currentTarget && ev.currentTarget.setPointerCapture) ev.currentTarget.setPointerCapture(ev.pointerId);
|
|
1087
|
+
} catch (err) {
|
|
1088
|
+
/* capture is a nicety */
|
|
1089
|
+
}
|
|
1090
|
+
ev.preventDefault();
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
function onDocMove(ev) {
|
|
1094
|
+
if (!S.dragging) return;
|
|
1095
|
+
S.t = tFromEvent(ev);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function onDocUp() {
|
|
1099
|
+
if (!S.dragging) return;
|
|
1100
|
+
S.dragging = false;
|
|
1101
|
+
snapTo(levelT(S.idx, S.count), 300);
|
|
1102
|
+
if (S.onRelease) {
|
|
1103
|
+
try {
|
|
1104
|
+
S.onRelease(S.idx);
|
|
1105
|
+
} catch (err) {
|
|
1106
|
+
/* ignore */
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function onKey(ev) {
|
|
1112
|
+
if (!S.mounted) return;
|
|
1113
|
+
/* The rail is now inline in the composer, so the caret may be in the
|
|
1114
|
+
prompt box while this listener is live. Never steal arrow keys from
|
|
1115
|
+
an editable target. */
|
|
1116
|
+
const target = ev.target;
|
|
1117
|
+
if (target && typeof target === "object") {
|
|
1118
|
+
const tag = String(target.tagName || "").toUpperCase();
|
|
1119
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || target.isContentEditable === true) return;
|
|
1120
|
+
}
|
|
1121
|
+
const top = topIndex();
|
|
1122
|
+
let next = null;
|
|
1123
|
+
if (ev.key === "ArrowRight" || ev.key === "ArrowUp") next = Math.min(top, S.idx + 1);
|
|
1124
|
+
else if (ev.key === "ArrowLeft" || ev.key === "ArrowDown") next = Math.max(0, S.idx - 1);
|
|
1125
|
+
else if (ev.key === "Home") next = 0;
|
|
1126
|
+
else if (ev.key === "End") next = top;
|
|
1127
|
+
if (next === null) return;
|
|
1128
|
+
ev.preventDefault();
|
|
1129
|
+
snapTo(levelT(next, S.count), ev.key === "Home" || ev.key === "End" ? 420 : 300);
|
|
1130
|
+
if (S.onRelease) {
|
|
1131
|
+
try {
|
|
1132
|
+
S.onRelease(next);
|
|
1133
|
+
} catch (err) {
|
|
1134
|
+
/* ignore */
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
function snapTo(to, dur) {
|
|
1140
|
+
S.anim = { from: S.t, to: clamp01(to), start: performance.now(), dur: dur || 300 };
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/* ---------------- lifecycle ---------------- */
|
|
1144
|
+
|
|
1145
|
+
function mount(opts) {
|
|
1146
|
+
S.els = opts.els;
|
|
1147
|
+
S.onLevel = opts.onLevel;
|
|
1148
|
+
S.onRelease = opts.onRelease;
|
|
1149
|
+
S.texts = opts.texts || S.texts;
|
|
1150
|
+
S.reduce = !!opts.reduce;
|
|
1151
|
+
S.count = Math.max(1, (opts.count || 1) | 0);
|
|
1152
|
+
S.mounted = true;
|
|
1153
|
+
S.time = 0;
|
|
1154
|
+
S.frameNo = 0;
|
|
1155
|
+
S.last = 0;
|
|
1156
|
+
S.e = S.t;
|
|
1157
|
+
S.topFired = false;
|
|
1158
|
+
S.warp = 0;
|
|
1159
|
+
S.waves.length = 0;
|
|
1160
|
+
S.debris.length = 0;
|
|
1161
|
+
S.streaks.length = 0;
|
|
1162
|
+
S.astra.length = 0;
|
|
1163
|
+
S.parts = [];
|
|
1164
|
+
S.prevParts = [];
|
|
1165
|
+
S.partKey = "";
|
|
1166
|
+
S.partKind = "";
|
|
1167
|
+
S.partFade = 1;
|
|
1168
|
+
S.prevFade = 0;
|
|
1169
|
+
initStars();
|
|
1170
|
+
if (!S.neb) S.neb = { a: nebulaSprite(216, 262), b: nebulaSprite(286, 196), c: nebulaSprite(190, 320) };
|
|
1171
|
+
fit();
|
|
1172
|
+
requestAnimationFrame(() => {
|
|
1173
|
+
if (S.mounted) fit();
|
|
1174
|
+
});
|
|
1175
|
+
window.addEventListener("resize", fit);
|
|
1176
|
+
window.addEventListener("orientationchange", fit);
|
|
1177
|
+
/* The popup grows and shrinks as the model list opens and closes. */
|
|
1178
|
+
if (typeof ResizeObserver === "function" && S.els && S.els.stage) {
|
|
1179
|
+
S.ro = new ResizeObserver(() => {
|
|
1180
|
+
if (S.mounted) fit();
|
|
1181
|
+
});
|
|
1182
|
+
try {
|
|
1183
|
+
S.ro.observe(S.els.stage);
|
|
1184
|
+
} catch (err) {
|
|
1185
|
+
S.ro = null;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
window.addEventListener("pointermove", onDocMove);
|
|
1189
|
+
window.addEventListener("pointerup", onDocUp);
|
|
1190
|
+
window.addEventListener("pointercancel", onDocUp);
|
|
1191
|
+
window.addEventListener("keydown", onKey);
|
|
1192
|
+
S.raf = requestAnimationFrame(loop);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
function unmount() {
|
|
1196
|
+
S.mounted = false;
|
|
1197
|
+
if (S.raf) cancelAnimationFrame(S.raf);
|
|
1198
|
+
S.raf = 0;
|
|
1199
|
+
clearTimeout(S.bigTimer);
|
|
1200
|
+
clearTimeout(S.warpTimer);
|
|
1201
|
+
window.removeEventListener("resize", fit);
|
|
1202
|
+
window.removeEventListener("orientationchange", fit);
|
|
1203
|
+
if (S.ro) {
|
|
1204
|
+
try {
|
|
1205
|
+
S.ro.disconnect();
|
|
1206
|
+
} catch (err) {}
|
|
1207
|
+
S.ro = null;
|
|
1208
|
+
}
|
|
1209
|
+
window.removeEventListener("pointermove", onDocMove);
|
|
1210
|
+
window.removeEventListener("pointerup", onDocUp);
|
|
1211
|
+
window.removeEventListener("pointercancel", onDocUp);
|
|
1212
|
+
window.removeEventListener("keydown", onKey);
|
|
1213
|
+
S.els = null;
|
|
1214
|
+
S.bgc = null;
|
|
1215
|
+
S.rxc = null;
|
|
1216
|
+
S.fxc = null;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
function setCount(n) {
|
|
1220
|
+
const next = Math.max(1, n | 0);
|
|
1221
|
+
if (next === S.count) return;
|
|
1222
|
+
S.count = next;
|
|
1223
|
+
S.idx = Math.min(S.idx, topIndexOf(S.count));
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
function setIndex(index, immediate) {
|
|
1227
|
+
const top = topIndex();
|
|
1228
|
+
const to = levelT(clamp(index, 0, top), S.count);
|
|
1229
|
+
if (immediate) {
|
|
1230
|
+
S.anim = null;
|
|
1231
|
+
S.t = to;
|
|
1232
|
+
S.e = to;
|
|
1233
|
+
S.idx = Math.round(to * top);
|
|
1234
|
+
/*
|
|
1235
|
+
* Opening the panel must not replay the top-level celebration. Only a
|
|
1236
|
+
* real move into the top level (drag, click, keyboard) fires boom();
|
|
1237
|
+
* mounting while already at the top just arms the guard.
|
|
1238
|
+
*/
|
|
1239
|
+
S.topFired = S.count > 1 && S.idx === top;
|
|
1240
|
+
} else {
|
|
1241
|
+
snapTo(to, 320);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
return {
|
|
1246
|
+
mount,
|
|
1247
|
+
unmount,
|
|
1248
|
+
setCount,
|
|
1249
|
+
setIndex,
|
|
1250
|
+
beginDrag,
|
|
1251
|
+
isDragging() {
|
|
1252
|
+
return S.dragging;
|
|
1253
|
+
},
|
|
1254
|
+
isMounted() {
|
|
1255
|
+
return S.mounted;
|
|
1256
|
+
},
|
|
1257
|
+
index() {
|
|
1258
|
+
return S.idx;
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/* ------------------------------------------------------------------ *
|
|
1264
|
+
* glyphs
|
|
1265
|
+
* ------------------------------------------------------------------ */
|
|
1266
|
+
|
|
1267
|
+
function boltGlyph(size, hue) {
|
|
1268
|
+
return h(
|
|
1269
|
+
"svg",
|
|
1270
|
+
{ width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true", style: { display: "block" } },
|
|
1271
|
+
h("path", {
|
|
1272
|
+
d: "M13.6 2 5.2 13.1h5.3L9.9 22l8.9-11.6h-5.6L13.6 2Z",
|
|
1273
|
+
fill: "currentColor",
|
|
1274
|
+
opacity: 0.92
|
|
1275
|
+
}),
|
|
1276
|
+
h("path", {
|
|
1277
|
+
d: "M13.6 2 5.2 13.1h5.3L9.9 22l8.9-11.6h-5.6L13.6 2Z",
|
|
1278
|
+
fill: "none",
|
|
1279
|
+
stroke: "hsl(" + hue + " 100% 78%)",
|
|
1280
|
+
strokeWidth: 0.7,
|
|
1281
|
+
opacity: 0.8
|
|
1282
|
+
})
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
function resetGlyph() {
|
|
1287
|
+
return h(
|
|
1288
|
+
"svg",
|
|
1289
|
+
{ width: 13, height: 13, viewBox: "0 0 24 24", "aria-hidden": "true" },
|
|
1290
|
+
h("path", {
|
|
1291
|
+
d: "M4 12a8 8 0 1 0 2.5-5.8",
|
|
1292
|
+
fill: "none",
|
|
1293
|
+
stroke: "currentColor",
|
|
1294
|
+
strokeWidth: 2,
|
|
1295
|
+
strokeLinecap: "round"
|
|
1296
|
+
}),
|
|
1297
|
+
h("path", { d: "M4 4v5h5", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" })
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
function closeGlyph() {
|
|
1302
|
+
return h(
|
|
1303
|
+
"svg",
|
|
1304
|
+
{ width: 13, height: 13, viewBox: "0 0 24 24", "aria-hidden": "true" },
|
|
1305
|
+
h("path", { d: "M6 6l12 12M18 6L6 18", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round" })
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
/* ------------------------------------------------------------------ *
|
|
1310
|
+
* controller
|
|
1311
|
+
* ------------------------------------------------------------------ */
|
|
1312
|
+
|
|
1313
|
+
function findModel(catalog, sel) {
|
|
1314
|
+
if (!catalog || !sel) return void 0;
|
|
1315
|
+
const groups = Array.isArray(catalog.groups) ? catalog.groups : [];
|
|
1316
|
+
for (let i = 0; i < groups.length; i++) {
|
|
1317
|
+
const g = groups[i];
|
|
1318
|
+
const models = g && Array.isArray(g.models) ? g.models : [];
|
|
1319
|
+
for (let j = 0; j < models.length; j++) {
|
|
1320
|
+
const m = models[j];
|
|
1321
|
+
if (m && m.id === sel.model && (!sel.provider || g.id === sel.provider)) return m;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
for (let i = 0; i < groups.length; i++) {
|
|
1325
|
+
const models = groups[i] && Array.isArray(groups[i].models) ? groups[i].models : [];
|
|
1326
|
+
for (let j = 0; j < models.length; j++) if (models[j] && models[j].id === sel.model) return models[j];
|
|
1327
|
+
}
|
|
1328
|
+
return void 0;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
/*
|
|
1332
|
+
* Reasoning-effort ids form one shared scale even though providers declare
|
|
1333
|
+
* different subsets of it (off/low/high/max here, off/minimal/low/medium/
|
|
1334
|
+
* high/xhigh/max there). Ranking on that shared scale is what lets a level
|
|
1335
|
+
* the user already picked be mapped onto a model that lacks it.
|
|
1336
|
+
*/
|
|
1337
|
+
const EFFORT_LADDER = ["off", "none", "minimal", "low", "medium", "med", "high", "xhigh", "max", "ultra"];
|
|
1338
|
+
|
|
1339
|
+
/** The effort levels one catalogue model declares, as plain objects. */
|
|
1340
|
+
function modelEfforts(model) {
|
|
1341
|
+
const reasoning = model && model.reasoning ? model.reasoning : void 0;
|
|
1342
|
+
const efforts = reasoning && Array.isArray(reasoning.efforts) ? reasoning.efforts : [];
|
|
1343
|
+
return efforts
|
|
1344
|
+
.filter((ef) => ef && ef.id !== void 0 && ef.id !== null)
|
|
1345
|
+
.map((ef) => ({
|
|
1346
|
+
id: String(ef.id),
|
|
1347
|
+
name: String(ef.name || ef.id),
|
|
1348
|
+
description: ef.description ? String(ef.description) : ""
|
|
1349
|
+
}));
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
/**
|
|
1353
|
+
* Map the effort being carried onto the effort list of the model being
|
|
1354
|
+
* switched to.
|
|
1355
|
+
*
|
|
1356
|
+
* `{ id }` the carried level is supported as-is
|
|
1357
|
+
* `{ id, from }` the model lacks it and the closest rung was swapped in
|
|
1358
|
+
* `{ id: null, from }` the model declares no reasoning at all, so the
|
|
1359
|
+
* request must omit `reasoningEffort` entirely (the
|
|
1360
|
+
* Host rejects any explicit effort for such a model)
|
|
1361
|
+
*/
|
|
1362
|
+
function resolveEffort(model, wantedId) {
|
|
1363
|
+
const levels = modelEfforts(model);
|
|
1364
|
+
const wanted = wantedId === void 0 || wantedId === null ? "" : String(wantedId);
|
|
1365
|
+
if (levels.length === 0) return { id: null, from: wanted };
|
|
1366
|
+
if (!wanted) return { id: null, from: "" };
|
|
1367
|
+
if (levels.some((lv) => lv.id === wanted)) return { id: wanted, from: wanted };
|
|
1368
|
+
const wantRank = EFFORT_LADDER.indexOf(wanted.toLowerCase());
|
|
1369
|
+
if (wantRank < 0) {
|
|
1370
|
+
/* A provider-specific name that is not on the shared scale: its
|
|
1371
|
+
distance is unknowable, so prefer this model's own default and only
|
|
1372
|
+
then its strongest rung. */
|
|
1373
|
+
const fallback = model.reasoning && model.reasoning.defaultEffort ? String(model.reasoning.defaultEffort) : "";
|
|
1374
|
+
if (fallback && levels.some((lv) => lv.id === fallback)) return { id: fallback, from: wanted };
|
|
1375
|
+
return { id: levels[levels.length - 1].id, from: wanted };
|
|
1376
|
+
}
|
|
1377
|
+
let best = 0;
|
|
1378
|
+
let bestGap = Infinity;
|
|
1379
|
+
let bestScaled = -Infinity;
|
|
1380
|
+
for (let i = 0; i < levels.length; i++) {
|
|
1381
|
+
const rank = EFFORT_LADDER.indexOf(levels[i].id.toLowerCase());
|
|
1382
|
+
/* An off-ladder rung still has to sit somewhere: spread the unknown
|
|
1383
|
+
ones evenly across the interior of the scale, in declared order. */
|
|
1384
|
+
const scaled = rank >= 0 ? rank : 1 + (levels.length > 1 ? (i / (levels.length - 1)) * (EFFORT_LADDER.length - 2) : 0);
|
|
1385
|
+
const gap = Math.abs(scaled - wantRank);
|
|
1386
|
+
/* Equal distance keeps the stronger rung — silently dropping reasoning
|
|
1387
|
+
power is the worse of the two surprises — and it is decided on the
|
|
1388
|
+
scale, not on declaration order, so a provider that lists its levels
|
|
1389
|
+
the other way round still gets the strong pick. */
|
|
1390
|
+
if (gap < bestGap - 1e-9 || (gap < bestGap + 1e-9 && scaled > bestScaled)) {
|
|
1391
|
+
bestGap = Math.min(gap, bestGap);
|
|
1392
|
+
bestScaled = scaled;
|
|
1393
|
+
best = i;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
return { id: levels[best].id, from: wanted };
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
/** Display name of one effort id on a model, falling back to the id. */
|
|
1400
|
+
function effortName(model, id) {
|
|
1401
|
+
const hit = modelEfforts(model).filter((lv) => lv.id === id)[0];
|
|
1402
|
+
return hit ? hit.name : String(id);
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
/**
|
|
1406
|
+
* The composer card that owns the trigger. The shipped DOM uses hashed
|
|
1407
|
+
* CSS-module names (`<hash>_card`), so match that suffix first and fall
|
|
1408
|
+
* back to "the nearest rounded box that owns a text field".
|
|
1409
|
+
*/
|
|
1410
|
+
/* A preference, not state: the halo can be switched off, and the choice
|
|
1411
|
+
outlives the page. localStorage is guarded because a host context may
|
|
1412
|
+
refuse it. */
|
|
1413
|
+
const HALO_KEY = "effort-slider:halo";
|
|
1414
|
+
function readHaloPref() {
|
|
1415
|
+
try {
|
|
1416
|
+
if (typeof window === "undefined" || !window.localStorage) return true;
|
|
1417
|
+
const raw = window.localStorage.getItem(HALO_KEY);
|
|
1418
|
+
return raw === null ? true : raw !== "0";
|
|
1419
|
+
} catch (err) {
|
|
1420
|
+
return true;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
function writeHaloPref(on) {
|
|
1424
|
+
try {
|
|
1425
|
+
if (typeof window === "undefined" || !window.localStorage) return;
|
|
1426
|
+
window.localStorage.setItem(HALO_KEY, on ? "1" : "0");
|
|
1427
|
+
} catch (err) {
|
|
1428
|
+
/* a preference that cannot persist still applies to this page */
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
function composerBox(el) {
|
|
1433
|
+
if (!el || typeof el.closest !== "function") return null;
|
|
1434
|
+
const card = el.closest('[class*="_card"]');
|
|
1435
|
+
if (card && card.querySelector && card.querySelector('textarea,[contenteditable="true"],[role="textbox"]')) return card;
|
|
1436
|
+
let node = el.parentElement;
|
|
1437
|
+
while (node && node !== document.body) {
|
|
1438
|
+
if (node.querySelector && node.querySelector('textarea,[contenteditable="true"],[role="textbox"]')) {
|
|
1439
|
+
const cs = window.getComputedStyle(node);
|
|
1440
|
+
if ((parseFloat(cs.borderTopLeftRadius) || 0) >= 10) return node;
|
|
1441
|
+
}
|
|
1442
|
+
node = node.parentElement;
|
|
1443
|
+
}
|
|
1444
|
+
return null;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
/** The page shares one ring, so a second mount cannot leave a ghost. */
|
|
1448
|
+
let HALO = null;
|
|
1449
|
+
function haloLayer() {
|
|
1450
|
+
if (HALO === null) HALO = createHalo();
|
|
1451
|
+
return HALO;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
/**
|
|
1455
|
+
* One fixed ring that hugs the composer. It is created once and reused, so
|
|
1456
|
+
* re-renders never stack copies; the geometry follows the card through a
|
|
1457
|
+
* ResizeObserver plus the window events that can move it.
|
|
1458
|
+
*/
|
|
1459
|
+
function createHalo() {
|
|
1460
|
+
let node = null;
|
|
1461
|
+
let target = null;
|
|
1462
|
+
let observer = null;
|
|
1463
|
+
let frame = 0;
|
|
1464
|
+
let mounts = 0;
|
|
1465
|
+
let anchor = null;
|
|
1466
|
+
let pollTimer = 0;
|
|
1467
|
+
let until = 0;
|
|
1468
|
+
let lastGeom = "";
|
|
1469
|
+
|
|
1470
|
+
function paint() {
|
|
1471
|
+
if (!node) return;
|
|
1472
|
+
/*
|
|
1473
|
+
* The host may rebuild the composer node (a session switch, a layout
|
|
1474
|
+
* change) without telling us; look the card up again from the trigger
|
|
1475
|
+
* rather than keeping a ring pinned to a detached node.
|
|
1476
|
+
*/
|
|
1477
|
+
if ((!target || !target.isConnected) && anchor && anchor.isConnected) {
|
|
1478
|
+
const again = composerBox(anchor);
|
|
1479
|
+
if (again !== target) {
|
|
1480
|
+
target = again;
|
|
1481
|
+
if (again && typeof window.ResizeObserver === "function") {
|
|
1482
|
+
if (observer && typeof observer.disconnect === "function") observer.disconnect();
|
|
1483
|
+
observer = new window.ResizeObserver(schedule);
|
|
1484
|
+
observer.observe(again);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
if (!target || !target.isConnected) {
|
|
1489
|
+
hide();
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
const rect = target.getBoundingClientRect();
|
|
1493
|
+
if (!rect.width || !rect.height || rect.bottom < 0 || rect.top > window.innerHeight) {
|
|
1494
|
+
hide();
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
/*
|
|
1498
|
+
* The composer moves without resizing — a Session switch turns the
|
|
1499
|
+
* centred empty state into the docked conversation view, and a pure
|
|
1500
|
+
* ResizeObserver never fires for that. Re-read the geometry instead of
|
|
1501
|
+
* trusting the last placed rect, but only touch the DOM when it moved.
|
|
1502
|
+
*/
|
|
1503
|
+
const key = rect.left + "," + rect.top + "," + rect.width + "," + rect.height;
|
|
1504
|
+
if (key === lastGeom && node.dataset.on === "1") return;
|
|
1505
|
+
lastGeom = key;
|
|
1506
|
+
const radius = parseFloat(window.getComputedStyle(target).borderTopLeftRadius) || 16;
|
|
1507
|
+
node.style.left = rect.left - 2 + "px";
|
|
1508
|
+
node.style.top = rect.top - 2 + "px";
|
|
1509
|
+
node.style.width = rect.width + 4 + "px";
|
|
1510
|
+
node.style.height = rect.height + 4 + "px";
|
|
1511
|
+
node.style.borderRadius = radius + 2 + "px";
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
/** Frames follow the card closely for a moment after anything may have moved it. */
|
|
1515
|
+
function watchFor(ms) {
|
|
1516
|
+
until = Math.max(until, Date.now() + ms);
|
|
1517
|
+
if (frame) return;
|
|
1518
|
+
frame = window.requestAnimationFrame(track);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
function track() {
|
|
1522
|
+
frame = 0;
|
|
1523
|
+
if (!node) return;
|
|
1524
|
+
paint();
|
|
1525
|
+
if (!target || Date.now() >= until) return;
|
|
1526
|
+
frame = window.requestAnimationFrame(track);
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
function schedule() {
|
|
1530
|
+
watchFor(1200);
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
/* A layout change can also land long after the switch, so a slow poll
|
|
1534
|
+
keeps watching for as long as the ring exists. */
|
|
1535
|
+
function poll() {
|
|
1536
|
+
pollTimer = setTimeout(() => {
|
|
1537
|
+
pollTimer = 0;
|
|
1538
|
+
if (!node) return;
|
|
1539
|
+
paint();
|
|
1540
|
+
poll();
|
|
1541
|
+
}, 250);
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
function startPoll() {
|
|
1545
|
+
if (pollTimer || typeof setTimeout !== "function") return;
|
|
1546
|
+
poll();
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
function stopPoll() {
|
|
1550
|
+
if (!pollTimer) return;
|
|
1551
|
+
clearTimeout(pollTimer);
|
|
1552
|
+
pollTimer = 0;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
/** Leaving no paint behind matters more than leaving no state behind. */
|
|
1556
|
+
function hide() {
|
|
1557
|
+
if (!node) return;
|
|
1558
|
+
node.dataset.on = "0";
|
|
1559
|
+
node.dataset.hot = "0";
|
|
1560
|
+
node.style.opacity = "0.000";
|
|
1561
|
+
node.style.visibility = "hidden";
|
|
1562
|
+
lastGeom = "";
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
function ensure() {
|
|
1566
|
+
if (node) return node;
|
|
1567
|
+
/* An earlier copy of this module may have left its ring in the body. */
|
|
1568
|
+
if (typeof document.querySelectorAll === "function") {
|
|
1569
|
+
const stale = document.querySelectorAll(".es-halo");
|
|
1570
|
+
for (let i = 0; i < stale.length; i += 1) {
|
|
1571
|
+
if (stale[i].parentNode) stale[i].parentNode.removeChild(stale[i]);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
node = document.createElement("div");
|
|
1575
|
+
node.className = "es-halo";
|
|
1576
|
+
node.setAttribute("aria-hidden", "true");
|
|
1577
|
+
document.body.appendChild(node);
|
|
1578
|
+
window.addEventListener("resize", schedule, { passive: true });
|
|
1579
|
+
document.addEventListener("scroll", schedule, { capture: true, passive: true });
|
|
1580
|
+
return node;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
function place(box, opts, from) {
|
|
1584
|
+
const el = ensure();
|
|
1585
|
+
if (from) anchor = from;
|
|
1586
|
+
if (box !== target) {
|
|
1587
|
+
target = box;
|
|
1588
|
+
if (observer && typeof observer.disconnect === "function") observer.disconnect();
|
|
1589
|
+
observer = null;
|
|
1590
|
+
if (target && typeof window.ResizeObserver === "function") {
|
|
1591
|
+
observer = new window.ResizeObserver(schedule);
|
|
1592
|
+
observer.observe(target);
|
|
1593
|
+
}
|
|
1594
|
+
lastGeom = "";
|
|
1595
|
+
if (target) startPoll();
|
|
1596
|
+
watchFor(3000);
|
|
1597
|
+
}
|
|
1598
|
+
const heat = Math.max(0, Math.min(1, Number(opts.heat) || 0));
|
|
1599
|
+
el.style.setProperty("--es-hue", String(opts.hue | 0));
|
|
1600
|
+
el.style.setProperty("--es-a0", (0.1 + heat * 0.34).toFixed(3));
|
|
1601
|
+
el.style.setProperty("--es-a1", (0.04 + heat * 0.14).toFixed(3));
|
|
1602
|
+
el.style.setProperty("--es-a2", (0.1 + heat * 0.44).toFixed(3));
|
|
1603
|
+
el.style.setProperty("--es-a3", (0.05 + heat * 0.3).toFixed(3));
|
|
1604
|
+
el.style.setProperty("--es-b1", (10 + heat * 26).toFixed(1) + "px");
|
|
1605
|
+
el.style.setProperty("--es-b2", (34 + heat * 78).toFixed(1) + "px");
|
|
1606
|
+
el.style.opacity = String((!target ? 0 : opts.open ? 0.62 + heat * 0.38 : 0.2 + heat * 0.8).toFixed(3));
|
|
1607
|
+
el.style.visibility = target ? "visible" : "hidden";
|
|
1608
|
+
el.dataset.on = target ? "1" : "0";
|
|
1609
|
+
el.dataset.hot = target && opts.hot ? "1" : "0";
|
|
1610
|
+
if (!target) hide();
|
|
1611
|
+
else schedule();
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
function retain() {
|
|
1615
|
+
mounts += 1;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
function release() {
|
|
1619
|
+
mounts = Math.max(0, mounts - 1);
|
|
1620
|
+
if (mounts > 0) return;
|
|
1621
|
+
stopPoll();
|
|
1622
|
+
lastGeom = "";
|
|
1623
|
+
if (observer && typeof observer.disconnect === "function") observer.disconnect();
|
|
1624
|
+
observer = null;
|
|
1625
|
+
target = null;
|
|
1626
|
+
anchor = null;
|
|
1627
|
+
hide();
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
return { place, retain, release };
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
class EffortController {
|
|
1634
|
+
constructor(ctx) {
|
|
1635
|
+
this.ctx = ctx;
|
|
1636
|
+
this.catalog = null;
|
|
1637
|
+
this.selection = null;
|
|
1638
|
+
this.levels = [];
|
|
1639
|
+
this.defaultIndex = 0;
|
|
1640
|
+
this.sessionId = void 0;
|
|
1641
|
+
this.loading = false;
|
|
1642
|
+
/* A read requested while another is in flight is queued, not dropped:
|
|
1643
|
+
the correction that follows a Session switch almost always arrives
|
|
1644
|
+
during the first catalogue read, and dropping it left the pill on
|
|
1645
|
+
the deployment default until something else happened to refresh. */
|
|
1646
|
+
this.refreshQueued = false;
|
|
1647
|
+
this.bindTimer = 0;
|
|
1648
|
+
this.bindTries = 0;
|
|
1649
|
+
this.commitTimer = 0;
|
|
1650
|
+
this.pendingIndex = -1;
|
|
1651
|
+
this.noticeTimer = 0;
|
|
1652
|
+
/* The last effort the user actually chose, kept so a round-trip through
|
|
1653
|
+
a model that cannot express it returns to that level, not a default. */
|
|
1654
|
+
this.lastEffort = "";
|
|
1655
|
+
this.token = 0;
|
|
1656
|
+
this.bindT = null;
|
|
1657
|
+
this.selFace = void 0;
|
|
1658
|
+
this.selUnsub = void 0;
|
|
1659
|
+
this.store = createStore({
|
|
1660
|
+
open: false,
|
|
1661
|
+
listOpen: false,
|
|
1662
|
+
levels: [],
|
|
1663
|
+
models: [],
|
|
1664
|
+
index: 0,
|
|
1665
|
+
t: 0,
|
|
1666
|
+
busy: false,
|
|
1667
|
+
error: null,
|
|
1668
|
+
notice: null,
|
|
1669
|
+
supported: true,
|
|
1670
|
+
ready: false,
|
|
1671
|
+
modelName: "",
|
|
1672
|
+
modelId: "",
|
|
1673
|
+
provider: "",
|
|
1674
|
+
halo: readHaloPref()
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
bindLocale() {
|
|
1679
|
+
const bind = readService(() => this.ctx.locale.bind(NS));
|
|
1680
|
+
if (typeof bind === "function") this.bindT = bind;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
t(key, vars) {
|
|
1684
|
+
let out = void 0;
|
|
1685
|
+
if (this.bindT) {
|
|
1686
|
+
try {
|
|
1687
|
+
const v = this.bindT(key, vars);
|
|
1688
|
+
if (typeof v === "string" && v.length && v !== key) out = v;
|
|
1689
|
+
} catch (err) {
|
|
1690
|
+
/* fall through to the bundled dictionary */
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
if (out === void 0) {
|
|
1694
|
+
out = typeof zh[key] === "string" ? zh[key] : key;
|
|
1695
|
+
if (vars) {
|
|
1696
|
+
Object.keys(vars).forEach((k) => {
|
|
1697
|
+
out = out.split("{" + k + "}").join(String(vars[k]));
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
return out;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
face() {
|
|
1705
|
+
return {
|
|
1706
|
+
store: this.store,
|
|
1707
|
+
t: (k, v) => this.t(k, v),
|
|
1708
|
+
open: () => this.open(),
|
|
1709
|
+
close: () => this.close(),
|
|
1710
|
+
toggle: () => this.toggle(),
|
|
1711
|
+
toggleModels: () => this.toggleModels(),
|
|
1712
|
+
toggleHalo: () => this.toggleHalo(),
|
|
1713
|
+
chooseModel: (provider, modelId) => this.chooseModel(provider, modelId),
|
|
1714
|
+
reset: () => this.reset(),
|
|
1715
|
+
commit: (i) => this.commit(i),
|
|
1716
|
+
setIndex: (i) => this.setIndex(i),
|
|
1717
|
+
setSession: (id) => this.setSession(id)
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
toggleHalo() {
|
|
1722
|
+
const on = !this.store.getSnapshot().halo;
|
|
1723
|
+
writeHaloPref(on);
|
|
1724
|
+
this.store.set({ halo: on });
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
dispose() {
|
|
1728
|
+
clearTimeout(this.commitTimer);
|
|
1729
|
+
if (this.bindTimer) {
|
|
1730
|
+
clearTimeout(this.bindTimer);
|
|
1731
|
+
this.bindTimer = 0;
|
|
1732
|
+
}
|
|
1733
|
+
this.dropSelectionSubscription();
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
/* ---------------- state ---------------- */
|
|
1737
|
+
|
|
1738
|
+
setIndex(index) {
|
|
1739
|
+
const snap = this.store.getSnapshot();
|
|
1740
|
+
const top = topIndexOf(snap.levels.length);
|
|
1741
|
+
const i = clamp(index | 0, 0, top);
|
|
1742
|
+
if (i === snap.index && Math.abs(snap.t - levelT(i, snap.levels.length)) < 0.001) return;
|
|
1743
|
+
this.store.set({ index: i, t: levelT(i, snap.levels.length) });
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
setSession(id) {
|
|
1747
|
+
if (!id || id === this.sessionId) return;
|
|
1748
|
+
this.sessionId = id;
|
|
1749
|
+
/* Load the level names for the pill itself, so it shows the real effort
|
|
1750
|
+
instead of the generic label before the panel is ever opened. */
|
|
1751
|
+
this.refresh();
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
/**
|
|
1755
|
+
* The Session's durable model-selection projection — the same source the
|
|
1756
|
+
* shipped selector reads. `projections.faceOf("modelSelection")` yields
|
|
1757
|
+
* `{ lastUsed, next }` where `next = pending ?? lastUsed`: a
|
|
1758
|
+
* `model/selection` event carries the user's intent immediately, and a
|
|
1759
|
+
* `request/header` records what a request actually used. `catalog.default`
|
|
1760
|
+
* is only the deployment default and can differ from what this Session
|
|
1761
|
+
* runs, so it is a fallback only.
|
|
1762
|
+
*/
|
|
1763
|
+
selectionFace() {
|
|
1764
|
+
const sessionId = this.resolveSessionId();
|
|
1765
|
+
if (!sessionId) return void 0;
|
|
1766
|
+
const sessions = readService(() => this.ctx.sessions);
|
|
1767
|
+
if (!sessions || typeof sessions.binding !== "function") return void 0;
|
|
1768
|
+
let binding;
|
|
1769
|
+
try {
|
|
1770
|
+
binding = sessions.binding(sessionId);
|
|
1771
|
+
} catch (err) {
|
|
1772
|
+
return void 0;
|
|
1773
|
+
}
|
|
1774
|
+
const session = binding && binding.session;
|
|
1775
|
+
const projections = session && session.projections;
|
|
1776
|
+
if (!projections || typeof projections.faceOf !== "function") return void 0;
|
|
1777
|
+
try {
|
|
1778
|
+
return projections.faceOf("modelSelection");
|
|
1779
|
+
} catch (err) {
|
|
1780
|
+
return void 0;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
/** Current projected selection, or undefined when the projection has no value yet. */
|
|
1785
|
+
projectedSelection() {
|
|
1786
|
+
const face = this.selectionFace();
|
|
1787
|
+
if (!face || typeof face.getSnapshot !== "function") return void 0;
|
|
1788
|
+
let view;
|
|
1789
|
+
try {
|
|
1790
|
+
view = face.getSnapshot();
|
|
1791
|
+
} catch (err) {
|
|
1792
|
+
return void 0;
|
|
1793
|
+
}
|
|
1794
|
+
const next = view && view.next;
|
|
1795
|
+
if (!next || typeof next.provider !== "string" || typeof next.model !== "string") return void 0;
|
|
1796
|
+
return next;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
dropSelectionSubscription() {
|
|
1800
|
+
if (this.selUnsub) {
|
|
1801
|
+
try {
|
|
1802
|
+
this.selUnsub();
|
|
1803
|
+
} catch (err) {
|
|
1804
|
+
/* an already-disposed face throws on unsubscribe */
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
this.selUnsub = void 0;
|
|
1808
|
+
this.selFace = void 0;
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
/**
|
|
1812
|
+
* Follow the Session's projection so a selection made anywhere else (the
|
|
1813
|
+
* `/model` command, a restored Session) updates this control instead of
|
|
1814
|
+
* leaving a stale label in the composer.
|
|
1815
|
+
*/
|
|
1816
|
+
bindSelection() {
|
|
1817
|
+
const face = this.selectionFace();
|
|
1818
|
+
/* Both being undefined is not "already bound": a Session that was just
|
|
1819
|
+
created has no binding yet, and comparing undefined to undefined
|
|
1820
|
+
used to skip the retry that fixes it. */
|
|
1821
|
+
if (face) {
|
|
1822
|
+
if (face === this.selFace) return;
|
|
1823
|
+
} else {
|
|
1824
|
+
this.scheduleBind();
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
this.dropSelectionSubscription();
|
|
1828
|
+
if (typeof face.subscribe !== "function") {
|
|
1829
|
+
this.scheduleBind();
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
this.bindTries = 0;
|
|
1833
|
+
if (this.bindTimer) {
|
|
1834
|
+
clearTimeout(this.bindTimer);
|
|
1835
|
+
this.bindTimer = 0;
|
|
1836
|
+
}
|
|
1837
|
+
this.selFace = face;
|
|
1838
|
+
this.selUnsub = face.subscribe(() => {
|
|
1839
|
+
/* Opening the face and later commits both notify; defer so the
|
|
1840
|
+
in-flight refresh guard has cleared before this one starts. */
|
|
1841
|
+
setTimeout(() => {
|
|
1842
|
+
this.refresh();
|
|
1843
|
+
}, 0);
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
scheduleBind() {
|
|
1848
|
+
if (this.bindTimer || this.bindTries >= 12) return;
|
|
1849
|
+
const delay = this.bindTries < 6 ? 200 : 1000;
|
|
1850
|
+
this.bindTries += 1;
|
|
1851
|
+
this.bindTimer = setTimeout(() => {
|
|
1852
|
+
this.bindTimer = 0;
|
|
1853
|
+
this.bindSelection();
|
|
1854
|
+
this.refresh();
|
|
1855
|
+
}, delay);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
resolveSessionId() {
|
|
1859
|
+
if (this.sessionId) return this.sessionId;
|
|
1860
|
+
const sessions = readService(() => this.ctx.sessions);
|
|
1861
|
+
if (!sessions || !sessions.list || typeof sessions.list.getSnapshot !== "function") return void 0;
|
|
1862
|
+
let snap = void 0;
|
|
1863
|
+
try {
|
|
1864
|
+
snap = sessions.list.getSnapshot();
|
|
1865
|
+
} catch (err) {
|
|
1866
|
+
return void 0;
|
|
1867
|
+
}
|
|
1868
|
+
return mainViewSessionId(snap && snap.byId);
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
open() {
|
|
1872
|
+
this.store.set({ open: true });
|
|
1873
|
+
this.refresh();
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
close() {
|
|
1877
|
+
this.store.set({ open: false, listOpen: false });
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
toggle() {
|
|
1881
|
+
if (this.store.getSnapshot().open) this.close();
|
|
1882
|
+
else this.open();
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
toggleModels() {
|
|
1886
|
+
const snap = this.store.getSnapshot();
|
|
1887
|
+
this.store.set({ listOpen: !snap.listOpen });
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
reset() {
|
|
1891
|
+
const target = this.defaultIndex;
|
|
1892
|
+
this.setIndex(target);
|
|
1893
|
+
this.commit(target);
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
/**
|
|
1897
|
+
* Switch the Session-local model while keeping the reasoning effort the
|
|
1898
|
+
* user already picked, then reload the catalogue because the new model
|
|
1899
|
+
* exposes its own effort list.
|
|
1900
|
+
*/
|
|
1901
|
+
async chooseModel(provider, modelId) {
|
|
1902
|
+
const snap = this.store.getSnapshot();
|
|
1903
|
+
if (snap.provider === provider && snap.modelId === modelId) {
|
|
1904
|
+
this.store.set({ listOpen: false });
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
const remote = readService(() => this.ctx.remote.session);
|
|
1908
|
+
if (!remote || typeof remote.selectModel !== "function") {
|
|
1909
|
+
this.store.set({ error: "selectModel is unavailable in this build" });
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
const sessionId = this.resolveSessionId();
|
|
1913
|
+
if (!sessionId) {
|
|
1914
|
+
this.store.set({ error: this.t("noSession") });
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
const level = this.levels[snap.index];
|
|
1918
|
+
if (level) this.lastEffort = level.id;
|
|
1919
|
+
const target = findModel(this.catalog, { provider: String(provider), model: String(modelId) });
|
|
1920
|
+
/* The Host rejects an explicit effort the target model does not declare
|
|
1921
|
+
(UNSUPPORTED_REASONING_EFFORT) and performs no clamping of its own, so
|
|
1922
|
+
the carried level is mapped onto the target's rungs before asking.
|
|
1923
|
+
An unknown target keeps the old shape and lets the Host be the
|
|
1924
|
+
authority. */
|
|
1925
|
+
const mapped = target ? resolveEffort(target, level ? level.id : this.lastEffort) : { id: level ? level.id : null, from: "" };
|
|
1926
|
+
const request = { sessionId, provider: String(provider), model: String(modelId) };
|
|
1927
|
+
if (mapped.id) request.reasoningEffort = mapped.id;
|
|
1928
|
+
const token = ++this.token;
|
|
1929
|
+
this.store.set({ busy: true, error: null, notice: null, listOpen: false });
|
|
1930
|
+
try {
|
|
1931
|
+
const res = await withTimeout(remote.selectModel(request), SELECT_TIMEOUT_MS);
|
|
1932
|
+
if (token !== this.token) return;
|
|
1933
|
+
if (res && res.ok) {
|
|
1934
|
+
const selected = res.value && res.value.selected;
|
|
1935
|
+
const next = Object.assign({}, this.selection || {}, selected || {}, {
|
|
1936
|
+
provider: request.provider,
|
|
1937
|
+
model: request.model
|
|
1938
|
+
});
|
|
1939
|
+
if (mapped.id) next.reasoningEffort = mapped.id;
|
|
1940
|
+
else delete next.reasoningEffort;
|
|
1941
|
+
this.selection = next;
|
|
1942
|
+
this.store.set({ busy: false, error: null, notice: this.note(this.switchNotice(target, level, mapped)) });
|
|
1943
|
+
this.loading = false;
|
|
1944
|
+
this.refresh();
|
|
1945
|
+
} else {
|
|
1946
|
+
this.store.set({ busy: false, error: messageOf(res && res.error) || "refused" });
|
|
1947
|
+
}
|
|
1948
|
+
} catch (err) {
|
|
1949
|
+
if (token !== this.token) return;
|
|
1950
|
+
this.store.set({ busy: false, error: messageOf(err) });
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
/**
|
|
1955
|
+
* One short line saying what the switch had to change, or null when the
|
|
1956
|
+
* carried effort was accepted verbatim.
|
|
1957
|
+
*/
|
|
1958
|
+
switchNotice(target, level, mapped) {
|
|
1959
|
+
if (!target || !level) return null;
|
|
1960
|
+
if (mapped.id === null) return this.t("noEffortModel", { model: target.name || target.id || "" });
|
|
1961
|
+
if (mapped.id !== level.id) return this.t("clamped", { from: level.name || level.id, to: effortName(target, mapped.id) });
|
|
1962
|
+
return null;
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
/** Show a transient footer line; it expires on its own and never sticks. */
|
|
1966
|
+
note(text) {
|
|
1967
|
+
if (!text) return null;
|
|
1968
|
+
clearTimeout(this.noticeTimer);
|
|
1969
|
+
const shown = text;
|
|
1970
|
+
this.noticeTimer = setTimeout(() => {
|
|
1971
|
+
if (this.store.getSnapshot().notice === shown) this.store.set({ notice: null });
|
|
1972
|
+
}, 9000);
|
|
1973
|
+
return shown;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
/* ---------------- catalogue ---------------- */
|
|
1977
|
+
|
|
1978
|
+
async refresh() {
|
|
1979
|
+
if (this.loading) {
|
|
1980
|
+
this.refreshQueued = true;
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
this.loading = true;
|
|
1984
|
+
try {
|
|
1985
|
+
do {
|
|
1986
|
+
this.refreshQueued = false;
|
|
1987
|
+
await this.refreshOnce();
|
|
1988
|
+
} while (this.refreshQueued);
|
|
1989
|
+
} finally {
|
|
1990
|
+
this.loading = false;
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
async refreshOnce() {
|
|
1995
|
+
/* Reading a projection before subscribing to it can return no value at
|
|
1996
|
+
all, so the follow-up starts the subscription first. */
|
|
1997
|
+
this.bindSelection();
|
|
1998
|
+
let remote;
|
|
1999
|
+
try {
|
|
2000
|
+
remote = this.ctx.remote.session;
|
|
2001
|
+
} catch (err) {
|
|
2002
|
+
this.store.set({ supported: false, ready: true, levels: [], modelName: "", error: null, debug: "inject:" + messageOf(err) });
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
if (!remote || typeof remote.modelCatalog !== "function") {
|
|
2006
|
+
this.store.set({
|
|
2007
|
+
supported: false,
|
|
2008
|
+
ready: true,
|
|
2009
|
+
levels: [],
|
|
2010
|
+
modelName: "",
|
|
2011
|
+
error: null,
|
|
2012
|
+
debug: "shape:" + (remote ? Object.keys(remote).slice(0, 8).join("+") : "undef")
|
|
2013
|
+
});
|
|
2014
|
+
return;
|
|
2015
|
+
}
|
|
2016
|
+
try {
|
|
2017
|
+
const res = await withTimeout(remote.modelCatalog(), SELECT_TIMEOUT_MS);
|
|
2018
|
+
if (!res || !res.ok) {
|
|
2019
|
+
this.store.set({ ready: true, error: messageOf(res && res.error) || "modelCatalog failed", debug: "res:" + (res ? String(res.ok) : "null") });
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
const catalog = res.value || {};
|
|
2023
|
+
this.catalog = catalog;
|
|
2024
|
+
const fallback = catalog.default || {};
|
|
2025
|
+
/* The durable projection is authoritative: it carries the user's
|
|
2026
|
+
pending intent immediately after a `model/selection` event and the
|
|
2027
|
+
last actually-used configuration after a `request/header`. */
|
|
2028
|
+
const projected = this.projectedSelection();
|
|
2029
|
+
const sel = Object.assign({}, fallback, projected || {});
|
|
2030
|
+
this.selection = Object.assign({}, sel);
|
|
2031
|
+
const model = findModel(catalog, sel) || findModel(catalog, fallback);
|
|
2032
|
+
const reasoning = model && model.reasoning ? model.reasoning : void 0;
|
|
2033
|
+
const efforts = reasoning && Array.isArray(reasoning.efforts) ? reasoning.efforts : [];
|
|
2034
|
+
const levels = efforts
|
|
2035
|
+
.filter((ef) => ef && ef.id !== void 0 && ef.id !== null)
|
|
2036
|
+
.map((ef) => ({
|
|
2037
|
+
id: String(ef.id),
|
|
2038
|
+
name: String(ef.name || ef.id),
|
|
2039
|
+
description: ef.description ? String(ef.description) : ""
|
|
2040
|
+
}));
|
|
2041
|
+
const fallbackId = reasoning && reasoning.defaultEffort ? String(reasoning.defaultEffort) : "";
|
|
2042
|
+
let index = levels.findIndex((l) => l.id === String(sel.reasoningEffort || ""));
|
|
2043
|
+
if (index < 0 && fallbackId) index = levels.findIndex((l) => l.id === fallbackId);
|
|
2044
|
+
if (index < 0) index = Math.max(0, levels.length - 1);
|
|
2045
|
+
this.levels = levels;
|
|
2046
|
+
this.defaultIndex = index;
|
|
2047
|
+
const groups = Array.isArray(catalog.groups) ? catalog.groups : [];
|
|
2048
|
+
const models = groups.map((group) => ({
|
|
2049
|
+
id: String(group && group.id != null ? group.id : ""),
|
|
2050
|
+
name: String((group && (group.name || group.id)) || ""),
|
|
2051
|
+
models: (Array.isArray(group && group.models) ? group.models : [])
|
|
2052
|
+
.filter((m) => m && m.id != null)
|
|
2053
|
+
.map((m) => ({
|
|
2054
|
+
id: String(m.id),
|
|
2055
|
+
name: String(m.name || m.id),
|
|
2056
|
+
description: m.description ? String(m.description) : "",
|
|
2057
|
+
efforts: m.reasoning && Array.isArray(m.reasoning.efforts) ? m.reasoning.efforts.length : 0
|
|
2058
|
+
}))
|
|
2059
|
+
}));
|
|
2060
|
+
this.store.set({
|
|
2061
|
+
levels,
|
|
2062
|
+
models,
|
|
2063
|
+
modelId: String(sel.model || ""),
|
|
2064
|
+
provider: String(sel.provider || ""),
|
|
2065
|
+
index,
|
|
2066
|
+
t: levelT(index, levels.length),
|
|
2067
|
+
supported: levels.length > 0,
|
|
2068
|
+
ready: true,
|
|
2069
|
+
error: null,
|
|
2070
|
+
debug: "ok:" + levels.length,
|
|
2071
|
+
modelName: model ? String(model.name || model.id || "") : String(sel.model || "")
|
|
2072
|
+
});
|
|
2073
|
+
} catch (err) {
|
|
2074
|
+
this.store.set({ ready: true, error: messageOf(err), debug: "throw:" + messageOf(err) });
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
/* ---------------- commit ---------------- */
|
|
2079
|
+
|
|
2080
|
+
commit(index) {
|
|
2081
|
+
this.setIndex(index);
|
|
2082
|
+
this.pendingIndex = index;
|
|
2083
|
+
clearTimeout(this.commitTimer);
|
|
2084
|
+
this.commitTimer = setTimeout(() => {
|
|
2085
|
+
this.flush();
|
|
2086
|
+
}, COMMIT_DEBOUNCE_MS);
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
async flush() {
|
|
2090
|
+
const index = this.pendingIndex;
|
|
2091
|
+
this.pendingIndex = -1;
|
|
2092
|
+
if (index < 0) return;
|
|
2093
|
+
const level = this.levels[index];
|
|
2094
|
+
if (!level) return;
|
|
2095
|
+
const remote = readService(() => this.ctx.remote.session);
|
|
2096
|
+
if (!remote || typeof remote.selectModel !== "function") {
|
|
2097
|
+
this.store.set({ error: "selectModel is unavailable in this build" });
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
const sessionId = this.resolveSessionId();
|
|
2101
|
+
if (!sessionId) {
|
|
2102
|
+
this.store.set({ error: this.t("noSession") });
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
const base = this.selection || {};
|
|
2106
|
+
const currentModel = this.catalog ? findModel(this.catalog, base) : void 0;
|
|
2107
|
+
/* A drag is normally inside the current model's own rungs; resolving
|
|
2108
|
+
anyway keeps a stale level (switch still in flight) from being sent
|
|
2109
|
+
as an explicit unsupported effort. */
|
|
2110
|
+
const mapped = currentModel ? resolveEffort(currentModel, level.id) : { id: level.id, from: level.id };
|
|
2111
|
+
/* Send the explicit triple the shipped selector sends, so the Host never
|
|
2112
|
+
has to infer provider/model from a partial request. */
|
|
2113
|
+
const request = {
|
|
2114
|
+
sessionId,
|
|
2115
|
+
provider: String(base.provider || ""),
|
|
2116
|
+
model: String(base.model || ""),
|
|
2117
|
+
...(mapped.id ? { reasoningEffort: mapped.id } : {})
|
|
2118
|
+
};
|
|
2119
|
+
const token = ++this.token;
|
|
2120
|
+
this.store.set({ busy: true, error: null, notice: null });
|
|
2121
|
+
try {
|
|
2122
|
+
const res = await withTimeout(remote.selectModel(request), SELECT_TIMEOUT_MS);
|
|
2123
|
+
if (token !== this.token) return;
|
|
2124
|
+
if (res && res.ok) {
|
|
2125
|
+
const selected = res.value && res.value.selected;
|
|
2126
|
+
const applied = Object.assign({}, base, selected || {});
|
|
2127
|
+
if (mapped.id) applied.reasoningEffort = mapped.id;
|
|
2128
|
+
else delete applied.reasoningEffort;
|
|
2129
|
+
if (mapped.id) this.lastEffort = mapped.id;
|
|
2130
|
+
this.selection = Object.assign({}, applied);
|
|
2131
|
+
/* The Host appends a `model/selection` event, so the projection now
|
|
2132
|
+
reports this choice; re-read it to confirm and pick up names. */
|
|
2133
|
+
this.store.set({
|
|
2134
|
+
busy: false,
|
|
2135
|
+
error: null,
|
|
2136
|
+
modelId: String(applied.model || ""),
|
|
2137
|
+
provider: String(applied.provider || "")
|
|
2138
|
+
});
|
|
2139
|
+
this.refresh();
|
|
2140
|
+
} else {
|
|
2141
|
+
this.store.set({ busy: false, error: messageOf(res && res.error) || "refused" });
|
|
2142
|
+
}
|
|
2143
|
+
} catch (err) {
|
|
2144
|
+
if (token !== this.token) return;
|
|
2145
|
+
this.store.set({ busy: false, error: messageOf(err) });
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
/* ------------------------------------------------------------------ *
|
|
2151
|
+
* components
|
|
2152
|
+
* ------------------------------------------------------------------ */
|
|
2153
|
+
|
|
2154
|
+
function chevronGlyph() {
|
|
2155
|
+
return h(
|
|
2156
|
+
"svg",
|
|
2157
|
+
{ width: 11, height: 11, viewBox: "0 0 24 24", "aria-hidden": "true", style: { display: "block" } },
|
|
2158
|
+
h("path", {
|
|
2159
|
+
d: "M6 9l6 6 6-6",
|
|
2160
|
+
fill: "none",
|
|
2161
|
+
stroke: "currentColor",
|
|
2162
|
+
strokeWidth: 2.2,
|
|
2163
|
+
strokeLinecap: "round",
|
|
2164
|
+
strokeLinejoin: "round"
|
|
2165
|
+
})
|
|
2166
|
+
);
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
/**
|
|
2170
|
+
* The whole control lives in the composer's model cell: a trigger that
|
|
2171
|
+
* reads "model · effort" plus a popup that carries the model list and the
|
|
2172
|
+
* glowing effort rail.
|
|
2173
|
+
*/
|
|
2174
|
+
function ModelEffortControl(props) {
|
|
2175
|
+
const snap = react.useSyncExternalStore(props.store.subscribe, props.store.getSnapshot);
|
|
2176
|
+
const sid = props.sessionId;
|
|
2177
|
+
react.useEffect(() => {
|
|
2178
|
+
if (sid && typeof props.setSession === "function") props.setSession(sid);
|
|
2179
|
+
}, [sid]);
|
|
2180
|
+
|
|
2181
|
+
const triggerRef = react.useRef(null);
|
|
2182
|
+
const menuRef = react.useRef(null);
|
|
2183
|
+
const trackRef = react.useRef(null);
|
|
2184
|
+
const bgRef = react.useRef(null);
|
|
2185
|
+
const railRef = react.useRef(null);
|
|
2186
|
+
const fxRef = react.useRef(null);
|
|
2187
|
+
const flashRef = react.useRef(null);
|
|
2188
|
+
const bigRef = react.useRef(null);
|
|
2189
|
+
const engineRef = react.useRef(null);
|
|
2190
|
+
const levelsRef = react.useRef(snap.levels);
|
|
2191
|
+
levelsRef.current = snap.levels;
|
|
2192
|
+
|
|
2193
|
+
const posState = react.useState(null);
|
|
2194
|
+
const pos = posState[0];
|
|
2195
|
+
const setPos = posState[1];
|
|
2196
|
+
|
|
2197
|
+
if (engineRef.current === null) engineRef.current = createEngine();
|
|
2198
|
+
|
|
2199
|
+
/* The glow that used to sit on the pill now wraps the composer card. */
|
|
2200
|
+
react.useEffect(() => {
|
|
2201
|
+
const halo = haloLayer();
|
|
2202
|
+
halo.retain();
|
|
2203
|
+
return () => halo.release();
|
|
2204
|
+
}, []);
|
|
2205
|
+
react.useEffect(() => {
|
|
2206
|
+
const halo = haloLayer();
|
|
2207
|
+
const count = snap.levels.length;
|
|
2208
|
+
if (!snap.halo || !snap.ready || !count) {
|
|
2209
|
+
/* Hide, but keep the mount counted: release() belongs to unmount. */
|
|
2210
|
+
halo.place(null, { hue: 0, heat: 0, open: false, hot: false });
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
const heat = count > 1 ? snap.index / (count - 1) : 0;
|
|
2214
|
+
halo.place(
|
|
2215
|
+
composerBox(triggerRef.current),
|
|
2216
|
+
{
|
|
2217
|
+
hue: Math.round(hueAtT(snap.t)),
|
|
2218
|
+
heat: heat,
|
|
2219
|
+
open: snap.open,
|
|
2220
|
+
hot: heat >= 0.85
|
|
2221
|
+
},
|
|
2222
|
+
triggerRef.current
|
|
2223
|
+
);
|
|
2224
|
+
}, [snap.halo, snap.ready, snap.index, snap.levels.length, snap.open, snap.t]);
|
|
2225
|
+
|
|
2226
|
+
/* Anchor the popup above the trigger, clamped into the viewport. */
|
|
2227
|
+
react.useEffect(() => {
|
|
2228
|
+
if (!snap.open) {
|
|
2229
|
+
setPos(null);
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
const place = () => {
|
|
2233
|
+
const el = triggerRef.current;
|
|
2234
|
+
if (!el || typeof el.getBoundingClientRect !== "function") return;
|
|
2235
|
+
const rect = el.getBoundingClientRect();
|
|
2236
|
+
/* A detached or hidden trigger reports an all-zero box; keep the last good spot. */
|
|
2237
|
+
if (rect.width < 1 && rect.height < 1) return;
|
|
2238
|
+
const vw = window.innerWidth || 1024;
|
|
2239
|
+
const vh = window.innerHeight || 768;
|
|
2240
|
+
const width = Math.min(400, Math.max(240, vw - 24));
|
|
2241
|
+
/* Right-align the popup to the trigger, the way a composer menu reads. */
|
|
2242
|
+
const left = Math.max(8, Math.min(rect.right - width, vw - width - 8));
|
|
2243
|
+
/*
|
|
2244
|
+
* Open upward by default (the trigger sits in the composer, near the
|
|
2245
|
+
* bottom). On a short viewport — a landscape phone, a small window —
|
|
2246
|
+
* there may be almost no room above, so fall back to opening downward
|
|
2247
|
+
* and cap the height to the space that is actually available. Without
|
|
2248
|
+
* this the popup ran off the top of the screen.
|
|
2249
|
+
*/
|
|
2250
|
+
const gap = 8;
|
|
2251
|
+
const spaceAbove = rect.top - gap - 8;
|
|
2252
|
+
const spaceBelow = vh - rect.bottom - gap - 8;
|
|
2253
|
+
const up = spaceAbove >= 220 || spaceAbove >= spaceBelow;
|
|
2254
|
+
const room = Math.max(140, up ? spaceAbove : spaceBelow);
|
|
2255
|
+
setPos(
|
|
2256
|
+
up
|
|
2257
|
+
? { left, width, bottom: vh - rect.top + gap, maxHeight: room, top: null }
|
|
2258
|
+
: { left, width, top: rect.bottom + gap, maxHeight: room, bottom: null }
|
|
2259
|
+
);
|
|
2260
|
+
};
|
|
2261
|
+
place();
|
|
2262
|
+
window.addEventListener("resize", place);
|
|
2263
|
+
window.addEventListener("scroll", place, true);
|
|
2264
|
+
return () => {
|
|
2265
|
+
window.removeEventListener("resize", place);
|
|
2266
|
+
window.removeEventListener("scroll", place, true);
|
|
2267
|
+
};
|
|
2268
|
+
}, [snap.open]);
|
|
2269
|
+
|
|
2270
|
+
const ready = snap.open && pos !== null;
|
|
2271
|
+
|
|
2272
|
+
react.useEffect(() => {
|
|
2273
|
+
if (!ready) return;
|
|
2274
|
+
const engine = engineRef.current;
|
|
2275
|
+
if (!engine) return;
|
|
2276
|
+
const reduce =
|
|
2277
|
+
typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
2278
|
+
engine.mount({
|
|
2279
|
+
els: {
|
|
2280
|
+
stage: menuRef.current,
|
|
2281
|
+
track: trackRef.current,
|
|
2282
|
+
bg: bgRef.current,
|
|
2283
|
+
rail: railRef.current,
|
|
2284
|
+
fx: fxRef.current,
|
|
2285
|
+
flash: flashRef.current,
|
|
2286
|
+
big: bigRef.current
|
|
2287
|
+
},
|
|
2288
|
+
count: levelsRef.current.length,
|
|
2289
|
+
texts: { boom: props.t("boom"), after: props.t("afterBoom") },
|
|
2290
|
+
reduce,
|
|
2291
|
+
onLevel: (i) => props.setIndex(i),
|
|
2292
|
+
onRelease: (i) => props.commit(i)
|
|
2293
|
+
});
|
|
2294
|
+
engine.setIndex(snap.index, true);
|
|
2295
|
+
return () => engine.unmount();
|
|
2296
|
+
}, [ready]);
|
|
2297
|
+
|
|
2298
|
+
react.useEffect(() => {
|
|
2299
|
+
const engine = engineRef.current;
|
|
2300
|
+
if (!engine || !ready || !engine.isMounted()) return;
|
|
2301
|
+
engine.setCount(snap.levels.length);
|
|
2302
|
+
if (!engine.isDragging()) engine.setIndex(snap.index, true);
|
|
2303
|
+
}, [snap.levels.length]);
|
|
2304
|
+
|
|
2305
|
+
/* Dismiss on an outside press or Escape. */
|
|
2306
|
+
react.useEffect(() => {
|
|
2307
|
+
if (!snap.open) return;
|
|
2308
|
+
const onDown = (ev) => {
|
|
2309
|
+
const target = ev.target;
|
|
2310
|
+
const menu = menuRef.current;
|
|
2311
|
+
const trigger = triggerRef.current;
|
|
2312
|
+
if (menu && typeof menu.contains === "function" && menu.contains(target)) return;
|
|
2313
|
+
if (trigger && typeof trigger.contains === "function" && trigger.contains(target)) return;
|
|
2314
|
+
props.close();
|
|
2315
|
+
};
|
|
2316
|
+
const onEsc = (ev) => {
|
|
2317
|
+
if (ev.key === "Escape") props.close();
|
|
2318
|
+
};
|
|
2319
|
+
document.addEventListener("pointerdown", onDown, true);
|
|
2320
|
+
document.addEventListener("keydown", onEsc, true);
|
|
2321
|
+
return () => {
|
|
2322
|
+
document.removeEventListener("pointerdown", onDown, true);
|
|
2323
|
+
document.removeEventListener("keydown", onEsc, true);
|
|
2324
|
+
};
|
|
2325
|
+
}, [snap.open]);
|
|
2326
|
+
|
|
2327
|
+
const hue = Math.round(hueAtT(snap.t));
|
|
2328
|
+
const current = snap.levels[snap.index];
|
|
2329
|
+
const n = snap.levels.length;
|
|
2330
|
+
const modelText = snap.modelId || snap.modelName || "";
|
|
2331
|
+
const effortText = current ? current.name : "";
|
|
2332
|
+
const locked = props.locked === true;
|
|
2333
|
+
const summary = props.t("triggerHint") + (modelText ? " — " + modelText : "") + (effortText ? " · " + effortText : "") + (snap.error ? " · " + snap.error : "");
|
|
2334
|
+
|
|
2335
|
+
const trigger = h(
|
|
2336
|
+
"button",
|
|
2337
|
+
{
|
|
2338
|
+
ref: triggerRef,
|
|
2339
|
+
key: "trigger",
|
|
2340
|
+
type: "button",
|
|
2341
|
+
className: "es-trigger",
|
|
2342
|
+
title: summary,
|
|
2343
|
+
"aria-label": summary,
|
|
2344
|
+
"aria-haspopup": "dialog",
|
|
2345
|
+
"aria-expanded": snap.open ? "true" : "false",
|
|
2346
|
+
"data-open": snap.open ? "1" : "0",
|
|
2347
|
+
"data-locked": locked ? "1" : "0",
|
|
2348
|
+
"data-halo": snap.halo ? "1" : "0",
|
|
2349
|
+
disabled: locked,
|
|
2350
|
+
style: { "--es-hue": String(hue), "--es-e": snap.t.toFixed(3) },
|
|
2351
|
+
onClick: () => props.toggle()
|
|
2352
|
+
},
|
|
2353
|
+
h("span", { className: "es-trigger-bolt", key: "b" }, boltGlyph(14, hue)),
|
|
2354
|
+
h("span", { className: "es-trigger-text", key: "m" }, safeLabel(modelText) || props.t("modelLabel")),
|
|
2355
|
+
effortText ? h("span", { className: "es-trigger-sep", key: "s" }, "·") : null,
|
|
2356
|
+
effortText ? h("span", { className: "es-trigger-effort", key: "e" }, safeLabel(effortText)) : null,
|
|
2357
|
+
h("span", { className: "es-trigger-chev", key: "c" }, chevronGlyph())
|
|
2358
|
+
);
|
|
2359
|
+
|
|
2360
|
+
if (!ready) return trigger;
|
|
2361
|
+
|
|
2362
|
+
const footText = snap.error
|
|
2363
|
+
? props.t("failed", { message: snap.error })
|
|
2364
|
+
: snap.busy
|
|
2365
|
+
? props.t("switching")
|
|
2366
|
+
: snap.notice
|
|
2367
|
+
? snap.notice
|
|
2368
|
+
: !snap.supported && snap.ready
|
|
2369
|
+
? props.t("noReasoning")
|
|
2370
|
+
: current && current.description
|
|
2371
|
+
? current.description
|
|
2372
|
+
: props.t("hint");
|
|
2373
|
+
const footNote = !snap.error && !snap.busy && !!snap.notice;
|
|
2374
|
+
|
|
2375
|
+
const modelNodes = [];
|
|
2376
|
+
snap.models.forEach((group) => {
|
|
2377
|
+
if (group.name) modelNodes.push(h("div", { key: "gh-" + group.id, className: "es-models-group" }, safeLabel(group.name)));
|
|
2378
|
+
group.models.forEach((entry) => {
|
|
2379
|
+
const on = entry.id === snap.modelId && group.id === snap.provider;
|
|
2380
|
+
modelNodes.push(
|
|
2381
|
+
h(
|
|
2382
|
+
"button",
|
|
2383
|
+
{
|
|
2384
|
+
key: "mi-" + group.id + "-" + entry.id,
|
|
2385
|
+
type: "button",
|
|
2386
|
+
className: "es-model-item",
|
|
2387
|
+
"data-on": on ? "1" : "0",
|
|
2388
|
+
title: entry.description || entry.name,
|
|
2389
|
+
onClick: () => props.chooseModel(group.id, entry.id)
|
|
2390
|
+
},
|
|
2391
|
+
h("span", { className: "es-model-item-name", key: "n" }, safeLabel(entry.name)),
|
|
2392
|
+
entry.efforts === 0 ? h("span", { className: "es-model-item-tag", key: "tag" }, props.t("noLevelsTag")) : null
|
|
2393
|
+
)
|
|
2394
|
+
);
|
|
2395
|
+
});
|
|
2396
|
+
});
|
|
2397
|
+
|
|
2398
|
+
const menu = h(
|
|
2399
|
+
"div",
|
|
2400
|
+
{
|
|
2401
|
+
ref: menuRef,
|
|
2402
|
+
key: "menu",
|
|
2403
|
+
className: "es-menu",
|
|
2404
|
+
role: "dialog",
|
|
2405
|
+
"aria-label": props.t("panelTitle"),
|
|
2406
|
+
style: {
|
|
2407
|
+
left: pos.left + "px",
|
|
2408
|
+
...(pos.top === null || pos.top === void 0 ? {} : { top: pos.top + "px" }),
|
|
2409
|
+
...(pos.bottom === null || pos.bottom === void 0 ? {} : { bottom: pos.bottom + "px" }),
|
|
2410
|
+
maxHeight: pos.maxHeight + "px",
|
|
2411
|
+
width: pos.width + "px",
|
|
2412
|
+
"--es-hue": String(hue),
|
|
2413
|
+
"--es-t": snap.t.toFixed(4),
|
|
2414
|
+
"--es-e": snap.t.toFixed(3)
|
|
2415
|
+
}
|
|
2416
|
+
},
|
|
2417
|
+
h("canvas", { className: "es-menu-bg", key: "bg", ref: bgRef }),
|
|
2418
|
+
h("div", { className: "es-menu-vig", key: "vig" }),
|
|
2419
|
+
h(
|
|
2420
|
+
"div",
|
|
2421
|
+
{ className: "es-menu-body", key: "body" },
|
|
2422
|
+
h(
|
|
2423
|
+
"button",
|
|
2424
|
+
{
|
|
2425
|
+
className: "es-menu-row",
|
|
2426
|
+
key: "row",
|
|
2427
|
+
type: "button",
|
|
2428
|
+
"data-open": snap.listOpen ? "1" : "0",
|
|
2429
|
+
"aria-expanded": snap.listOpen ? "true" : "false",
|
|
2430
|
+
onClick: () => props.toggleModels()
|
|
2431
|
+
},
|
|
2432
|
+
h("span", { className: "es-menu-row-label", key: "l" }, props.t("modelLabel")),
|
|
2433
|
+
h("span", { className: "es-menu-row-value", key: "v", title: modelText }, safeLabel(modelText) || props.t("noModels")),
|
|
2434
|
+
h("span", { className: "es-menu-chev", key: "c" }, chevronGlyph())
|
|
2435
|
+
),
|
|
2436
|
+
snap.listOpen
|
|
2437
|
+
? h(
|
|
2438
|
+
"div",
|
|
2439
|
+
{ className: "es-models", key: "models" },
|
|
2440
|
+
modelNodes.length
|
|
2441
|
+
? modelNodes
|
|
2442
|
+
: h("div", { className: "es-models-group", key: "none" }, props.t("noModels"))
|
|
2443
|
+
)
|
|
2444
|
+
: null,
|
|
2445
|
+
h("div", { className: "es-menu-sep", key: "sep" }),
|
|
2446
|
+
h(
|
|
2447
|
+
"div",
|
|
2448
|
+
{ className: "es-effort-head", key: "eh" },
|
|
2449
|
+
h("span", { className: "es-head-glyph", key: "g" }, boltGlyph(13, hue)),
|
|
2450
|
+
h("span", { key: "t" }, props.t("effortLabel")),
|
|
2451
|
+
current ? h("span", { className: "es-tag", key: "tag" }, current.name) : null
|
|
2452
|
+
),
|
|
2453
|
+
h(
|
|
2454
|
+
"div",
|
|
2455
|
+
{ className: "es-track", key: "track", ref: trackRef, onPointerDown: (ev) => engineRef.current.beginDrag(ev) },
|
|
2456
|
+
h("canvas", { className: "es-rail", key: "rail", ref: railRef }),
|
|
2457
|
+
h(
|
|
2458
|
+
"div",
|
|
2459
|
+
{ className: "es-ticks", key: "ticks" },
|
|
2460
|
+
snap.levels.map((lv, i) =>
|
|
2461
|
+
h("span", {
|
|
2462
|
+
key: "tick-" + lv.id,
|
|
2463
|
+
className: "es-tick",
|
|
2464
|
+
"data-on": i <= snap.index ? "1" : "0",
|
|
2465
|
+
style: { "--i": n > 1 ? (i / (n - 1)).toFixed(4) : "0" }
|
|
2466
|
+
})
|
|
2467
|
+
)
|
|
2468
|
+
),
|
|
2469
|
+
h("div", { className: "es-thumb", key: "thumb" })
|
|
2470
|
+
),
|
|
2471
|
+
h(
|
|
2472
|
+
"div",
|
|
2473
|
+
{ className: "es-scale", key: "scale" },
|
|
2474
|
+
snap.levels.map((lv, i) =>
|
|
2475
|
+
h(
|
|
2476
|
+
"button",
|
|
2477
|
+
{
|
|
2478
|
+
key: "sc-" + lv.id,
|
|
2479
|
+
type: "button",
|
|
2480
|
+
className: "es-scale-item",
|
|
2481
|
+
"data-on": i === snap.index ? "1" : "0",
|
|
2482
|
+
style: { left: "calc(31px + (100% - 62px) * " + (n > 1 ? (i / (n - 1)).toFixed(4) : "0") + ")" },
|
|
2483
|
+
title: lv.description || lv.name,
|
|
2484
|
+
onClick: () => props.commit(i)
|
|
2485
|
+
},
|
|
2486
|
+
lv.name
|
|
2487
|
+
)
|
|
2488
|
+
)
|
|
2489
|
+
),
|
|
2490
|
+
h(
|
|
2491
|
+
"div",
|
|
2492
|
+
{ className: "es-opt", key: "opt" },
|
|
2493
|
+
h("span", { key: "l" }, props.t("haloLabel")),
|
|
2494
|
+
h(
|
|
2495
|
+
"button",
|
|
2496
|
+
{
|
|
2497
|
+
type: "button",
|
|
2498
|
+
role: "switch",
|
|
2499
|
+
className: "es-switch",
|
|
2500
|
+
title: props.t("haloTitle"),
|
|
2501
|
+
"aria-label": props.t("haloLabel"),
|
|
2502
|
+
"aria-checked": snap.halo ? "true" : "false",
|
|
2503
|
+
"data-on": snap.halo ? "1" : "0",
|
|
2504
|
+
onClick: () => props.toggleHalo()
|
|
2505
|
+
},
|
|
2506
|
+
h("span", { className: "es-switch-knob", key: "k" })
|
|
2507
|
+
)
|
|
2508
|
+
),
|
|
2509
|
+
h("div", { className: "es-foot", key: "foot", "data-err": snap.error ? "1" : "0", "data-note": footNote ? "1" : "0" }, footText)
|
|
2510
|
+
),
|
|
2511
|
+
h("canvas", { className: "es-fx", key: "fx", ref: fxRef }),
|
|
2512
|
+
h("div", { className: "es-flash", key: "flash", ref: flashRef }),
|
|
2513
|
+
h("div", { className: "es-big", key: "big", ref: bigRef })
|
|
2514
|
+
);
|
|
2515
|
+
|
|
2516
|
+
/* The popup is portalled to <body>: the composer row can sit inside a
|
|
2517
|
+
transformed or overflow-clipped ancestor, which would otherwise move
|
|
2518
|
+
or cut a position:fixed child. */
|
|
2519
|
+
const portal =
|
|
2520
|
+
reactDom && typeof reactDom.createPortal === "function" && typeof document !== "undefined"
|
|
2521
|
+
? reactDom.createPortal(menu, document.body)
|
|
2522
|
+
: menu;
|
|
2523
|
+
|
|
2524
|
+
return h("span", { className: "es-anchor", key: "anchor" }, trigger, portal);
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
/* ------------------------------------------------------------------ *
|
|
2528
|
+
* module surface
|
|
2529
|
+
* ------------------------------------------------------------------ */
|
|
2530
|
+
|
|
2531
|
+
/**
|
|
2532
|
+
* BOTH `remote` and `remote.session` are required: Cordis validates the
|
|
2533
|
+
* nested property access (`ctx.get("remote").session`) against this list, and
|
|
2534
|
+
* a missing entry throws `cannot get property "remote.session" without
|
|
2535
|
+
* inject` — which, from a render path, blanks the panel.
|
|
2536
|
+
*/
|
|
2537
|
+
const inject = ["slots", "locale", "sessions", "remote", "remote.session"];
|
|
2538
|
+
|
|
2539
|
+
function apply(ctx) {
|
|
2540
|
+
injectCss();
|
|
2541
|
+
|
|
2542
|
+
ctx.effect(
|
|
2543
|
+
() => {
|
|
2544
|
+
try {
|
|
2545
|
+
return ctx.locale.register(NS, { zh, en });
|
|
2546
|
+
} catch (err) {
|
|
2547
|
+
return () => {};
|
|
2548
|
+
}
|
|
2549
|
+
},
|
|
2550
|
+
"effort-slider: dictionaries"
|
|
2551
|
+
);
|
|
2552
|
+
|
|
2553
|
+
const controller = new EffortController(ctx);
|
|
2554
|
+
controller.bindLocale();
|
|
2555
|
+
|
|
2556
|
+
ctx.effect(() => () => controller.dispose(), "effort-slider: controller");
|
|
2557
|
+
|
|
2558
|
+
/* Take over the shipped model cell so model + reasoning effort are one
|
|
2559
|
+
control. A single slot renders the LOWEST priority first
|
|
2560
|
+
(`entries` is sorted ascending by priority, and the renderer takes
|
|
2561
|
+
`entriesOfSlot(key)[0]`), so the shipped occupant (priority 0) is
|
|
2562
|
+
shadowed only by a negative priority. */
|
|
2563
|
+
ctx.slots.inject("conversation.input.model", () => {
|
|
2564
|
+
try {
|
|
2565
|
+
const unregister = ctx.slots.register(
|
|
2566
|
+
{
|
|
2567
|
+
name: "conversation.input.model",
|
|
2568
|
+
priority: -1,
|
|
2569
|
+
label: () => "Model and reasoning effort",
|
|
2570
|
+
inject: () => controller.face()
|
|
2571
|
+
},
|
|
2572
|
+
ModelEffortControl
|
|
2573
|
+
);
|
|
2574
|
+
return () => {
|
|
2575
|
+
unregister();
|
|
2576
|
+
};
|
|
2577
|
+
} catch (err) {
|
|
2578
|
+
return () => {};
|
|
2579
|
+
}
|
|
2580
|
+
});
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
exports.name = "@lolannn/dsh-effort-slider";
|
|
2584
|
+
exports.inject = inject;
|
|
2585
|
+
exports.apply = apply;
|
|
2586
|
+
|
|
2587
|
+
return module.exports;
|
|
2588
|
+
}
|
|
2589
|
+
});
|