packing-bingo 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -25
- package/dist/{chunk-ZPZZDZUH.js → chunk-A5JVNVWS.js} +654 -115
- package/dist/core.cjs +658 -117
- package/dist/core.d.cts +134 -6
- package/dist/core.d.ts +134 -6
- package/dist/core.js +7 -3
- package/dist/index.cjs +675 -123
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +24 -9
- package/dist/packing-bingo.global.js +119 -41
- package/package.json +1 -1
package/dist/core.cjs
CHANGED
|
@@ -26,7 +26,9 @@ __export(core_exports, {
|
|
|
26
26
|
bingoLines: () => bingoLines,
|
|
27
27
|
createPackingBingo: () => createPackingBingo,
|
|
28
28
|
en: () => en,
|
|
29
|
-
|
|
29
|
+
formatTime: () => formatTime,
|
|
30
|
+
ja: () => ja,
|
|
31
|
+
speedBonus: () => speedBonus
|
|
30
32
|
});
|
|
31
33
|
module.exports = __toCommonJS(core_exports);
|
|
32
34
|
|
|
@@ -133,11 +135,27 @@ var ja = {
|
|
|
133
135
|
shuffle: "\u{1F500} \u30B7\u30E3\u30C3\u30D5\u30EB",
|
|
134
136
|
soundOn: "\u{1F50A} \u97F3\u3042\u308A",
|
|
135
137
|
soundOff: "\u{1F507} \u97F3\u306A\u3057",
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
readyTitle: "Are you ready?",
|
|
139
|
+
readyPrompt: "",
|
|
140
|
+
soundHint: "\u7AEF\u672B\u306E\u97F3\u91CF\u3092MAX\u306B",
|
|
141
|
+
vibrationHint: "\u304A\u3084\u3059\u307F\u30E2\u30FC\u30C9\u3092\u30AA\u30D5",
|
|
142
|
+
readyTap: "\u30BF\u30C3\u30D7\u3067\u30B9\u30BF\u30FC\u30C8",
|
|
143
|
+
readyGo: "START!",
|
|
144
|
+
time: "\u30BF\u30A4\u30E0",
|
|
145
|
+
statTime: "\u30AF\u30EA\u30A2\u30BF\u30A4\u30E0",
|
|
146
|
+
modeLabel: "\u30E2\u30FC\u30C9",
|
|
147
|
+
modeNormal: "\u901A\u5E38",
|
|
148
|
+
modeInfinite: "\u221E \u7121\u9650",
|
|
149
|
+
normalTitle: "NORMAL",
|
|
150
|
+
normalSub: "\u901A\u5E38\u30E2\u30FC\u30C9",
|
|
151
|
+
infiniteTitle: "INFINITE",
|
|
152
|
+
infiniteSub: "\u7121\u9650\u30D3\u30F3\u30B4",
|
|
153
|
+
infiniteRule: "\u305D\u308D\u3063\u305F\u5217\u306F\u6D88\u3048\u3066\u3001\u307E\u305F\u57CB\u3081\u3089\u308C\u308B\u3002\u4F55\u5EA6\u3067\u3082\u30D3\u30F3\u30B4\uFF01",
|
|
154
|
+
finish: "\u{1F3C1} FINISH",
|
|
155
|
+
finishTitle: "\u30D5\u30A3\u30CB\u30C3\u30B7\u30E5\uFF01",
|
|
156
|
+
finishStamp: "\u221E",
|
|
157
|
+
finishTime: "\u30D7\u30EC\u30A4\u30BF\u30A4\u30E0",
|
|
158
|
+
again: "\u3082\u3046\u4E00\u5EA6",
|
|
141
159
|
stamp: "OK",
|
|
142
160
|
free: "FREE",
|
|
143
161
|
reachBadge: "\u30EA\u30FC\u30C1",
|
|
@@ -148,15 +166,15 @@ var ja = {
|
|
|
148
166
|
bannerNth: (n) => `${n} \u30E9\u30A4\u30F3\u76EE\uFF01`,
|
|
149
167
|
bannerMulti: (n) => `\u4E00\u6C17\u306B ${n} \u30E9\u30A4\u30F3\uFF01`,
|
|
150
168
|
statusStart: () => "",
|
|
151
|
-
statusLines: (
|
|
169
|
+
statusLines: () => "",
|
|
152
170
|
statusReach: () => "",
|
|
153
|
-
statusDone: "
|
|
171
|
+
statusDone: "",
|
|
154
172
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
155
173
|
perfectStamp: "\u6E96\u5099\n\u5B8C\u4E86",
|
|
156
174
|
perfectTitle: "\u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01",
|
|
157
|
-
perfectBody:
|
|
158
|
-
\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u3001\u3044\u3063\u3066\u3089\u3063\u3057\u3083\u3044\uFF01`,
|
|
175
|
+
perfectBody: "",
|
|
159
176
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
177
|
+
speedBonus: (n) => `SPEED BONUS +${n.toLocaleString()}`,
|
|
160
178
|
statScore: "SCORE",
|
|
161
179
|
statCombo: "MAX COMBO",
|
|
162
180
|
statLines: "BINGO",
|
|
@@ -179,11 +197,27 @@ var en = {
|
|
|
179
197
|
shuffle: "\u{1F500} Shuffle",
|
|
180
198
|
soundOn: "\u{1F50A} Sound on",
|
|
181
199
|
soundOff: "\u{1F507} Muted",
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
200
|
+
readyTitle: "Are you ready?",
|
|
201
|
+
readyPrompt: "",
|
|
202
|
+
soundHint: "Turn your volume to MAX",
|
|
203
|
+
vibrationHint: "Turn off Do Not Disturb",
|
|
204
|
+
readyTap: "Tap to start",
|
|
205
|
+
readyGo: "GO!",
|
|
206
|
+
time: "Time",
|
|
207
|
+
statTime: "Time",
|
|
208
|
+
modeLabel: "Mode",
|
|
209
|
+
modeNormal: "Normal",
|
|
210
|
+
modeInfinite: "\u221E Infinite",
|
|
211
|
+
normalTitle: "NORMAL",
|
|
212
|
+
normalSub: "Normal mode",
|
|
213
|
+
infiniteTitle: "INFINITE",
|
|
214
|
+
infiniteSub: "Infinite bingo",
|
|
215
|
+
infiniteRule: "Completed lines clear, so you can fill them again. Bingo forever!",
|
|
216
|
+
finish: "\u{1F3C1} FINISH",
|
|
217
|
+
finishTitle: "Finish!",
|
|
218
|
+
finishStamp: "\u221E",
|
|
219
|
+
finishTime: "Time played",
|
|
220
|
+
again: "Play again",
|
|
187
221
|
stamp: "OK",
|
|
188
222
|
free: "FREE",
|
|
189
223
|
reachBadge: "REACH",
|
|
@@ -194,15 +228,15 @@ var en = {
|
|
|
194
228
|
bannerNth: (n) => `Line ${n}!`,
|
|
195
229
|
bannerMulti: (n) => `${n} lines at once!`,
|
|
196
230
|
statusStart: () => "",
|
|
197
|
-
statusLines: (
|
|
231
|
+
statusLines: () => "",
|
|
198
232
|
statusReach: () => "",
|
|
199
|
-
statusDone: "
|
|
233
|
+
statusDone: "",
|
|
200
234
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
201
235
|
perfectStamp: "ALL\nSET",
|
|
202
236
|
perfectTitle: "Perfect!",
|
|
203
|
-
perfectBody:
|
|
204
|
-
Have a great trip!`,
|
|
237
|
+
perfectBody: "",
|
|
205
238
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
239
|
+
speedBonus: (n) => `SPEED BONUS +${n.toLocaleString()}`,
|
|
206
240
|
statScore: "SCORE",
|
|
207
241
|
statCombo: "MAX COMBO",
|
|
208
242
|
statLines: "BINGO",
|
|
@@ -343,6 +377,36 @@ function createSound(isMuted) {
|
|
|
343
377
|
tone(hz(7), 0.34, 0.12, "triangle", 0.14);
|
|
344
378
|
tone(hz(12), 0.42, 0.2, "triangle", 0.14);
|
|
345
379
|
},
|
|
380
|
+
count(go) {
|
|
381
|
+
if (go) {
|
|
382
|
+
tone(hz(12), 0, 0.5, "square", 0.07);
|
|
383
|
+
tone(hz(24), 0, 0.5, "triangle", 0.14);
|
|
384
|
+
noise(0, 0.2, 0.12, 3e3);
|
|
385
|
+
} else {
|
|
386
|
+
tone(hz(5), 0, 0.14, "square", 0.07);
|
|
387
|
+
tone(hz(17), 0, 0.14, "triangle", 0.12);
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
mode(infinite) {
|
|
391
|
+
if (infinite) {
|
|
392
|
+
tone(196, 0, 0.42, "sine", 0.12, 784);
|
|
393
|
+
[0, 2, 4, 7, 9, 12, 14, 16, 19, 21, 24].forEach((s, i) => tone(hz(s - 12), i * 0.032, 0.16, "triangle", 0.07));
|
|
394
|
+
[12, 16, 19, 24].forEach((s) => tone(hz(s), 0.38, 1.1, "sine", 0.07));
|
|
395
|
+
tone(hz(31), 0.38, 0.9, "triangle", 0.03);
|
|
396
|
+
noise(0, 0.5, 0.07, 6500);
|
|
397
|
+
noise(0.36, 0.7, 0.05, 9e3);
|
|
398
|
+
} else {
|
|
399
|
+
tone(hz(7), 0, 0.1, "square", 0.05);
|
|
400
|
+
tone(hz(0), 0.1, 0.28, "triangle", 0.14);
|
|
401
|
+
tone(160, 0.1, 0.2, "sine", 0.26, 55);
|
|
402
|
+
noise(0.1, 0.12, 0.14, 1400);
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
clear(squares) {
|
|
406
|
+
const n = Math.max(1, Math.min(8, squares));
|
|
407
|
+
for (let i = 0; i < n; i++) tone(step(SCALE.length - 1 - i * 2), i * 0.035, 0.18, "sine", 0.07);
|
|
408
|
+
noise(0, 0.3, 0.08, 7e3);
|
|
409
|
+
},
|
|
346
410
|
stop,
|
|
347
411
|
close() {
|
|
348
412
|
stop();
|
|
@@ -385,7 +449,10 @@ function createHaptics(enabled) {
|
|
|
385
449
|
const current = ++generation;
|
|
386
450
|
const tier = Math.min(3, Math.floor(combo / 3));
|
|
387
451
|
const lead = Math.max(80, impactAt - 40);
|
|
388
|
-
const pattern = moment === "off" ? 35 : moment === "perfect" ? [160, 45, 100] : moment === "bingo" ? lines >= 3 ? [lead, 40, 500, 50, 650, 50, 850] : lines === 2 ? [lead, 40, 500, 50, 650] : [lead, 40, 500] : moment === "reach" ? [140, 40, 240] : moment === "shuffle" ? [90, 35, 90, 35, 220] :
|
|
452
|
+
const pattern = moment === "off" ? 35 : moment === "perfect" ? [160, 45, 100] : moment === "bingo" ? lines >= 3 ? [lead, 40, 500, 50, 650, 50, 850] : lines === 2 ? [lead, 40, 500, 50, 650] : [lead, 40, 500] : moment === "reach" ? [140, 40, 240] : moment === "shuffle" ? [90, 35, 90, 35, 220] : moment === "count" ? 60 : moment === "go" ? 260 : (
|
|
453
|
+
// Switching modes: a quick rising flutter into a hold, or one firm stamp.
|
|
454
|
+
moment === "infinite" ? [25, 30, 35, 30, 45, 30, 220] : moment === "normal" ? [50, 70, 160] : moment === "finish" ? [120, 40, 120, 40, 320] : tier ? [100 + tier * 20, 15, 80 + tier * 20] : 90
|
|
455
|
+
);
|
|
389
456
|
if (!send(pattern)) return;
|
|
390
457
|
return (beat) => {
|
|
391
458
|
if (generation === current && owner === token) send([...beat]);
|
|
@@ -436,24 +503,17 @@ var CSS = `
|
|
|
436
503
|
.pkb-board.pkb-punch{animation:pkb-punch .4s cubic-bezier(.3,1.6,.5,1)}
|
|
437
504
|
@keyframes pkb-punch{30%{transform:scale(1.018)}}
|
|
438
505
|
.pkb-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px 16px;margin-bottom:18px}
|
|
439
|
-
.pkb-
|
|
440
|
-
.pkb-play-dialog:not([open]){display:none}
|
|
441
|
-
.pkb-play-dialog::backdrop{background:rgba(42,31,20,.6)}
|
|
442
|
-
.pkb-play-dialog h3{margin-bottom:16px;font-size:22px;font-weight:800;line-height:1.4}
|
|
443
|
-
.pkb-play-dialog p{margin-bottom:12px;font-size:15px;font-weight:700;line-height:1.7;overflow-wrap:anywhere}
|
|
444
|
-
.pkb-play-actions{display:grid;gap:10px;margin-top:22px}
|
|
445
|
-
.pkb-play-dialog button{padding:12px 16px;border:2px solid var(--pk-ink);border-radius:12px;color:var(--pk-ink);font-size:15px;font-weight:800;line-height:1.5;cursor:pointer}
|
|
446
|
-
.pkb-play-start{background:var(--pk-sun)}
|
|
447
|
-
.pkb-play-skip{background:transparent}
|
|
448
|
-
.pkb-play-dialog button:focus-visible{outline:3px solid var(--pk-ink);outline-offset:3px}
|
|
449
|
-
.pkb-play-dialog button:hover{filter:brightness(.96)}
|
|
450
|
-
.pkb-tags{display:flex;gap:8px;align-items:center}
|
|
506
|
+
.pkb-tags{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
|
|
451
507
|
.pkb-kicker{display:inline-block;background:var(--pk-ink);color:var(--pk-sun);font-weight:800;font-size:11px;letter-spacing:.14em;padding:4px 10px;border-radius:999px}
|
|
452
508
|
.pkb-kicker:empty{display:none}
|
|
453
509
|
.pkb-kicker.pkb-gold{color:#fff;background:linear-gradient(90deg,#E8473B,#FF9F1C,#1FA971,#2F6FED,#E8473B);background-size:200% 100%;animation:pkb-rb 2s linear infinite}
|
|
454
510
|
@keyframes pkb-rb{to{background-position:-200% 0}}
|
|
455
511
|
.pkb button{font-family:var(--pk-body)}
|
|
456
512
|
.pkb-snd{appearance:none;border:2px solid var(--pk-ink);background:var(--pk-paper);border-radius:999px;padding:2px 9px;font-weight:800;font-size:11px;line-height:1.4;color:var(--pk-ink);cursor:pointer}
|
|
513
|
+
.pkb-watch{display:inline-block;min-width:5.4em;padding:3px 10px 2px;border-radius:999px;background:var(--pk-ink);color:var(--pk-paper);font-family:var(--pk-display);font-size:13px;line-height:1.3;text-align:center;font-variant-numeric:tabular-nums;letter-spacing:.03em}
|
|
514
|
+
.pkb-watch::before{content:"\u23F1 ";font-family:var(--pk-body)}
|
|
515
|
+
.pkb-watch.pkb-run{color:var(--pk-sun)}
|
|
516
|
+
.pkb-watch.pkb-done{background:var(--pk-red);color:#fff}
|
|
457
517
|
.pkb-title{font-family:var(--pk-display);font-weight:400;font-size:34px;line-height:1.15;margin-top:8px;letter-spacing:.02em;color:inherit}
|
|
458
518
|
.pkb-sub{font-weight:800;font-size:13px;margin-top:4px;word-break:auto-phrase}
|
|
459
519
|
.pkb-side{display:flex;flex-direction:column;gap:8px;flex:none}
|
|
@@ -476,7 +536,7 @@ var CSS = `
|
|
|
476
536
|
.pkb-gw{position:relative}
|
|
477
537
|
.pkb-grid{display:grid;grid-template-columns:repeat(var(--pk-n),minmax(0,1fr));gap:calc(12px*var(--pk-d))}
|
|
478
538
|
.pkb-tile{position:relative;min-width:0;place-self:stretch;aspect-ratio:1/.82;background:var(--pk-paper);border:2.5px solid var(--pk-ink);border-radius:calc(16px*var(--pk-d));box-shadow:4px 4px 0 var(--pk-ink);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:calc(6px*var(--pk-d));cursor:pointer;user-select:none;-webkit-tap-highlight-color:transparent;transition:transform .1s,box-shadow .1s,background-color .25s}
|
|
479
|
-
.pkb-tile:active:not(.pkb-free){transform:translate(3px,3px);box-shadow:1px 1px 0 var(--pk-ink)}
|
|
539
|
+
.pkb-tile:active:not(.pkb-free):not(.pkb-lock){transform:translate(3px,3px);box-shadow:1px 1px 0 var(--pk-ink)}
|
|
480
540
|
.pkb-tile input{position:absolute;opacity:0;pointer-events:none;width:1px;height:1px}
|
|
481
541
|
.pkb-tile:has(input:focus-visible){outline:3px solid var(--pk-ink);outline-offset:3px}
|
|
482
542
|
.pkb-emo{font-size:max(14px,calc(32px*var(--pk-s)));line-height:1;transition:opacity .2s}
|
|
@@ -484,6 +544,7 @@ var CSS = `
|
|
|
484
544
|
.pkb-stamp{position:absolute;left:50%;top:50%;width:64%;aspect-ratio:1;border-radius:50%;border:3px solid var(--pk-red);box-shadow:inset 0 0 0 2px var(--pk-paper),inset 0 0 0 4px var(--pk-red);color:var(--pk-red);display:grid;place-items:center;font-family:var(--pk-display);font-size:max(9px,calc(20px*var(--pk-s)));letter-spacing:.04em;opacity:0;transform:translate(-50%,-50%) rotate(-16deg) scale(1.7);transition:transform .25s cubic-bezier(.3,1.5,.5,1),opacity .15s;pointer-events:none}
|
|
485
545
|
.pkb-tile.pkb-on .pkb-stamp{opacity:.9;transform:translate(-50%,-50%) rotate(-16deg) scale(1)}
|
|
486
546
|
.pkb-tile.pkb-on .pkb-emo,.pkb-tile.pkb-on .pkb-lbl{opacity:.35}
|
|
547
|
+
.pkb-tile.pkb-lock{cursor:default}
|
|
487
548
|
.pkb-tile.pkb-free{cursor:default;container-type:inline-size}
|
|
488
549
|
.pkb-tile.pkb-free .pkb-stamp{width:72%;opacity:1;transition:none;font-size:17cqi;letter-spacing:.02em;line-height:1;text-align:center;background:var(--pk-red);color:var(--pk-paper);border-color:var(--pk-red);box-shadow:inset 0 0 0 2px var(--pk-red),inset 0 0 0 4px var(--pk-paper)}
|
|
489
550
|
.pkb-tile.pkb-line{background-color:var(--pk-mint)}
|
|
@@ -511,10 +572,11 @@ var CSS = `
|
|
|
511
572
|
.pkb-foot{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px 10px;margin-top:18px;font-weight:800;font-size:13px;word-break:auto-phrase}
|
|
512
573
|
.pkb-foot>span{flex:1 1 14em}
|
|
513
574
|
.pkb-foot>span:empty{flex-basis:0}
|
|
514
|
-
.pkb-acts{display:flex;gap:8px;flex:
|
|
575
|
+
.pkb-acts{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;flex:0 1 auto;max-width:100%;margin-left:auto}
|
|
515
576
|
.pkb-reset,.pkb-shuf{appearance:none;flex:none;border:2px solid var(--pk-ink);background:transparent;border-radius:999px;padding:5px 12px;font-weight:800;font-size:12px;line-height:1.4;color:var(--pk-ink);cursor:pointer}
|
|
516
577
|
.pkb-shuf{background:var(--pk-paper);box-shadow:2px 2px 0 var(--pk-ink)}
|
|
517
|
-
.pkb-shuf:active{transform:translate(2px,2px);box-shadow:none}
|
|
578
|
+
.pkb-shuf:active:not(:disabled){transform:translate(2px,2px);box-shadow:none}
|
|
579
|
+
.pkb-shuf:disabled{cursor:default;opacity:.4}
|
|
518
580
|
.pkb-flash{position:absolute;inset:0;z-index:7;background:#fff;opacity:0;pointer-events:none}
|
|
519
581
|
.pkb-flash.pkb-go{animation:pkb-flash .4s ease-out}
|
|
520
582
|
@keyframes pkb-flash{0%{opacity:.8}100%{opacity:0}}
|
|
@@ -531,8 +593,33 @@ var CSS = `
|
|
|
531
593
|
.pkb-float.pkb-big{font-size:clamp(14px,5.6cqi,34px);color:#FFE066}
|
|
532
594
|
.pkb-float.pkb-rch{font-size:20px;color:var(--pk-red);-webkit-text-stroke:4px var(--pk-paper)}
|
|
533
595
|
@keyframes pkb-rise{0%{opacity:0;transform:translate(-50%,-20%) scale(.4)}18%{opacity:1;transform:translate(-50%,-60%) scale(1.2)}30%{transform:translate(-50%,-70%) scale(1)}75%{opacity:1}100%{opacity:0;transform:translate(-50%,-190%) scale(1)}}
|
|
596
|
+
.pkb-ready{appearance:none;position:absolute;inset:0;z-index:12;display:grid;place-items:center;padding:20px;border:0;border-radius:inherit;background:rgba(42,31,20,.5);color:var(--pk-ink);font:inherit;text-align:center;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background-color .35s}
|
|
597
|
+
.pkb-ready[hidden]{display:none}
|
|
598
|
+
.pkb-ready:focus-visible{outline:3px solid var(--pk-ink);outline-offset:-6px}
|
|
599
|
+
.pkb-ready.pkb-gone{pointer-events:none;background-color:transparent}
|
|
600
|
+
.pkb-board.pkb-waiting .pkb-snd{position:relative;z-index:13}
|
|
601
|
+
.pkb-ready-in,.pkb-count{grid-area:1/1}
|
|
602
|
+
.pkb-ready-in{display:flex;flex-direction:column;align-items:center;width:min(400px,100%);padding:24px 22px 22px;border:2.5px solid var(--pk-ink);border-radius:22px;background:var(--pk-paper)}
|
|
603
|
+
.pkb-ready.pkb-counting .pkb-ready-in{visibility:hidden}
|
|
604
|
+
.pkb-ready-t{font-family:var(--pk-display);font-weight:400;font-size:clamp(26px,7.5cqi,36px);line-height:1.15;color:var(--pk-red)}
|
|
605
|
+
.pkb-ready-q{margin-top:6px;font-weight:800;font-size:18px;line-height:1.4}
|
|
606
|
+
.pkb-ready-l{display:flex;flex-direction:column;gap:4px;width:100%;margin-top:14px;padding:12px 14px;border-radius:14px;background:rgba(42,31,20,.07);font-weight:800;font-size:16px;line-height:1.6;text-align:left;overflow-wrap:anywhere;word-break:auto-phrase}
|
|
607
|
+
.pkb-ready-l>span::before{content:"\u30FB"}
|
|
608
|
+
.pkb-ready-go{margin-top:18px;padding:10px 30px;border:2.5px solid var(--pk-ink);border-radius:999px;background:var(--pk-sun);font-weight:800;font-size:16px;line-height:1.4;box-shadow:3px 3px 0 var(--pk-ink);animation:pkb-beat 1.2s ease-in-out infinite}
|
|
609
|
+
@keyframes pkb-beat{50%{transform:scale(1.06)}}
|
|
610
|
+
.pkb-count{font-family:var(--pk-display);font-weight:400;line-height:1.1;paint-order:stroke fill;font-size:clamp(90px,34cqi,200px);color:var(--pk-sun);-webkit-text-stroke:5px var(--pk-ink);text-shadow:7px 7px 0 var(--pk-ink);opacity:0;pointer-events:none}
|
|
611
|
+
.pkb-count.pkb-go{font-size:clamp(48px,17cqi,120px);color:var(--pk-red);white-space:nowrap}
|
|
612
|
+
.pkb-count.pkb-tick{animation:pkb-cnt 1s cubic-bezier(.2,1.4,.4,1) forwards}
|
|
613
|
+
.pkb-count.pkb-go.pkb-tick{animation:pkb-cnt-go .75s cubic-bezier(.2,1.4,.4,1) forwards}
|
|
614
|
+
@keyframes pkb-cnt{0%{opacity:0;transform:scale(2.2)}18%{opacity:1;transform:scale(.92)}30%{transform:scale(1)}75%{opacity:1}100%{opacity:0;transform:scale(.8)}}
|
|
615
|
+
@keyframes pkb-cnt-go{0%{opacity:0;transform:scale(.4) rotate(-8deg)}25%{opacity:1;transform:scale(1.12) rotate(-5deg)}40%{transform:scale(1) rotate(-5deg)}70%{opacity:1}100%{opacity:0;transform:scale(1.5) rotate(-5deg)}}
|
|
616
|
+
.pkb-ptime{display:inline-flex;align-items:baseline;gap:8px;margin:-4px 0 14px;padding:5px 16px 4px;border-radius:999px;background:var(--pk-ink);color:var(--pk-sun)}
|
|
617
|
+
.pkb-ptime[hidden]{display:none}
|
|
618
|
+
.pkb-ptime small{font-size:11px;font-weight:800;letter-spacing:.08em;color:#fff}
|
|
619
|
+
.pkb-ptime b{font-family:var(--pk-display);font-weight:400;font-size:22px;line-height:1.2;font-variant-numeric:tabular-nums}
|
|
534
620
|
.pkb-perfect{position:absolute;inset:0;z-index:8;display:grid;place-items:center;padding:20px;background:rgba(42,31,20,.5);opacity:0;visibility:hidden;transition:opacity .3s,visibility .3s}
|
|
535
|
-
.
|
|
621
|
+
/* Visible at once when opening (focus can't land on a hidden dialog); hidden only after the fade when closing. */
|
|
622
|
+
.pkb-perfect.pkb-show{opacity:1;visibility:visible;transition:opacity .3s,visibility 0s}
|
|
536
623
|
.pkb-card{width:min(400px,100%);background:var(--pk-paper);border:2.5px solid var(--pk-ink);border-radius:22px;box-shadow:6px 6px 0 var(--pk-ink);padding:26px 22px 20px;text-align:center;transform:scale(.6) translateY(30px);transition:transform .45s cubic-bezier(.3,1.5,.5,1)}
|
|
537
624
|
.pkb-perfect.pkb-show .pkb-card{transform:none}
|
|
538
625
|
.pkb-medal{width:124px;height:124px;margin:0 auto 12px;border-radius:50%;border:5px solid var(--pk-red);box-shadow:inset 0 0 0 4px var(--pk-paper),inset 0 0 0 7px var(--pk-red);color:var(--pk-red);display:grid;place-content:center;font-family:var(--pk-display);font-size:26px;line-height:1.15;white-space:pre-line;opacity:0;transform:rotate(-14deg) scale(2.6)}
|
|
@@ -542,6 +629,7 @@ var CSS = `
|
|
|
542
629
|
@keyframes pkb-slam{to{opacity:1;transform:rotate(-14deg) scale(1)}}
|
|
543
630
|
.pkb-ptitle{font-family:var(--pk-display);font-weight:400;font-size:30px;line-height:1.3;color:inherit}
|
|
544
631
|
.pkb-pbody{font-weight:800;font-size:13.5px;line-height:1.6;margin:6px 0 14px;white-space:pre-line;word-break:auto-phrase}
|
|
632
|
+
.pkb-pbody:empty{margin:0 0 14px}
|
|
545
633
|
.pkb-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:16px}
|
|
546
634
|
.pkb-stats>div{border:2px solid var(--pk-ink);border-radius:12px;padding:6px 4px 5px;background:#fff}
|
|
547
635
|
.pkb-stats small{display:block;font-size:9.5px;font-weight:800;letter-spacing:.1em}
|
|
@@ -551,10 +639,72 @@ var CSS = `
|
|
|
551
639
|
.pkb-fx{position:absolute;inset:0;width:100%;height:100%;z-index:10;pointer-events:none}
|
|
552
640
|
.pkb-demo{max-width:720px;margin:12px auto 4px;display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-family:var(--pk-body);font-size:12px;color:#666}
|
|
553
641
|
.pkb-demo button{appearance:none;border:1px solid #ccc;background:#f3f3f3;color:#222;border-radius:999px;padding:5px 12px;font-size:12px;cursor:pointer}
|
|
554
|
-
|
|
555
|
-
|
|
642
|
+
.pkb-mode{position:relative;isolation:isolate;display:inline-grid;grid-auto-flow:column;grid-auto-columns:1fr;padding:2px;border-radius:999px;background:var(--pk-ink);transition:box-shadow .4s}
|
|
643
|
+
.pkb-mode::after{content:"";position:absolute;inset:0;z-index:2;border-radius:inherit;pointer-events:none;background:linear-gradient(110deg,transparent 30%,rgba(255,255,255,.75) 45%,transparent 60%) no-repeat;background-size:250% 100%;background-position:200% 0;opacity:0}
|
|
644
|
+
.pkb-mode button{appearance:none;position:relative;z-index:1;border:0;background:none;border-radius:999px;padding:3px 11px 2px;font-weight:800;font-size:11px;line-height:1.4;letter-spacing:.04em;white-space:nowrap;color:var(--pk-paper);opacity:.72;cursor:pointer;transition:color .3s,opacity .3s;-webkit-tap-highlight-color:transparent}
|
|
645
|
+
.pkb-mode button[aria-checked="true"]{opacity:1;color:var(--pk-ink);cursor:default}
|
|
646
|
+
.pkb-mode button:focus-visible{outline:2px solid var(--pk-paper);outline-offset:-4px}
|
|
647
|
+
.pkb-mode-th{position:absolute;top:2px;bottom:2px;left:2px;width:calc(50% - 2px);border-radius:999px;background:var(--pk-sun);transition:transform .55s cubic-bezier(.3,1.45,.45,1)}
|
|
648
|
+
.pkb.pkb-inf .pkb-mode{box-shadow:0 0 14px 1px rgba(155,93,229,.6)}
|
|
649
|
+
.pkb.pkb-inf .pkb-mode::after{opacity:1;animation:pkb-glint 3.2s ease-in-out infinite}
|
|
650
|
+
.pkb.pkb-inf .pkb-mode-th{transform:translateX(100%);background:linear-gradient(90deg,#FF9F1C,#E8473B,#9B5DE5,#2F6FED,#1FA971,#FF9F1C) 0 0/200% 100%;animation:pkb-rb 2.4s linear infinite}
|
|
651
|
+
.pkb.pkb-inf .pkb-mode button[data-m="infinite"]{color:#fff;text-shadow:0 1px 1px rgba(42,31,20,.55)}
|
|
652
|
+
.pkb-board.pkb-waiting .pkb-mode,.pkb-board.pkb-switching .pkb-mode{z-index:15}
|
|
653
|
+
.pkb-board.pkb-switching .pkb-fx{z-index:16}
|
|
654
|
+
.pkb.pkb-inf .pkb-board{border-color:transparent;background:radial-gradient(rgba(42,31,20,.12) 1.3px,transparent 1.6px) 0 0/16px 16px padding-box,linear-gradient(var(--pk-sun),var(--pk-sun)) padding-box,linear-gradient(120deg,#FF9F1C,#E8473B 22%,#9B5DE5 45%,#2F6FED 68%,#1FA971 88%,#FF9F1C) border-box;box-shadow:0 0 0 2px var(--pk-ink),0 10px 34px -10px rgba(155,93,229,.7)}
|
|
655
|
+
.pkb.pkb-inf .pkb-demo [data-r="dP"],.pkb.pkb-inf .pkb-demo [data-r="dR"]{display:none}
|
|
656
|
+
.pkb-ready-b,.pkb-ready-r{display:none}
|
|
657
|
+
.pkb.pkb-inf .pkb-ready-in{border-color:transparent;background:linear-gradient(var(--pk-paper),var(--pk-paper)) padding-box,linear-gradient(120deg,#FF9F1C,#E8473B 22%,#9B5DE5 45%,#2F6FED 68%,#1FA971 88%,#FF9F1C) border-box;box-shadow:0 0 0 2px var(--pk-ink),0 0 32px rgba(155,93,229,.5)}
|
|
658
|
+
.pkb.pkb-inf .pkb-ready-b{display:inline-block;margin-bottom:10px;padding:4px 14px 3px;border-radius:999px;background:linear-gradient(90deg,#FF9F1C,#E8473B,#9B5DE5,#2F6FED,#1FA971,#FF9F1C) 0 0/200% 100%;color:#fff;font-weight:800;font-size:13px;line-height:1.4;letter-spacing:.08em;text-shadow:0 1px 1px rgba(42,31,20,.5);box-shadow:0 0 14px rgba(155,93,229,.55);animation:pkb-rb 2.4s linear infinite}
|
|
659
|
+
.pkb.pkb-inf .pkb-ready-r{display:block;margin-top:8px;font-weight:800;font-size:15px;line-height:1.5;word-break:auto-phrase}
|
|
660
|
+
.pkb-tile.pkb-clr{animation:pkb-clr .62s var(--pkb-cd,0ms) both}
|
|
661
|
+
@keyframes pkb-clr{0%{transform:perspective(600px) rotateY(0);animation-timing-function:cubic-bezier(.55,0,.9,.45)}42%{transform:perspective(600px) rotateY(90deg) scale(.9)}42.01%{transform:perspective(600px) rotateY(-90deg) scale(.9);animation-timing-function:cubic-bezier(.2,1.45,.4,1)}100%{transform:perspective(600px) rotateY(0)}}
|
|
662
|
+
.pkb-mfx{position:absolute;inset:0;z-index:14;display:grid;place-items:center;overflow:hidden;border-radius:inherit;pointer-events:none;visibility:hidden;opacity:0;text-align:center}
|
|
663
|
+
.pkb-mfx.pkb-show{visibility:visible;animation:pkb-mfx 1.7s ease-out forwards}
|
|
664
|
+
@keyframes pkb-mfx{0%{opacity:0}9%{opacity:1}72%{opacity:1}100%{opacity:0}}
|
|
665
|
+
.pkb-mfx-inf{background:radial-gradient(120% 95% at 50% 42%,#33205c 0%,#170f2e 52%,#08060f 100%)}
|
|
666
|
+
.pkb-mfx-n{background:var(--pk-sun) radial-gradient(rgba(42,31,20,.16) 1.3px,transparent 1.6px) 0 0/16px 16px}
|
|
667
|
+
.pkb-mfx::after{content:"";position:absolute;top:-30%;bottom:-30%;left:-50%;width:50%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.55),transparent);transform:skewX(-18deg);opacity:0}
|
|
668
|
+
.pkb-mfx.pkb-show::after{animation:pkb-beam 1s cubic-bezier(.4,0,.2,1) .22s both}
|
|
669
|
+
@keyframes pkb-beam{0%{opacity:1;transform:skewX(-18deg) translateX(0)}100%{opacity:1;transform:skewX(-18deg) translateX(400%)}}
|
|
670
|
+
.pkb-mfx-glow{position:absolute;left:50%;top:46%;width:min(150cqi,1000px);aspect-ratio:1;translate:-50% -50%;border-radius:50%;background:conic-gradient(#FF9F1C,#E8473B,#9B5DE5,#2F6FED,#1FA971,#FF9F1C);-webkit-mask:radial-gradient(circle,transparent 14%,#000 30%,rgba(0,0,0,.4) 48%,transparent 68%);mask:radial-gradient(circle,transparent 14%,#000 30%,rgba(0,0,0,.4) 48%,transparent 68%);filter:blur(28px);opacity:0}
|
|
671
|
+
.pkb-mfx.pkb-show .pkb-mfx-glow{animation:pkb-spin 3s linear infinite,pkb-glow 1.7s ease-out both}
|
|
672
|
+
@keyframes pkb-glow{0%{opacity:0;scale:.35}30%{opacity:.8;scale:1}100%{opacity:.5;scale:1.12}}
|
|
673
|
+
.pkb-mfx i{position:absolute;left:var(--x);top:var(--y);width:calc(20px*var(--s));aspect-ratio:1;background:var(--c);clip-path:polygon(50% 0,62% 38%,100% 50%,62% 62%,50% 100%,38% 62%,0 50%,38% 38%);opacity:0}
|
|
674
|
+
.pkb-mfx.pkb-show i{animation:pkb-twk .85s ease-in-out var(--t) both}
|
|
675
|
+
@keyframes pkb-twk{0%{opacity:0;scale:0;rotate:0deg}45%{opacity:1;scale:1;rotate:70deg}100%{opacity:0;scale:.2;rotate:160deg}}
|
|
676
|
+
.pkb-mfx-in{position:relative;display:flex;flex-direction:column;align-items:center;padding:0 16px}
|
|
677
|
+
.pkb-mfx-sym{display:block;font-family:var(--pk-display);font-weight:400;line-height:1}
|
|
678
|
+
.pkb-mfx-inf .pkb-mfx-sym{font-size:clamp(110px,36cqi,230px);line-height:.9;padding:0 .06em;background:linear-gradient(90deg,#FFE066,#FF9F1C,#E8473B,#9B5DE5,#2F6FED,#1FA971,#FFE066) 0 0/200% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;filter:drop-shadow(0 0 22px rgba(155,93,229,.9)) drop-shadow(0 0 3px rgba(255,255,255,.8))}
|
|
679
|
+
.pkb-mfx.pkb-show.pkb-mfx-inf .pkb-mfx-sym{animation:pkb-sym 1.7s cubic-bezier(.2,1.25,.35,1) both,pkb-rb 1.7s linear both}
|
|
680
|
+
@keyframes pkb-sym{0%{opacity:0;transform:scale(.25) rotate(-30deg)}26%{opacity:1;transform:scale(1.1) rotate(4deg)}38%{transform:scale(1) rotate(0)}100%{opacity:1;transform:scale(1.06)}}
|
|
681
|
+
.pkb-mfx-n .pkb-mfx-sym{display:grid;place-items:center;width:clamp(110px,30cqi,170px);aspect-ratio:1;border-radius:50%;border:5px solid var(--pk-red);box-shadow:inset 0 0 0 4px var(--pk-sun),inset 0 0 0 7px var(--pk-red);color:var(--pk-red);font-size:clamp(34px,10cqi,56px);letter-spacing:.04em;transform:rotate(-14deg)}
|
|
682
|
+
.pkb-mfx.pkb-show.pkb-mfx-n .pkb-mfx-sym{animation:pkb-mslam .42s cubic-bezier(.5,0,.75,0) .08s both}
|
|
683
|
+
@keyframes pkb-mslam{from{opacity:0;transform:rotate(-14deg) scale(2.6)}to{opacity:1;transform:rotate(-14deg) scale(1)}}
|
|
684
|
+
.pkb-mfx-t{display:block;margin-top:6px;font-family:var(--pk-display);font-weight:400;font-size:clamp(24px,8.5cqi,52px);line-height:1.1;letter-spacing:.2em;margin-right:-.2em;white-space:nowrap}
|
|
685
|
+
.pkb-mfx-inf .pkb-mfx-t{color:#fff;text-shadow:0 0 18px rgba(155,93,229,.95),0 0 4px rgba(255,255,255,.9)}
|
|
686
|
+
.pkb-mfx-n .pkb-mfx-t{margin-top:16px;color:var(--pk-ink)}
|
|
687
|
+
.pkb-mfx.pkb-show .pkb-mfx-t{animation:pkb-track 1.7s cubic-bezier(.16,1,.3,1) .12s both}
|
|
688
|
+
@keyframes pkb-track{0%{opacity:0;letter-spacing:.9em;margin-right:-.9em;filter:blur(8px)}40%{opacity:1;letter-spacing:.2em;margin-right:-.2em;filter:blur(0)}100%{opacity:1;letter-spacing:.24em;margin-right:-.24em}}
|
|
689
|
+
.pkb-mfx-s{display:inline-block;margin-top:12px;padding:5px 16px 4px;border-radius:999px;font-weight:800;font-size:14px;line-height:1.4;letter-spacing:.06em}
|
|
690
|
+
.pkb-mfx-inf .pkb-mfx-s{color:#fff;background:rgba(255,255,255,.12);border:1.5px solid rgba(255,255,255,.4)}
|
|
691
|
+
.pkb-mfx-n .pkb-mfx-s{background:var(--pk-ink);color:var(--pk-sun)}
|
|
692
|
+
.pkb-mfx.pkb-show .pkb-mfx-s{animation:pkb-up .6s cubic-bezier(.2,1.3,.4,1) .4s both}
|
|
693
|
+
@keyframes pkb-up{from{opacity:0;transform:translateY(12px)}}
|
|
694
|
+
.pkb-finbtn{display:none;appearance:none;flex:none;border:2px solid var(--pk-ink);border-radius:999px;padding:5px 14px;font-weight:800;font-size:12px;line-height:1.4;letter-spacing:.04em;color:#fff;text-shadow:0 1px 1px rgba(42,31,20,.55);background:linear-gradient(90deg,#FF9F1C,#E8473B,#9B5DE5,#2F6FED,#1FA971,#FF9F1C) 0 0/200% 100%;box-shadow:2px 2px 0 var(--pk-ink),0 0 12px rgba(155,93,229,.45);cursor:pointer;animation:pkb-rb 2.4s linear infinite}
|
|
695
|
+
.pkb.pkb-inf .pkb-finbtn{display:inline-block}
|
|
696
|
+
.pkb-finbtn:active:not(:disabled){transform:translate(2px,2px);box-shadow:none}
|
|
697
|
+
.pkb-finbtn:disabled{cursor:default;opacity:.4;animation:none;box-shadow:2px 2px 0 var(--pk-ink)}
|
|
698
|
+
.pkb-board.pkb-over .pkb-grid{opacity:.55;filter:saturate(.5);transition:opacity .4s,filter .4s}
|
|
699
|
+
.pkb-pbtns{display:flex;flex-wrap:wrap;justify-content:center;gap:10px}
|
|
700
|
+
.pkb-perfect.pkb-fin .pkb-card{border-color:transparent;background:linear-gradient(var(--pk-paper),var(--pk-paper)) padding-box,linear-gradient(120deg,#FF9F1C,#E8473B 22%,#9B5DE5 45%,#2F6FED 68%,#1FA971 88%,#FF9F1C) border-box;box-shadow:0 0 0 2px var(--pk-ink),6px 6px 0 2px var(--pk-ink),0 0 40px rgba(155,93,229,.5)}
|
|
701
|
+
.pkb-perfect.pkb-fin .pkb-medal{border-color:transparent;background:linear-gradient(var(--pk-paper),var(--pk-paper)) padding-box,conic-gradient(#FF9F1C,#E8473B,#9B5DE5,#2F6FED,#1FA971,#FF9F1C) border-box;box-shadow:0 0 18px rgba(155,93,229,.55);color:#7B3FD0;font-size:clamp(44px,14cqi,64px);line-height:1;text-shadow:0 0 12px rgba(155,93,229,.45)}
|
|
702
|
+
.pkb-perfect.pkb-fin .pkb-again{background:linear-gradient(90deg,#FF9F1C,#E8473B,#9B5DE5,#2F6FED,#1FA971,#FF9F1C) 0 0/200% 100%;color:#fff;text-shadow:0 1px 1px rgba(42,31,20,.55);animation:pkb-rb 2.4s linear infinite}
|
|
703
|
+
.pkb-perfect.pkb-fin .pkb-close:not(.pkb-again){background:var(--pk-paper)}
|
|
704
|
+
@container (max-width:560px){.pkb-board{padding:18px 14px 16px}.pkb-head{flex-direction:column;align-items:stretch;gap:12px}.pkb-score{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.pkb-pill,.pkb-pill.pkb-sc{min-width:0;padding:5px 4px 4px}.pkb-pill b{font-size:19px}.pkb-title{font-size:26px}.pkb-grid{gap:max(3px,calc(8px*var(--pk-d)))}.pkb-tile{border-radius:calc(12px*var(--pk-d));box-shadow:3px 3px 0 var(--pk-ink);gap:calc(3px*var(--pk-d));aspect-ratio:1/1}.pkb-emo{font-size:max(13px,calc(24px*var(--pk-s)))}.pkb-lbl{font-size:max(8.5px,calc(10.5px*var(--pk-s)));padding:0 calc(2px*var(--pk-d))}.pkb-stamp{font-size:max(9px,calc(14px*var(--pk-s)))}.pkb-tile.pkb-reach::after{font-size:9px;padding:2px 5px;top:-9px;right:-5px}.pkb-lines .pkb-u{stroke-width:calc(10px*var(--pk-d))}.pkb-lines .pkb-o{stroke-width:calc(5px*var(--pk-d))}.pkb-banner::before{width:380px;height:380px;margin:-190px 0 0 -190px}.pkb-banner span{font-size:12px}.pkb-medal{width:96px;height:96px;font-size:20px}.pkb-card{padding:20px 14px 16px}.pkb-ptitle{font-size:24px}.pkb-stats b{font-size:17px}.pkb-float.pkb-pt{font-size:17px}.pkb-float.pkb-cmb{font-size:13px}.pkb-ready-in{padding:20px 16px 18px}.pkb-ready-l{font-size:15px;padding:10px 12px}.pkb-ready-go{font-size:15px}.pkb-ptime b{font-size:19px}}
|
|
705
|
+
@container (max-width:400px){.pkb-board{padding:16px 10px 14px}.pkb-mode button{padding:3px 8px 2px}.pkb-grid{gap:max(3px,calc(6px*var(--pk-d)))}.pkb-lbl{font-size:max(8.5px,calc(10px*var(--pk-s)));padding:0 calc(1px*var(--pk-d))}.pkb-emo{font-size:max(12px,calc(22px*var(--pk-s)))}.pkb-stamp{font-size:max(9px,calc(13px*var(--pk-s)))}.pkb-foot{margin-top:14px;font-size:12px}.pkb-perfect,.pkb-ready{padding:12px}.pkb-stats{gap:6px}.pkb-stats>div{padding:6px 2px 5px}.pkb-stats small{letter-spacing:.02em}}
|
|
556
706
|
@container (max-width:320px){.pkb-board{padding-inline:8px}.pkb-grid{gap:max(3px,calc(5px*var(--pk-d)))}.pkb-lbl{font-size:max(8.5px,calc(9.5px*var(--pk-s)));padding:0}}
|
|
557
|
-
@media (prefers-reduced-motion:reduce){.pkb-board.pkb-shake,.pkb-board.pkb-punch,.pkb-flash.pkb-go,.pkb-tile.pkb-hop,.pkb-tile.pkb-squash,.pkb-tile.pkb-on .pkb-stamp.pkb-hit,.pkb-tile.pkb-reach,.pkb-tile.pkb-line::before,.pkb-pill.pkb-hot,.pkb-bump,.pkb-tile.pkb-reach::after,.pkb-banner::before,.pkb-lines .pkb-draw line,.pkb-kicker.pkb-gold{animation:none}.pkb-banner.pkb-show{animation:pkb-fade 1.8s forwards}.pkb-float{animation:pkb-fade 1s both}.pkb-perfect.pkb-show .pkb-medal{animation:none;opacity:1;transform:rotate(-14deg)}.pkb-card,.pkb-tile,.pkb-stamp{transition:none}}
|
|
707
|
+
@media (prefers-reduced-motion:reduce){.pkb-board.pkb-shake,.pkb-board.pkb-punch,.pkb-flash.pkb-go,.pkb-tile.pkb-hop,.pkb-tile.pkb-squash,.pkb-tile.pkb-on .pkb-stamp.pkb-hit,.pkb-tile.pkb-reach,.pkb-tile.pkb-line::before,.pkb-pill.pkb-hot,.pkb-bump,.pkb-tile.pkb-reach::after,.pkb-banner::before,.pkb-lines .pkb-draw line,.pkb-kicker.pkb-gold,.pkb-ready-go,.pkb.pkb-inf .pkb-mode-th,.pkb.pkb-inf .pkb-mode::after,.pkb.pkb-inf .pkb-ready-b,.pkb-tile.pkb-clr,.pkb-finbtn,.pkb-perfect.pkb-fin .pkb-again,.pkb-mfx.pkb-show.pkb-mfx-inf .pkb-mfx-sym,.pkb-mfx.pkb-show.pkb-mfx-n .pkb-mfx-sym,.pkb-mfx.pkb-show .pkb-mfx-t,.pkb-mfx.pkb-show .pkb-mfx-s,.pkb-mfx.pkb-show .pkb-mfx-glow,.pkb-mfx.pkb-show::after{animation:none}.pkb-count.pkb-tick,.pkb-count.pkb-go.pkb-tick{animation:pkb-fade 1s forwards}.pkb-mfx.pkb-show{animation:pkb-fade 1.4s forwards}.pkb-mode-th{transition:none}.pkb-ready{transition:none}.pkb-banner.pkb-show{animation:pkb-fade 1.8s forwards}.pkb-float{animation:pkb-fade 1s both}.pkb-perfect.pkb-show .pkb-medal{animation:none;opacity:1;transform:rotate(-14deg)}.pkb-card,.pkb-tile,.pkb-stamp{transition:none}}
|
|
558
708
|
@keyframes pkb-fade{0%,100%{opacity:0}15%,80%{opacity:1}}
|
|
559
709
|
`;
|
|
560
710
|
|
|
@@ -564,7 +714,6 @@ var CONFETTI = ["#E8473B", "#2F6FED", "#1FA971", "#FFFDF6", "#2A1F14", "#FF9F1C"
|
|
|
564
714
|
var COMBO_COLORS = ["#FF9F1C", "#1FA971", "#2F6FED", "#9B5DE5", "#E8473B"];
|
|
565
715
|
var COMBO_MS = 2500;
|
|
566
716
|
var MUTE_KEY = "packing-bingo-muted";
|
|
567
|
-
var playReady = /* @__PURE__ */ new WeakSet();
|
|
568
717
|
var ORDER_PREFIX = "packing-bingo-order:";
|
|
569
718
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
570
719
|
function readStore(key) {
|
|
@@ -582,7 +731,22 @@ function writeStore(key, value) {
|
|
|
582
731
|
}
|
|
583
732
|
var glintDelay = (c, n) => `${+((Math.floor(c / n) + c % n) * 0.36 / Math.max(1, n - 1)).toFixed(3)}s`;
|
|
584
733
|
var lineKey = (l) => l.slice().sort((a, b) => a - b).join(",");
|
|
734
|
+
var COUNT_MS = 1e3;
|
|
735
|
+
var GO_MS = 750;
|
|
736
|
+
function formatTime(ms, digits = 1) {
|
|
737
|
+
const unit = 10 ** digits;
|
|
738
|
+
const t = Math.floor(Math.max(0, ms) / (1e3 / unit));
|
|
739
|
+
const frac = digits ? "." + String(t % unit).padStart(digits, "0") : "";
|
|
740
|
+
const sec = Math.floor(t / unit), s = sec % 60, m = Math.floor(sec / 60) % 60, h = Math.floor(sec / 3600);
|
|
741
|
+
const pad = (v) => String(v).padStart(2, "0");
|
|
742
|
+
return (h ? `${h}:${pad(m)}` : String(m)) + `:${pad(s)}${frac}`;
|
|
743
|
+
}
|
|
744
|
+
function speedBonus(items, ms) {
|
|
745
|
+
return Math.round(items * 1e6 / Math.max(ms, 1));
|
|
746
|
+
}
|
|
585
747
|
var FLY_MS = 560;
|
|
748
|
+
var CLEAR_MS = 1e3;
|
|
749
|
+
var SPARK = ["#FFFFFF", "#FFE066", "#FF9F1C", "#E8473B", "#9B5DE5", "#2F6FED", "#1FA971"];
|
|
586
750
|
function ensureStyles() {
|
|
587
751
|
if (document.getElementById("pkb-styles")) return;
|
|
588
752
|
const s = document.createElement("style");
|
|
@@ -590,7 +754,8 @@ function ensureStyles() {
|
|
|
590
754
|
s.textContent = CSS;
|
|
591
755
|
document.head.appendChild(s);
|
|
592
756
|
}
|
|
593
|
-
|
|
757
|
+
var opt = (v, fallback) => v ?? fallback ?? "";
|
|
758
|
+
function template(L, M, { demo, shuffle, vibrate, ready, modes }) {
|
|
594
759
|
const n = L.size;
|
|
595
760
|
const input = vibrate ? `<input type="checkbox" switch role="checkbox">` : `<input type="checkbox">`;
|
|
596
761
|
const tiles = Array.from({ length: n * n }, (_, c) => {
|
|
@@ -602,16 +767,15 @@ function template(L, M, { demo, shuffle, vibrate }) {
|
|
|
602
767
|
}).join("");
|
|
603
768
|
const scale = `--pk-n:${n};--pk-s:${+Math.min(1.35, 4 / n).toFixed(3)};--pk-d:${+Math.min(1, 4 / n).toFixed(3)}`;
|
|
604
769
|
const demoBar = demo ? `<div class="pkb-demo"><span>${esc(M.demoLabel)}</span><button type="button" data-r="dB">${esc(M.demoBingo)}</button><button type="button" data-r="dP">${esc(M.demoPerfect)}</button><button type="button" data-r="dR">${esc(M.demoRestore)}</button></div>` : "";
|
|
605
|
-
const hints = [M.soundHint
|
|
606
|
-
const
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
<button type="button" class="pkb-play-skip">${esc(M.playSkip?.trim() || ja.playSkip || M.close)}</button></div></dialog>` : "";
|
|
770
|
+
const hints = [opt(M.soundHint, ja.soundHint), vibrate ? opt(M.vibrationHint, ja.vibrationHint) : ""].filter((h) => h.trim());
|
|
771
|
+
const prompt = opt(M.readyPrompt, ja.readyPrompt);
|
|
772
|
+
const rule = opt(M.infiniteRule, ja.infiniteRule);
|
|
773
|
+
const readyScreen = ready ? `<button type="button" class="pkb-ready" data-r="ready"><span class="pkb-ready-in"><span class="pkb-ready-b">\u221E ${esc(M.infiniteSub?.trim() || ja.infiniteSub)}</span><b class="pkb-ready-t">${esc(M.readyTitle?.trim() || ja.readyTitle)}</b>` + (prompt.trim() ? `<span class="pkb-ready-q">${esc(prompt)}</span>` : "") + (rule.trim() ? `<span class="pkb-ready-r">${esc(rule)}</span>` : "") + (hints.length ? `<span class="pkb-ready-l">${hints.map((h) => `<span>${esc(h)}</span>`).join("")}</span>` : "") + `<span class="pkb-ready-go">${esc(M.readyTap?.trim() || ja.readyTap)}</span></span><b class="pkb-count" data-r="count" aria-live="assertive"></b></button>` : "";
|
|
774
|
+
const timeLabel = M.time?.trim() || ja.time;
|
|
775
|
+
const modeSwitch = modes ? `<span class="pkb-mode" data-r="mode" role="radiogroup" aria-label="${esc(M.modeLabel?.trim() || ja.modeLabel)}"><i class="pkb-mode-th" data-r="modeTh"></i><button type="button" role="radio" data-m="normal">${esc(M.modeNormal?.trim() || ja.modeNormal)}</button><button type="button" role="radio" data-m="infinite">${esc(M.modeInfinite?.trim() || ja.modeInfinite)}</button></span>` : "";
|
|
612
776
|
return `<div class="pkb" style="${scale}"><div class="pkb-board">
|
|
613
777
|
<div class="pkb-head"><div>
|
|
614
|
-
<div class="pkb-tags"><span class="pkb-kicker" data-r="kick">${esc(M.tag)}</span
|
|
778
|
+
<div class="pkb-tags"><span class="pkb-kicker" data-r="kick">${esc(M.tag)}</span>${modeSwitch}<button type="button" class="pkb-snd" data-r="snd"></button><span class="pkb-watch" data-r="watch" role="timer" aria-label="${esc(timeLabel)}">${formatTime(0)}</span></div>
|
|
615
779
|
<h2 class="pkb-title">${esc(M.title)}</h2>${M.subtitle ? `<p class="pkb-sub">${esc(M.subtitle)}</p>` : ""}</div>
|
|
616
780
|
<div class="pkb-side"><div class="pkb-score">
|
|
617
781
|
<div class="pkb-pill"><small>${esc(M.packed)}</small><b data-r="cnt"></b></div>
|
|
@@ -620,13 +784,14 @@ ${hints.map((hint) => `<p>${esc(hint)}</p>`).join("")}
|
|
|
620
784
|
<div class="pkb-combo" data-r="combo"><b data-r="cmbN"></b><span class="pkb-bar"><i data-r="cbar"></i></span></div></div></div>
|
|
621
785
|
<div class="pkb-gw"><div class="pkb-grid">${tiles}</div><svg class="pkb-lines" aria-hidden="true"></svg>
|
|
622
786
|
<div class="pkb-banner" data-r="banner" aria-hidden="true"><b data-r="bT"></b><span data-r="bS"></span></div></div>
|
|
623
|
-
<div class="pkb-foot"><span data-r="msg" aria-live="polite"></span><div class="pkb-acts">${shuffle ? `<button type="button" class="pkb-shuf" data-r="shuf">${esc(M.shuffle)}</button>` : ""}<button type="button" class="pkb-reset" data-r="reset">${esc(M.reset)}</button></div></div>
|
|
787
|
+
<div class="pkb-foot"><span data-r="msg" aria-live="polite"></span><div class="pkb-acts"><button type="button" class="pkb-finbtn" data-r="fin" disabled>${esc(M.finish?.trim() || ja.finish)}</button>${shuffle ? `<button type="button" class="pkb-shuf" data-r="shuf">${esc(M.shuffle)}</button>` : ""}<button type="button" class="pkb-reset" data-r="reset">${esc(M.reset)}</button></div></div>
|
|
624
788
|
<div class="pkb-flash" data-r="flash"></div>
|
|
625
|
-
<div class="pkb-perfect" data-r="perfect"><div class="pkb-card" role="dialog" aria-label="${esc(M.perfectTitle)}">
|
|
626
|
-
<div class="pkb-medal">${esc(M.perfectStamp)}</div><h3 class="pkb-ptitle">${esc(M.perfectTitle)}</h3><p class="pkb-pbody" data-r="pbody">${esc(perfectText(M, L.items.length))}</p>
|
|
789
|
+
<div class="pkb-perfect" data-r="perfect"><div class="pkb-card" data-r="pcard" role="dialog" aria-label="${esc(M.perfectTitle)}">
|
|
790
|
+
<div class="pkb-medal" data-r="medal">${esc(M.perfectStamp)}</div><h3 class="pkb-ptitle" data-r="ptitle">${esc(M.perfectTitle)}</h3><p class="pkb-pbody" data-r="pbody">${esc(perfectText(M, L.items.length))}</p>
|
|
791
|
+
<p class="pkb-ptime" data-r="ptime" hidden><small data-r="pTl">${esc(M.statTime?.trim() || ja.statTime)}</small><b data-r="pT"></b></p>
|
|
627
792
|
<div class="pkb-stats"><div><small>${esc(M.statScore)}</small><b data-r="sScore">0</b></div><div><small>${esc(M.statCombo)}</small><b data-r="sCombo">\xD70</b></div><div><small>${esc(M.statLines)}</small><b data-r="sBingo">0</b></div></div>
|
|
628
|
-
<button type="button" class="pkb-close" data-r="close">${esc(M.close)}</button></div></div>
|
|
629
|
-
<canvas class="pkb-fx"></canvas></div>${demoBar}
|
|
793
|
+
<div class="pkb-pbtns"><button type="button" class="pkb-close pkb-again" data-r="again" hidden>${esc(M.again?.trim() || ja.again)}</button><button type="button" class="pkb-close" data-r="close">${esc(M.close)}</button></div></div></div>
|
|
794
|
+
<canvas class="pkb-fx"></canvas>${readyScreen}<div class="pkb-mfx" data-r="mfx" aria-hidden="true"></div></div>${demoBar}</div>`;
|
|
630
795
|
}
|
|
631
796
|
var perfectText = (M, total) => typeof M.perfectBody === "function" ? M.perfectBody(total) : M.perfectBody;
|
|
632
797
|
function createPackingBingo(container, options = {}) {
|
|
@@ -658,8 +823,15 @@ function createPackingBingo(container, options = {}) {
|
|
|
658
823
|
const orderKey = storageKey && ORDER_PREFIX + storageKey;
|
|
659
824
|
const demo = !!options.demo;
|
|
660
825
|
const vibrate = options.vibrate !== false;
|
|
826
|
+
const uncheck = options.uncheck === true;
|
|
661
827
|
const calm = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
662
828
|
const haptics = createHaptics(vibrate);
|
|
829
|
+
let mode = options.mode === "infinite" ? "infinite" : "normal";
|
|
830
|
+
const inf = () => mode === "infinite";
|
|
831
|
+
let packing = null;
|
|
832
|
+
let cleared = 0;
|
|
833
|
+
const pending = /* @__PURE__ */ new Set();
|
|
834
|
+
let clearTimer = 0, clearAt = 0;
|
|
663
835
|
let dead = false;
|
|
664
836
|
const timers = /* @__PURE__ */ new Set();
|
|
665
837
|
const later = (fn, ms) => {
|
|
@@ -672,12 +844,19 @@ function createPackingBingo(container, options = {}) {
|
|
|
672
844
|
const wait = (ms) => new Promise((r) => window.setTimeout(r, ms));
|
|
673
845
|
let gen = 0;
|
|
674
846
|
ensureStyles();
|
|
675
|
-
container.innerHTML = template(L, M, {
|
|
847
|
+
container.innerHTML = template(L, M, {
|
|
848
|
+
demo,
|
|
849
|
+
shuffle: options.shuffle !== false,
|
|
850
|
+
vibrate,
|
|
851
|
+
ready: options.ready !== false,
|
|
852
|
+
modes: options.modeSwitch !== false
|
|
853
|
+
});
|
|
676
854
|
const $ = (r) => {
|
|
677
855
|
const el = container.querySelector(`[data-r="${r}"]`);
|
|
678
856
|
if (!el) throw new Error(`[packing-bingo] internal: missing element "${r}"`);
|
|
679
857
|
return el;
|
|
680
858
|
};
|
|
859
|
+
const root = container.querySelector(".pkb");
|
|
681
860
|
const board = container.querySelector(".pkb-board");
|
|
682
861
|
const gw = container.querySelector(".pkb-gw");
|
|
683
862
|
const grid = container.querySelector(".pkb-grid");
|
|
@@ -689,21 +868,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
689
868
|
const freeTile = L.center >= 0 ? cellTiles[L.center] : null;
|
|
690
869
|
const ins = tiles.map((t) => t.querySelector("input"));
|
|
691
870
|
const stamps = tiles.map((t) => t.querySelector(".pkb-stamp"));
|
|
692
|
-
const kick = $("kick"), snd = $("snd"), perfectEl = $("perfect");
|
|
693
|
-
const playDialog = container.querySelector('[data-r="play"]');
|
|
694
|
-
const beforePlay = (event) => {
|
|
695
|
-
if (!playDialog || playReady.has(container.ownerDocument) || typeof playDialog.showModal !== "function" || !(event.target instanceof Element) || !event.target.closest(".pkb-tile:not(.pkb-free)")) return;
|
|
696
|
-
event.preventDefault();
|
|
697
|
-
event.stopImmediatePropagation();
|
|
698
|
-
playDialog.showModal();
|
|
699
|
-
};
|
|
700
|
-
grid.addEventListener("click", beforePlay, true);
|
|
701
|
-
playDialog?.querySelectorAll("button").forEach((button) => {
|
|
702
|
-
button.addEventListener("click", () => playDialog.close());
|
|
703
|
-
});
|
|
704
|
-
playDialog?.addEventListener("close", () => {
|
|
705
|
-
if (!dead) playReady.add(container.ownerDocument);
|
|
706
|
-
});
|
|
871
|
+
const kick = $("kick"), snd = $("snd"), perfectEl = $("perfect"), mfx = $("mfx");
|
|
707
872
|
let checked = normalize(void 0);
|
|
708
873
|
let cur = [];
|
|
709
874
|
let prevLines = /* @__PURE__ */ new Set();
|
|
@@ -739,7 +904,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
739
904
|
cv.width = Math.round(BW * dpr);
|
|
740
905
|
cv.height = Math.round(BH * dpr);
|
|
741
906
|
};
|
|
742
|
-
const piece = (x, y, vx, vy, g, s, life, shape2) => ({
|
|
907
|
+
const piece = (x, y, vx, vy, g, s, life, shape2, colors = CONFETTI) => ({
|
|
743
908
|
x,
|
|
744
909
|
y,
|
|
745
910
|
vx,
|
|
@@ -751,7 +916,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
751
916
|
h: (3 + Math.random() * 5) * s,
|
|
752
917
|
r: Math.random() * 6,
|
|
753
918
|
vr: (Math.random() - 0.5) * 0.35,
|
|
754
|
-
c:
|
|
919
|
+
c: colors[Math.random() * colors.length | 0]
|
|
755
920
|
});
|
|
756
921
|
const go = () => {
|
|
757
922
|
if (!raf && ctx) raf = requestAnimationFrame(tick);
|
|
@@ -762,7 +927,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
762
927
|
const a = (o.angle ?? -Math.PI / 2) + (Math.random() - 0.5) * (o.spread ?? Math.PI * 2);
|
|
763
928
|
const v = (o.speed ?? 10) * (0.35 + Math.random() * 0.85);
|
|
764
929
|
const sh = Math.random() < (o.star ?? 0.15) ? "star" : Math.random() < 0.3 ? "round" : "rect";
|
|
765
|
-
P.push(piece(x, y, Math.cos(a) * v, Math.sin(a) * v, o.g ?? 0.28, o.size ?? 1, (o.life ?? 110) + Math.random() * 40, sh));
|
|
930
|
+
P.push(piece(x, y, Math.cos(a) * v, Math.sin(a) * v, o.g ?? 0.28, o.size ?? 1, (o.life ?? 110) + Math.random() * 40, sh, o.colors));
|
|
766
931
|
}
|
|
767
932
|
go();
|
|
768
933
|
}
|
|
@@ -860,6 +1025,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
860
1025
|
if (e.animationName === "pkb-stamp-hit") t.classList.remove("pkb-hit");
|
|
861
1026
|
if (t.classList.contains("pkb-tile") && (e.animationName === "pkb-hop" || e.animationName === "pkb-squash"))
|
|
862
1027
|
t.classList.remove(e.animationName);
|
|
1028
|
+
if (t.classList.contains("pkb-tile") && e.animationName === "pkb-clr") t.classList.remove("pkb-clr");
|
|
863
1029
|
});
|
|
864
1030
|
const ring = (t) => {
|
|
865
1031
|
const r = document.createElement("span");
|
|
@@ -878,20 +1044,20 @@ function createPackingBingo(container, options = {}) {
|
|
|
878
1044
|
board.appendChild(el);
|
|
879
1045
|
el.addEventListener("animationend", () => el.remove());
|
|
880
1046
|
};
|
|
881
|
-
const prog = (t,
|
|
1047
|
+
const prog = (t, t02, d) => Math.max(0, Math.min(1, (t - t02) / d));
|
|
882
1048
|
const ease = (k) => 1 - Math.pow(1 - k, 3);
|
|
883
1049
|
let tw = 0;
|
|
884
1050
|
function countScore() {
|
|
885
1051
|
cancelAnimationFrame(tw);
|
|
886
|
-
const a = shown, b = score,
|
|
1052
|
+
const a = shown, b = score, t02 = performance.now();
|
|
887
1053
|
const st = (t) => {
|
|
888
1054
|
if (dead) return;
|
|
889
|
-
const k = prog(t,
|
|
1055
|
+
const k = prog(t, t02, 450);
|
|
890
1056
|
shown = Math.round(a + (b - a) * ease(k));
|
|
891
1057
|
$("sc").textContent = shown.toLocaleString();
|
|
892
1058
|
if (k < 1) tw = requestAnimationFrame(st);
|
|
893
1059
|
};
|
|
894
|
-
st(
|
|
1060
|
+
st(t02);
|
|
895
1061
|
if (!calm) replay($("sp"), "pkb-bump");
|
|
896
1062
|
}
|
|
897
1063
|
const showGain = (pts, x, y, cls = "pt") => {
|
|
@@ -899,12 +1065,12 @@ function createPackingBingo(container, options = {}) {
|
|
|
899
1065
|
countScore();
|
|
900
1066
|
};
|
|
901
1067
|
function countUp(el, a, b, d, fmt, ticks = false) {
|
|
902
|
-
const
|
|
1068
|
+
const t02 = performance.now();
|
|
903
1069
|
const g = gen;
|
|
904
1070
|
let lt = 0;
|
|
905
1071
|
const st = (t) => {
|
|
906
1072
|
if (dead || g !== gen) return;
|
|
907
|
-
const k = prog(t,
|
|
1073
|
+
const k = prog(t, t02, d);
|
|
908
1074
|
el.textContent = fmt(Math.round(a + (b - a) * ease(k)));
|
|
909
1075
|
if (ticks && k < 1 && t - lt > 55) {
|
|
910
1076
|
sfx.tick();
|
|
@@ -913,7 +1079,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
913
1079
|
if (k < 1) requestAnimationFrame(st);
|
|
914
1080
|
else if (!calm) replay(el, "pkb-bump");
|
|
915
1081
|
};
|
|
916
|
-
st(
|
|
1082
|
+
st(t02);
|
|
917
1083
|
}
|
|
918
1084
|
const ctr = (t) => {
|
|
919
1085
|
return { x: t.offsetLeft + t.offsetWidth / 2, y: t.offsetTop + t.offsetHeight / 2 };
|
|
@@ -932,6 +1098,120 @@ function createPackingBingo(container, options = {}) {
|
|
|
932
1098
|
return `<g${fresh.includes(k) ? ' class="pkb-draw"' : ""}><line class="pkb-u" ${at}/><line class="pkb-o" ${at}/></g>`;
|
|
933
1099
|
}).join("");
|
|
934
1100
|
}
|
|
1101
|
+
const readyEl = container.querySelector('[data-r="ready"]');
|
|
1102
|
+
const watch = $("watch");
|
|
1103
|
+
let phase = "idle";
|
|
1104
|
+
let t0 = 0, watchTimer = 0;
|
|
1105
|
+
let finalMs = null;
|
|
1106
|
+
const cdTimers = /* @__PURE__ */ new Set();
|
|
1107
|
+
const cd = (fn, ms) => {
|
|
1108
|
+
const id = window.setTimeout(() => {
|
|
1109
|
+
cdTimers.delete(id);
|
|
1110
|
+
if (!dead) fn();
|
|
1111
|
+
}, ms);
|
|
1112
|
+
cdTimers.add(id);
|
|
1113
|
+
};
|
|
1114
|
+
const stopTimers = () => {
|
|
1115
|
+
cdTimers.forEach((id) => clearTimeout(id));
|
|
1116
|
+
cdTimers.clear();
|
|
1117
|
+
clearInterval(watchTimer);
|
|
1118
|
+
};
|
|
1119
|
+
const elapsed = () => phase === "play" ? performance.now() - t0 : finalMs ?? 0;
|
|
1120
|
+
const showWatch = () => {
|
|
1121
|
+
watch.textContent = formatTime(elapsed());
|
|
1122
|
+
};
|
|
1123
|
+
function startWatch() {
|
|
1124
|
+
phase = "play";
|
|
1125
|
+
t0 = performance.now();
|
|
1126
|
+
finalMs = null;
|
|
1127
|
+
clearInterval(watchTimer);
|
|
1128
|
+
watchTimer = window.setInterval(showWatch, 100);
|
|
1129
|
+
watch.classList.remove("pkb-done");
|
|
1130
|
+
watch.classList.add("pkb-run");
|
|
1131
|
+
showWatch();
|
|
1132
|
+
syncFin();
|
|
1133
|
+
}
|
|
1134
|
+
const finBtn = $("fin");
|
|
1135
|
+
const shufBtn = container.querySelector('[data-r="shuf"]');
|
|
1136
|
+
const syncFin = () => {
|
|
1137
|
+
finBtn.disabled = !(inf() && phase === "play");
|
|
1138
|
+
if (shufBtn) shufBtn.disabled = inf() && phase === "done";
|
|
1139
|
+
};
|
|
1140
|
+
function finish() {
|
|
1141
|
+
if (phase === "play") {
|
|
1142
|
+
finalMs = performance.now() - t0;
|
|
1143
|
+
watch.classList.replace("pkb-run", "pkb-done");
|
|
1144
|
+
} else if (phase !== "done") {
|
|
1145
|
+
stopTimers();
|
|
1146
|
+
openGrid(false);
|
|
1147
|
+
}
|
|
1148
|
+
phase = "done";
|
|
1149
|
+
clearInterval(watchTimer);
|
|
1150
|
+
showWatch();
|
|
1151
|
+
}
|
|
1152
|
+
function openGrid(fade) {
|
|
1153
|
+
grid.removeAttribute("inert");
|
|
1154
|
+
if (!readyEl || readyEl.hidden) return;
|
|
1155
|
+
const focused = document.activeElement === readyEl;
|
|
1156
|
+
readyEl.classList.add("pkb-gone");
|
|
1157
|
+
board.classList.remove("pkb-waiting");
|
|
1158
|
+
readyEl.tabIndex = -1;
|
|
1159
|
+
if (focused) grid.querySelector("input")?.focus({ preventScroll: true });
|
|
1160
|
+
if (fade) cd(() => readyEl.hidden = true, GO_MS);
|
|
1161
|
+
else readyEl.hidden = true;
|
|
1162
|
+
}
|
|
1163
|
+
function toReady() {
|
|
1164
|
+
stopTimers();
|
|
1165
|
+
finalMs = null;
|
|
1166
|
+
watch.classList.remove("pkb-run", "pkb-done");
|
|
1167
|
+
phase = readyEl ? "ready" : "idle";
|
|
1168
|
+
showWatch();
|
|
1169
|
+
syncFin();
|
|
1170
|
+
board.classList.remove("pkb-over");
|
|
1171
|
+
grid.removeAttribute("inert");
|
|
1172
|
+
if (!readyEl) return;
|
|
1173
|
+
readyEl.hidden = false;
|
|
1174
|
+
readyEl.removeAttribute("tabindex");
|
|
1175
|
+
readyEl.classList.remove("pkb-gone", "pkb-counting");
|
|
1176
|
+
board.classList.add("pkb-waiting");
|
|
1177
|
+
$("count").textContent = "";
|
|
1178
|
+
grid.setAttribute("inert", "");
|
|
1179
|
+
}
|
|
1180
|
+
let resetAsked = false;
|
|
1181
|
+
const restartGame = () => {
|
|
1182
|
+
if (!controlled || !checked.some(Boolean)) toReady();
|
|
1183
|
+
else resetAsked = true;
|
|
1184
|
+
};
|
|
1185
|
+
function skipReady() {
|
|
1186
|
+
if (phase === "play" || phase === "done") return;
|
|
1187
|
+
stopTimers();
|
|
1188
|
+
openGrid(false);
|
|
1189
|
+
startWatch();
|
|
1190
|
+
}
|
|
1191
|
+
function countdown() {
|
|
1192
|
+
if (phase !== "ready") return;
|
|
1193
|
+
phase = "count";
|
|
1194
|
+
readyEl.classList.add("pkb-counting");
|
|
1195
|
+
const count = $("count");
|
|
1196
|
+
const words = ["3", "2", "1", M.readyGo?.trim() || ja.readyGo];
|
|
1197
|
+
words.forEach((word, k) => {
|
|
1198
|
+
const go2 = k === words.length - 1;
|
|
1199
|
+
const step = () => {
|
|
1200
|
+
count.textContent = word;
|
|
1201
|
+
count.classList.toggle("pkb-go", go2);
|
|
1202
|
+
replay(count, "pkb-tick");
|
|
1203
|
+
sfx.count(go2);
|
|
1204
|
+
haptics.play(go2 ? "go" : "count");
|
|
1205
|
+
if (go2) {
|
|
1206
|
+
startWatch();
|
|
1207
|
+
openGrid(true);
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
if (k) cd(step, k * COUNT_MS);
|
|
1211
|
+
else step();
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
readyEl?.addEventListener("click", countdown);
|
|
935
1215
|
function tapFx(i, on, moment, fresh) {
|
|
936
1216
|
const p = rel(tiles[i]);
|
|
937
1217
|
if (!on) {
|
|
@@ -983,7 +1263,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
983
1263
|
});
|
|
984
1264
|
later(() => sfx.reach(), 150);
|
|
985
1265
|
}
|
|
986
|
-
function bingo(fresh, pts) {
|
|
1266
|
+
function bingo(fresh, pts, total) {
|
|
987
1267
|
const nl = fresh.length;
|
|
988
1268
|
const seq = [...new Set(fresh.flatMap((k) => cellLines[k]))].map((c) => cellTiles[c]);
|
|
989
1269
|
const step = Math.min(70, 300 / seq.length);
|
|
@@ -997,7 +1277,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
997
1277
|
);
|
|
998
1278
|
later(() => {
|
|
999
1279
|
$("bT").textContent = nl >= 3 ? M.bannerTriple : nl === 2 ? M.bannerDouble : M.bannerSingle;
|
|
1000
|
-
$("bS").textContent = nl > 1 ? M.bannerMulti(nl) : M.bannerNth(
|
|
1280
|
+
$("bS").textContent = nl > 1 ? M.bannerMulti(nl) : M.bannerNth(total);
|
|
1001
1281
|
replay($("banner"), "pkb-show");
|
|
1002
1282
|
if (!calm) {
|
|
1003
1283
|
replay($("flash"), "pkb-go");
|
|
@@ -1014,12 +1294,16 @@ function createPackingBingo(container, options = {}) {
|
|
|
1014
1294
|
perfectPending = true;
|
|
1015
1295
|
perfectEl.classList.remove("pkb-settled");
|
|
1016
1296
|
const bonus = gotP ? 0 : 1e3;
|
|
1297
|
+
const speed = gotP || finalMs == null ? 0 : speedBonus(N, finalMs);
|
|
1017
1298
|
gotP = true;
|
|
1018
|
-
score += bonus;
|
|
1299
|
+
score += bonus + speed;
|
|
1019
1300
|
cellTiles.forEach((t, c) => hop(t, far(c) * 180 / Math.max(1, n - 1)));
|
|
1020
1301
|
sfx.perfect();
|
|
1021
1302
|
if (!calm) replay($("flash"), "pkb-go");
|
|
1303
|
+
setResult(false);
|
|
1022
1304
|
$("pbody").textContent = perfectText(M, N);
|
|
1305
|
+
$("ptime").hidden = finalMs == null;
|
|
1306
|
+
$("pT").textContent = formatTime(finalMs ?? 0, 2);
|
|
1023
1307
|
$("sScore").textContent = "0";
|
|
1024
1308
|
$("sCombo").textContent = "\xD70";
|
|
1025
1309
|
$("sBingo").textContent = "0";
|
|
@@ -1035,9 +1319,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
1035
1319
|
burst(BW, BH, 90, { angle: -Math.PI * 2 / 3, spread: 0.8, speed: 22, g: 0.32, star: 0.3 });
|
|
1036
1320
|
rain(3500);
|
|
1037
1321
|
if (bonus) floatText(M.perfectBonus(bonus), BW / 2, BH * 0.16, "big");
|
|
1322
|
+
const gap = Math.min(34, Math.max(14, BW * 0.056)) * 1.3 + 6;
|
|
1323
|
+
if (speed) floatText((M.speedBonus ?? ja.speedBonus)(speed), BW / 2, Math.max(gap / 2 + 4, BH * 0.16 - gap), "big");
|
|
1038
1324
|
countUp($("sScore"), 0, score, 1300, (v) => v.toLocaleString(), true);
|
|
1039
1325
|
countUp($("sCombo"), 0, maxCombo, 700, (v) => "\xD7" + v);
|
|
1040
|
-
countUp($("sBingo"), 0,
|
|
1326
|
+
countUp($("sBingo"), 0, resultLines(), 700, String);
|
|
1041
1327
|
countScore();
|
|
1042
1328
|
}, PERFECT_STAMP_DELAY_MS);
|
|
1043
1329
|
later(() => emojiRain(3e3), 1500);
|
|
@@ -1048,10 +1334,67 @@ function createPackingBingo(container, options = {}) {
|
|
|
1048
1334
|
beat?.(pulse);
|
|
1049
1335
|
}, at)
|
|
1050
1336
|
);
|
|
1051
|
-
const e = { score, maxCombo };
|
|
1337
|
+
const e = { score, maxCombo, time: finalMs == null ? null : Math.round(finalMs), speedBonus: speed };
|
|
1052
1338
|
events.push(() => options.onPerfect?.(e));
|
|
1053
1339
|
}
|
|
1340
|
+
function clearNow(animate) {
|
|
1341
|
+
clearTimeout(clearTimer);
|
|
1342
|
+
clearTimer = 0;
|
|
1343
|
+
if (!pending.size) return;
|
|
1344
|
+
const gone = [...pending].filter((i) => checked[i]);
|
|
1345
|
+
pending.clear();
|
|
1346
|
+
checked = checked.slice();
|
|
1347
|
+
gone.forEach((i) => {
|
|
1348
|
+
checked[i] = false;
|
|
1349
|
+
ins[i].checked = false;
|
|
1350
|
+
got.delete(i);
|
|
1351
|
+
});
|
|
1352
|
+
gotL.clear();
|
|
1353
|
+
const left = completeLines();
|
|
1354
|
+
left.forEach((k) => gotL.add(lineKey(lines[k])));
|
|
1355
|
+
prevLines = new Set(left);
|
|
1356
|
+
render(null, true);
|
|
1357
|
+
if (!animate || !gone.length) return;
|
|
1358
|
+
sfx.clear(gone.length);
|
|
1359
|
+
gone.forEach((i, j) => {
|
|
1360
|
+
const t = tiles[i];
|
|
1361
|
+
if (!calm) {
|
|
1362
|
+
t.style.setProperty("--pkb-cd", Math.min(j * 28, 280) + "ms");
|
|
1363
|
+
t.classList.remove("pkb-squash", "pkb-hop");
|
|
1364
|
+
replay(t, "pkb-clr");
|
|
1365
|
+
}
|
|
1366
|
+
const p = rel(t);
|
|
1367
|
+
burst(p.x, p.y, 9, { speed: 4.5, g: 0.06, life: 45, size: 0.75, star: 1, colors: SPARK.slice(1) });
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
function scheduleClear(ms) {
|
|
1371
|
+
const at = performance.now() + ms;
|
|
1372
|
+
if (clearTimer && at <= clearAt) return;
|
|
1373
|
+
clearTimeout(clearTimer);
|
|
1374
|
+
clearAt = at;
|
|
1375
|
+
clearTimer = window.setTimeout(() => {
|
|
1376
|
+
clearTimer = 0;
|
|
1377
|
+
if (!dead) clearNow(true);
|
|
1378
|
+
}, ms);
|
|
1379
|
+
}
|
|
1380
|
+
const dropClear = () => {
|
|
1381
|
+
clearTimeout(clearTimer);
|
|
1382
|
+
clearTimer = 0;
|
|
1383
|
+
pending.clear();
|
|
1384
|
+
};
|
|
1385
|
+
function setResult(fin) {
|
|
1386
|
+
const title = fin ? M.finishTitle?.trim() || ja.finishTitle : M.perfectTitle;
|
|
1387
|
+
perfectEl.classList.toggle("pkb-fin", fin);
|
|
1388
|
+
$("pcard").setAttribute("aria-label", title);
|
|
1389
|
+
$("medal").textContent = fin ? M.finishStamp?.trim() || ja.finishStamp : M.perfectStamp;
|
|
1390
|
+
$("ptitle").textContent = title;
|
|
1391
|
+
$("pTl").textContent = fin ? M.finishTime?.trim() || ja.finishTime : M.statTime?.trim() || ja.statTime;
|
|
1392
|
+
$("again").hidden = !fin;
|
|
1393
|
+
}
|
|
1394
|
+
const resultLines = () => inf() ? cleared : cur.length;
|
|
1395
|
+
const resultFocus = () => $(perfectEl.classList.contains("pkb-fin") ? "again" : "close");
|
|
1054
1396
|
function cancelFx() {
|
|
1397
|
+
if (pending.size) clearNow(false);
|
|
1055
1398
|
gen++;
|
|
1056
1399
|
perfectPending = false;
|
|
1057
1400
|
timers.forEach((id) => clearTimeout(id));
|
|
@@ -1072,21 +1415,32 @@ function createPackingBingo(container, options = {}) {
|
|
|
1072
1415
|
$("flash").classList.remove("pkb-go");
|
|
1073
1416
|
$("combo").classList.remove("pkb-on");
|
|
1074
1417
|
$("cbar").classList.remove("pkb-draining");
|
|
1075
|
-
cellTiles.forEach((t) => t.classList.remove("pkb-squash", "pkb-hop"));
|
|
1418
|
+
cellTiles.forEach((t) => t.classList.remove("pkb-squash", "pkb-hop", "pkb-clr"));
|
|
1076
1419
|
stamps.forEach((s) => s.classList.remove("pkb-hit"));
|
|
1077
|
-
board.classList.remove("pkb-punch", "pkb-shake");
|
|
1420
|
+
board.classList.remove("pkb-punch", "pkb-shake", "pkb-switching");
|
|
1421
|
+
mfx.classList.remove("pkb-show");
|
|
1078
1422
|
if (!keepPerfect) perfectEl.classList.remove("pkb-show", "pkb-settled");
|
|
1079
1423
|
}
|
|
1080
1424
|
const completeLines = () => lines.flatMap((l, k) => l.every((i) => checked[i]) ? [k] : []);
|
|
1081
1425
|
function render(src, quiet) {
|
|
1082
1426
|
const events = [];
|
|
1083
1427
|
const on = checked, c = on.filter(Boolean).length;
|
|
1428
|
+
const endless = inf();
|
|
1429
|
+
const all = c === N && !endless;
|
|
1084
1430
|
cur = completeLines();
|
|
1085
1431
|
const fresh = cur.filter((k) => !prevLines.has(k));
|
|
1086
1432
|
if ([...prevLines].some((k) => !cur.includes(k))) {
|
|
1087
1433
|
cancelFx();
|
|
1088
1434
|
if (c < N) perfectEl.classList.remove("pkb-show");
|
|
1089
1435
|
}
|
|
1436
|
+
if (endless) {
|
|
1437
|
+
if (!quiet) cleared += fresh.length;
|
|
1438
|
+
if (cur.length) {
|
|
1439
|
+
const known = pending.size;
|
|
1440
|
+
cur.forEach((k) => lines[k].forEach((i) => pending.add(i)));
|
|
1441
|
+
if (pending.size > known || !clearTimer) scheduleClear(!quiet && fresh.length ? CLEAR_MS : FLY_MS + 200);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1090
1444
|
const hit = new Set(cur.flatMap((k) => cellLines[k]));
|
|
1091
1445
|
const reach = /* @__PURE__ */ new Set();
|
|
1092
1446
|
for (const l of lines) {
|
|
@@ -1095,26 +1449,31 @@ function createPackingBingo(container, options = {}) {
|
|
|
1095
1449
|
}
|
|
1096
1450
|
tiles.forEach((t, i) => {
|
|
1097
1451
|
t.classList.toggle("pkb-on", on[i]);
|
|
1452
|
+
const lock = on[i] && (!uncheck || pending.has(i));
|
|
1453
|
+
t.classList.toggle("pkb-lock", lock);
|
|
1454
|
+
if (lock) ins[i].setAttribute("aria-readonly", "true");
|
|
1455
|
+
else ins[i].removeAttribute("aria-readonly");
|
|
1098
1456
|
t.classList.toggle("pkb-line", hit.has(L.itemCell[slotOf[i]]));
|
|
1099
1457
|
t.classList.toggle("pkb-reach", reach.has(i));
|
|
1100
1458
|
});
|
|
1101
1459
|
freeTile?.classList.toggle("pkb-line", hit.has(L.center));
|
|
1102
|
-
board.classList.toggle("pkb-all",
|
|
1103
|
-
kick.classList.toggle("pkb-gold",
|
|
1104
|
-
kick.textContent =
|
|
1460
|
+
board.classList.toggle("pkb-all", all);
|
|
1461
|
+
kick.classList.toggle("pkb-gold", all);
|
|
1462
|
+
kick.textContent = all ? M.perfectTag : M.tag;
|
|
1105
1463
|
$("cnt").textContent = `${c}/${N}`;
|
|
1106
|
-
|
|
1464
|
+
const total = endless ? cleared : cur.length;
|
|
1465
|
+
$("bng").textContent = String(total);
|
|
1107
1466
|
const bp = $("bp");
|
|
1108
|
-
if (
|
|
1467
|
+
if (total > lastN) {
|
|
1109
1468
|
bp.classList.remove("pkb-hot");
|
|
1110
1469
|
void bp.offsetWidth;
|
|
1111
1470
|
}
|
|
1112
|
-
bp.classList.toggle("pkb-hot",
|
|
1113
|
-
lastN =
|
|
1471
|
+
bp.classList.toggle("pkb-hot", total > 0);
|
|
1472
|
+
lastN = total;
|
|
1114
1473
|
drawLines(fresh);
|
|
1115
1474
|
if (src !== null) {
|
|
1116
1475
|
const newReach = [...reach].some((i) => !prevReach.has(i));
|
|
1117
|
-
const moment = !quiet &&
|
|
1476
|
+
const moment = !quiet && all && !prevAll ? "perfect" : !quiet && fresh.length ? "bingo" : !quiet && newReach ? "reach" : "tap";
|
|
1118
1477
|
tapFx(src, on[src], moment, fresh);
|
|
1119
1478
|
}
|
|
1120
1479
|
const newL = fresh.map((k) => lineKey(lines[k])).filter((key) => !gotL.has(key));
|
|
@@ -1123,10 +1482,10 @@ function createPackingBingo(container, options = {}) {
|
|
|
1123
1482
|
score += linePts;
|
|
1124
1483
|
if (quiet) {
|
|
1125
1484
|
if (linePts) countScore();
|
|
1126
|
-
} else if (
|
|
1485
|
+
} else if (all && !prevAll) perfect(events);
|
|
1127
1486
|
else if (fresh.length) {
|
|
1128
|
-
bingo(fresh, linePts);
|
|
1129
|
-
const e = { newLines: fresh, totalLines:
|
|
1487
|
+
bingo(fresh, linePts, total);
|
|
1488
|
+
const e = { newLines: fresh, totalLines: total, score, mode };
|
|
1130
1489
|
events.push(() => options.onBingo?.(e));
|
|
1131
1490
|
} else if (src !== null && on[src]) {
|
|
1132
1491
|
const nr = [...reach].filter((i) => !prevReach.has(i));
|
|
@@ -1134,9 +1493,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
1134
1493
|
}
|
|
1135
1494
|
prevLines = new Set(cur);
|
|
1136
1495
|
prevReach = reach;
|
|
1137
|
-
prevAll =
|
|
1496
|
+
prevAll = all;
|
|
1138
1497
|
const labels = [...reach].map((i) => items[i].label);
|
|
1139
|
-
$("msg").textContent =
|
|
1498
|
+
$("msg").textContent = all ? M.statusDone : labels.length ? M.statusReach(labels) : cur.length ? M.statusLines(cur.length, N - c) : M.statusStart(N - c);
|
|
1140
1499
|
return events;
|
|
1141
1500
|
}
|
|
1142
1501
|
const flush = (events) => {
|
|
@@ -1146,9 +1505,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
1146
1505
|
}
|
|
1147
1506
|
};
|
|
1148
1507
|
const save = () => {
|
|
1149
|
-
if (storageKey) writeStore(storageKey, JSON.stringify(checked));
|
|
1508
|
+
if (storageKey && !inf()) writeStore(storageKey, JSON.stringify(checked));
|
|
1150
1509
|
};
|
|
1151
1510
|
function apply(next, src, quiet) {
|
|
1511
|
+
if (phase === "idle" && src !== null && next[src]) startWatch();
|
|
1512
|
+
if (!inf() && next.every(Boolean)) finish();
|
|
1152
1513
|
checked = next;
|
|
1153
1514
|
ins.forEach((x, i) => x.checked = checked[i]);
|
|
1154
1515
|
const events = render(src, quiet);
|
|
@@ -1156,8 +1517,12 @@ function createPackingBingo(container, options = {}) {
|
|
|
1156
1517
|
return events;
|
|
1157
1518
|
}
|
|
1158
1519
|
function baseline(next) {
|
|
1520
|
+
dropClear();
|
|
1521
|
+
cleared = 0;
|
|
1159
1522
|
cancelFx();
|
|
1160
1523
|
clearVisuals();
|
|
1524
|
+
if (!inf() && next.every(Boolean)) finish();
|
|
1525
|
+
else if ((phase === "play" || phase === "done") && !next.some(Boolean)) toReady();
|
|
1161
1526
|
checked = next;
|
|
1162
1527
|
ins.forEach((x, i) => x.checked = checked[i]);
|
|
1163
1528
|
got.clear();
|
|
@@ -1167,7 +1532,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1167
1532
|
done.forEach((k) => gotL.add(lineKey(lines[k])));
|
|
1168
1533
|
prevLines = new Set(done);
|
|
1169
1534
|
prevReach = /* @__PURE__ */ new Set();
|
|
1170
|
-
prevAll = checked.every(Boolean);
|
|
1535
|
+
prevAll = !inf() && checked.every(Boolean);
|
|
1171
1536
|
gotP = prevAll;
|
|
1172
1537
|
score = shown = got.size * 10 + gotL.size * 100;
|
|
1173
1538
|
combo = 0;
|
|
@@ -1177,7 +1542,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
1177
1542
|
save();
|
|
1178
1543
|
}
|
|
1179
1544
|
function request(next, src, opts = {}) {
|
|
1180
|
-
|
|
1545
|
+
const game = inf();
|
|
1546
|
+
if (controlled && !game) {
|
|
1547
|
+
resetAsked = !!opts.reset && resetAsked;
|
|
1181
1548
|
ins.forEach((x, i) => x.checked = checked[i]);
|
|
1182
1549
|
options.onChange?.(next.slice());
|
|
1183
1550
|
return;
|
|
@@ -1185,7 +1552,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1185
1552
|
let events = [];
|
|
1186
1553
|
if (opts.reset) baseline(next);
|
|
1187
1554
|
else events = apply(next, src, !!opts.quiet);
|
|
1188
|
-
options.onChange?.(checked.slice());
|
|
1555
|
+
if (!game) options.onChange?.(checked.slice());
|
|
1189
1556
|
flush(events);
|
|
1190
1557
|
}
|
|
1191
1558
|
function place(next) {
|
|
@@ -1204,12 +1571,12 @@ function createPackingBingo(container, options = {}) {
|
|
|
1204
1571
|
focused.focus({ preventScroll: true });
|
|
1205
1572
|
}
|
|
1206
1573
|
const saveOrder = () => {
|
|
1207
|
-
if (!orderKey) return;
|
|
1574
|
+
if (!orderKey || inf()) return;
|
|
1208
1575
|
writeStore(orderKey, JSON.stringify(order));
|
|
1209
1576
|
};
|
|
1210
1577
|
let flights = [];
|
|
1211
1578
|
function arrange(next) {
|
|
1212
|
-
if (!checked.every(Boolean)) cancelFx();
|
|
1579
|
+
if (inf() || !checked.every(Boolean)) cancelFx();
|
|
1213
1580
|
const from = calm ? null : tiles.map((t) => t.getBoundingClientRect());
|
|
1214
1581
|
flights.forEach((a) => a.cancel());
|
|
1215
1582
|
flights = [];
|
|
@@ -1245,6 +1612,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1245
1612
|
svg.innerHTML = "";
|
|
1246
1613
|
}
|
|
1247
1614
|
function shuffleCard() {
|
|
1615
|
+
if (inf() && phase === "done") return;
|
|
1248
1616
|
let next = order.slice();
|
|
1249
1617
|
do {
|
|
1250
1618
|
for (let s = N - 1; s > 0; s--) {
|
|
@@ -1259,26 +1627,93 @@ function createPackingBingo(container, options = {}) {
|
|
|
1259
1627
|
const unshuffle = () => {
|
|
1260
1628
|
if (!isIdentity(order)) arrange(identity());
|
|
1261
1629
|
};
|
|
1630
|
+
const locked = (i) => checked[i] && (!uncheck || pending.has(i));
|
|
1631
|
+
grid.addEventListener("click", (e) => {
|
|
1632
|
+
const i = ins.indexOf(e.target);
|
|
1633
|
+
if (i >= 0 && locked(i)) e.preventDefault();
|
|
1634
|
+
});
|
|
1262
1635
|
grid.addEventListener("change", (e) => {
|
|
1263
1636
|
const i = ins.indexOf(e.target);
|
|
1264
1637
|
if (i < 0) return;
|
|
1638
|
+
if (locked(i)) {
|
|
1639
|
+
ins[i].checked = true;
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1265
1642
|
const next = checked.slice();
|
|
1266
1643
|
next[i] = !next[i];
|
|
1267
1644
|
request(next, i);
|
|
1268
1645
|
});
|
|
1269
1646
|
container.querySelector('[data-r="shuf"]')?.addEventListener("click", shuffleCard);
|
|
1647
|
+
const restartInfinite = () => {
|
|
1648
|
+
baseline(normalize(void 0));
|
|
1649
|
+
toReady();
|
|
1650
|
+
};
|
|
1270
1651
|
$("reset").addEventListener("click", () => {
|
|
1652
|
+
if (inf()) return restartInfinite();
|
|
1271
1653
|
unshuffle();
|
|
1654
|
+
restartGame();
|
|
1272
1655
|
request(normalize(void 0), null, { reset: true });
|
|
1273
1656
|
});
|
|
1274
1657
|
$("close").addEventListener("click", () => perfectEl.classList.remove("pkb-show"));
|
|
1658
|
+
$("again").addEventListener("click", () => {
|
|
1659
|
+
if (!inf()) return;
|
|
1660
|
+
restartInfinite();
|
|
1661
|
+
(readyEl && !readyEl.hidden ? readyEl : grid.querySelector("input"))?.focus({ preventScroll: true });
|
|
1662
|
+
});
|
|
1663
|
+
function finishGame() {
|
|
1664
|
+
if (dead || !inf() || phase !== "play") return;
|
|
1665
|
+
cancelFx();
|
|
1666
|
+
clearVisuals();
|
|
1667
|
+
finalMs = performance.now() - t0;
|
|
1668
|
+
phase = "done";
|
|
1669
|
+
clearInterval(watchTimer);
|
|
1670
|
+
watch.classList.replace("pkb-run", "pkb-done");
|
|
1671
|
+
showWatch();
|
|
1672
|
+
syncFin();
|
|
1673
|
+
combo = 0;
|
|
1674
|
+
grid.setAttribute("inert", "");
|
|
1675
|
+
board.classList.add("pkb-over");
|
|
1676
|
+
setResult(true);
|
|
1677
|
+
$("pbody").textContent = "";
|
|
1678
|
+
$("ptime").hidden = false;
|
|
1679
|
+
$("pT").textContent = formatTime(finalMs, 2);
|
|
1680
|
+
$("sScore").textContent = "0";
|
|
1681
|
+
$("sCombo").textContent = "\xD70";
|
|
1682
|
+
$("sBingo").textContent = "0";
|
|
1683
|
+
perfectEl.classList.remove("pkb-settled");
|
|
1684
|
+
replay(perfectEl, "pkb-show");
|
|
1685
|
+
resultFocus().focus({ preventScroll: true });
|
|
1686
|
+
sfx.perfect();
|
|
1687
|
+
const beat = haptics.play("finish");
|
|
1688
|
+
if (!calm) replay($("flash"), "pkb-go");
|
|
1689
|
+
later(() => {
|
|
1690
|
+
if (!calm) replay(board, "pkb-shake");
|
|
1691
|
+
sfx.boom();
|
|
1692
|
+
beat?.(PERFECT_STAMP_BEAT);
|
|
1693
|
+
burst(0, BH, 80, { angle: -Math.PI / 3, spread: 0.8, speed: 21, g: 0.32, star: 0.5, colors: SPARK });
|
|
1694
|
+
burst(BW, BH, 80, { angle: -Math.PI * 2 / 3, spread: 0.8, speed: 21, g: 0.32, star: 0.5, colors: SPARK });
|
|
1695
|
+
rain(2500);
|
|
1696
|
+
countUp($("sScore"), 0, score, 1300, (v) => v.toLocaleString(), true);
|
|
1697
|
+
countUp($("sCombo"), 0, maxCombo, 700, (v) => "\xD7" + v);
|
|
1698
|
+
countUp($("sBingo"), 0, cleared, 700, String);
|
|
1699
|
+
}, PERFECT_STAMP_DELAY_MS);
|
|
1700
|
+
[1250, 1700, 2200].forEach(
|
|
1701
|
+
(at, k) => later(() => {
|
|
1702
|
+
firework(BW * (0.15 + Math.random() * 0.7), BH * (0.12 + Math.random() * 0.3));
|
|
1703
|
+
beat?.(PERFECT_FIREWORKS[k * 2].beat);
|
|
1704
|
+
}, at)
|
|
1705
|
+
);
|
|
1706
|
+
const e = { score, maxCombo, lines: cleared, time: Math.round(finalMs) };
|
|
1707
|
+
options.onFinish?.(e);
|
|
1708
|
+
}
|
|
1709
|
+
finBtn.addEventListener("click", finishGame);
|
|
1275
1710
|
perfectEl.addEventListener("click", (e) => {
|
|
1276
1711
|
if (e.target === perfectEl) perfectEl.classList.remove("pkb-show");
|
|
1277
1712
|
});
|
|
1278
1713
|
if (demo) {
|
|
1279
1714
|
let busy = false;
|
|
1280
1715
|
const prepare = () => {
|
|
1281
|
-
if (checked.every(Boolean)) request(normalize(void 0), null, { reset: true });
|
|
1716
|
+
if (!inf() && checked.every(Boolean)) request(normalize(void 0), null, { reset: true });
|
|
1282
1717
|
else perfectEl.classList.remove("pkb-show");
|
|
1283
1718
|
};
|
|
1284
1719
|
const run = async (steps) => {
|
|
@@ -1286,6 +1721,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1286
1721
|
busy = true;
|
|
1287
1722
|
try {
|
|
1288
1723
|
prepare();
|
|
1724
|
+
skipReady();
|
|
1289
1725
|
const g = gen;
|
|
1290
1726
|
for (const s of steps) {
|
|
1291
1727
|
await wait(s.delay);
|
|
@@ -1299,21 +1735,100 @@ function createPackingBingo(container, options = {}) {
|
|
|
1299
1735
|
}
|
|
1300
1736
|
};
|
|
1301
1737
|
$("dB").addEventListener("click", () => {
|
|
1738
|
+
if (inf() && phase === "done") return;
|
|
1302
1739
|
const open = () => lines.map((l) => l.filter((i) => !checked[i])).filter((m) => m.length).sort((a, b) => a.length - b.length)[0] ?? [];
|
|
1303
|
-
const pick = checked.every(Boolean) ? [...lines[0]] : open();
|
|
1740
|
+
const pick = !inf() && checked.every(Boolean) ? [...lines[0]] : open();
|
|
1304
1741
|
void run(pick.map((i) => ({ i, delay: 320 })));
|
|
1305
1742
|
});
|
|
1306
1743
|
$("dP").addEventListener("click", () => {
|
|
1744
|
+
if (inf()) return;
|
|
1307
1745
|
const all = items.map((_, i) => i);
|
|
1308
1746
|
const miss = checked.every(Boolean) ? all : all.filter((i) => !checked[i]);
|
|
1309
1747
|
void run(miss.map((i, k) => k < miss.length - 1 ? { i, delay: 170, quiet: true } : { i, delay: 450 }));
|
|
1310
1748
|
});
|
|
1311
1749
|
$("dR").addEventListener("click", () => {
|
|
1312
|
-
if (busy) return;
|
|
1750
|
+
if (busy || inf()) return;
|
|
1313
1751
|
unshuffle();
|
|
1752
|
+
restartGame();
|
|
1314
1753
|
request(demoStart(), null, { reset: true });
|
|
1315
1754
|
});
|
|
1316
1755
|
}
|
|
1756
|
+
const modeEl = container.querySelector('[data-r="mode"]');
|
|
1757
|
+
const radios = modeEl ? Array.from(modeEl.querySelectorAll("button[data-m]")) : [];
|
|
1758
|
+
const syncMode = () => {
|
|
1759
|
+
root.classList.toggle("pkb-inf", inf());
|
|
1760
|
+
radios.forEach((b) => {
|
|
1761
|
+
const on = b.dataset.m === mode;
|
|
1762
|
+
b.setAttribute("aria-checked", String(on));
|
|
1763
|
+
b.tabIndex = on ? 0 : -1;
|
|
1764
|
+
});
|
|
1765
|
+
};
|
|
1766
|
+
function modeFx() {
|
|
1767
|
+
const endless = inf();
|
|
1768
|
+
const title = endless ? M.infiniteTitle?.trim() || ja.infiniteTitle : M.normalTitle?.trim() || ja.normalTitle;
|
|
1769
|
+
const sub = endless ? M.infiniteSub?.trim() || ja.infiniteSub : M.normalSub?.trim() || ja.normalSub;
|
|
1770
|
+
const sparks = endless && !calm ? Array.from({ length: 18 }, (_, k) => {
|
|
1771
|
+
const x = (5 + Math.random() * 90).toFixed(1), y = (6 + Math.random() * 88).toFixed(1);
|
|
1772
|
+
const s = (0.45 + Math.random() * 0.95).toFixed(2), t = Math.round(100 + Math.random() * 950);
|
|
1773
|
+
return `<i style="--x:${x}%;--y:${y}%;--s:${s};--t:${t}ms;--c:${SPARK[k % SPARK.length]}"></i>`;
|
|
1774
|
+
}).join("") : "";
|
|
1775
|
+
mfx.className = "pkb-mfx " + (endless ? "pkb-mfx-inf" : "pkb-mfx-n");
|
|
1776
|
+
mfx.innerHTML = (endless ? `<span class="pkb-mfx-glow"></span>` : "") + sparks + `<span class="pkb-mfx-in"><b class="pkb-mfx-sym">${endless ? "\u221E" : esc(M.stamp)}</b><b class="pkb-mfx-t">${esc(title)}</b><span class="pkb-mfx-s">${esc(sub)}</span></span>`;
|
|
1777
|
+
replay(mfx, "pkb-show");
|
|
1778
|
+
board.classList.add("pkb-switching");
|
|
1779
|
+
if (modeEl && !calm) replay(modeEl, "pkb-bump");
|
|
1780
|
+
sfx.mode(endless);
|
|
1781
|
+
haptics.play(endless ? "infinite" : "normal");
|
|
1782
|
+
const picked = radios.find((b) => b.dataset.m === mode);
|
|
1783
|
+
if (picked) {
|
|
1784
|
+
const p = rel(picked);
|
|
1785
|
+
burst(p.x, p.y, endless ? 46 : 24, { speed: endless ? 7 : 5, g: 0.1, life: 55, size: 0.8, star: endless ? 0.9 : 0.3, colors: endless ? SPARK : void 0 });
|
|
1786
|
+
}
|
|
1787
|
+
later(() => {
|
|
1788
|
+
const c = rel(gw);
|
|
1789
|
+
if (endless) burst(c.x, c.y, 90, { speed: 12, g: 0.07, life: 90, star: 0.75, colors: SPARK });
|
|
1790
|
+
else {
|
|
1791
|
+
if (!calm) replay(board, "pkb-punch");
|
|
1792
|
+
burst(c.x, c.y, 70, { speed: 11, star: 0.2 });
|
|
1793
|
+
}
|
|
1794
|
+
}, endless ? 260 : 430);
|
|
1795
|
+
}
|
|
1796
|
+
mfx.addEventListener("animationend", (e) => {
|
|
1797
|
+
if (e.target !== mfx) return;
|
|
1798
|
+
mfx.classList.remove("pkb-show");
|
|
1799
|
+
board.classList.remove("pkb-switching");
|
|
1800
|
+
mfx.innerHTML = "";
|
|
1801
|
+
});
|
|
1802
|
+
function setMode(next) {
|
|
1803
|
+
if (dead || next === mode || next !== "normal" && next !== "infinite") return;
|
|
1804
|
+
const back = packing;
|
|
1805
|
+
if (next === "infinite") packing = { checked: checked.slice(), order: order.slice() };
|
|
1806
|
+
else packing = null;
|
|
1807
|
+
mode = next;
|
|
1808
|
+
resetAsked = false;
|
|
1809
|
+
if (back && back.order.some((v, s) => v !== order[s])) {
|
|
1810
|
+
flights.forEach((a) => a.cancel());
|
|
1811
|
+
flights = [];
|
|
1812
|
+
clearTimeout(landing);
|
|
1813
|
+
landing = 0;
|
|
1814
|
+
place(back.order);
|
|
1815
|
+
}
|
|
1816
|
+
syncMode();
|
|
1817
|
+
toReady();
|
|
1818
|
+
baseline(back ? back.checked : normalize(void 0));
|
|
1819
|
+
modeFx();
|
|
1820
|
+
options.onModeChange?.(mode);
|
|
1821
|
+
}
|
|
1822
|
+
modeEl?.addEventListener("click", (e) => {
|
|
1823
|
+
const b = e.target.closest("button[data-m]");
|
|
1824
|
+
if (b) setMode(b.dataset.m);
|
|
1825
|
+
});
|
|
1826
|
+
modeEl?.addEventListener("keydown", (e) => {
|
|
1827
|
+
if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(e.key)) return;
|
|
1828
|
+
e.preventDefault();
|
|
1829
|
+
setMode(inf() ? "normal" : "infinite");
|
|
1830
|
+
radios.find((b) => b.dataset.m === mode)?.focus();
|
|
1831
|
+
});
|
|
1317
1832
|
let startOrder = null;
|
|
1318
1833
|
if (orderKey) {
|
|
1319
1834
|
try {
|
|
@@ -1335,7 +1850,10 @@ function createPackingBingo(container, options = {}) {
|
|
|
1335
1850
|
if (!initial && options.defaultChecked) initial = normalize(options.defaultChecked);
|
|
1336
1851
|
if (!initial && demo) initial = demoStart();
|
|
1337
1852
|
fit();
|
|
1338
|
-
|
|
1853
|
+
if (inf()) packing = { checked: initial ?? normalize(void 0), order: order.slice() };
|
|
1854
|
+
syncMode();
|
|
1855
|
+
baseline(inf() ? normalize(void 0) : initial ?? normalize(void 0));
|
|
1856
|
+
if (phase !== "done") toReady();
|
|
1339
1857
|
const ro = typeof ResizeObserver === "function" ? new ResizeObserver(() => {
|
|
1340
1858
|
fit();
|
|
1341
1859
|
drawLines();
|
|
@@ -1344,7 +1862,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1344
1862
|
ro?.observe(gw);
|
|
1345
1863
|
const onVisibility = () => {
|
|
1346
1864
|
if (!document.hidden) {
|
|
1347
|
-
if (restorePerfectFocus && perfectEl.classList.contains("pkb-show"))
|
|
1865
|
+
if (restorePerfectFocus && perfectEl.classList.contains("pkb-show")) resultFocus().focus({ preventScroll: true });
|
|
1348
1866
|
restorePerfectFocus = false;
|
|
1349
1867
|
return;
|
|
1350
1868
|
}
|
|
@@ -1355,7 +1873,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1355
1873
|
if (keepPerfect) {
|
|
1356
1874
|
$("sScore").textContent = score.toLocaleString();
|
|
1357
1875
|
$("sCombo").textContent = "\xD7" + maxCombo;
|
|
1358
|
-
$("sBingo").textContent = String(
|
|
1876
|
+
$("sBingo").textContent = String(resultLines());
|
|
1359
1877
|
perfectEl.classList.add("pkb-show", "pkb-settled");
|
|
1360
1878
|
}
|
|
1361
1879
|
combo = 0;
|
|
@@ -1365,6 +1883,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
1365
1883
|
setChecked(next, opts = {}) {
|
|
1366
1884
|
if (dead) return;
|
|
1367
1885
|
const n2 = normalize(next);
|
|
1886
|
+
if (packing) {
|
|
1887
|
+
packing.checked = n2;
|
|
1888
|
+
resetAsked = false;
|
|
1889
|
+
if (storageKey) writeStore(storageKey, JSON.stringify(n2));
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1892
|
+
if (resetAsked && !n2.some(Boolean)) toReady();
|
|
1893
|
+
resetAsked = false;
|
|
1368
1894
|
const changed = n2.flatMap((v, i) => v !== checked[i] ? [i] : []);
|
|
1369
1895
|
if (!changed.length) return;
|
|
1370
1896
|
if (!opts.animate || changed.length !== 1) {
|
|
@@ -1375,12 +1901,20 @@ function createPackingBingo(container, options = {}) {
|
|
|
1375
1901
|
},
|
|
1376
1902
|
reset() {
|
|
1377
1903
|
if (dead) return;
|
|
1904
|
+
if (inf()) return restartInfinite();
|
|
1378
1905
|
unshuffle();
|
|
1906
|
+
restartGame();
|
|
1379
1907
|
request(normalize(void 0), null, { reset: true });
|
|
1380
1908
|
},
|
|
1381
1909
|
shuffle() {
|
|
1382
1910
|
if (!dead) shuffleCard();
|
|
1383
1911
|
},
|
|
1912
|
+
setMode(next) {
|
|
1913
|
+
setMode(next);
|
|
1914
|
+
},
|
|
1915
|
+
finish() {
|
|
1916
|
+
finishGame();
|
|
1917
|
+
},
|
|
1384
1918
|
refresh() {
|
|
1385
1919
|
if (!dead) render(null, true);
|
|
1386
1920
|
},
|
|
@@ -1390,20 +1924,25 @@ function createPackingBingo(container, options = {}) {
|
|
|
1390
1924
|
lines: cur.slice(),
|
|
1391
1925
|
score,
|
|
1392
1926
|
maxCombo,
|
|
1393
|
-
complete: checked.every(Boolean),
|
|
1927
|
+
complete: !inf() && checked.every(Boolean),
|
|
1928
|
+
mode,
|
|
1929
|
+
cleared,
|
|
1930
|
+
finished: inf() && phase === "done",
|
|
1931
|
+
packing: packing && { checked: packing.checked.slice(), order: packing.order.slice() },
|
|
1394
1932
|
size: n,
|
|
1395
1933
|
free: L.free,
|
|
1396
|
-
order: order.slice()
|
|
1934
|
+
order: order.slice(),
|
|
1935
|
+
time: Math.round(elapsed())
|
|
1397
1936
|
};
|
|
1398
1937
|
},
|
|
1399
1938
|
destroy() {
|
|
1400
1939
|
if (dead) return;
|
|
1401
1940
|
dead = true;
|
|
1402
|
-
|
|
1403
|
-
if (playDialog?.open) playDialog.close();
|
|
1941
|
+
stopTimers();
|
|
1404
1942
|
timers.forEach((id) => clearTimeout(id));
|
|
1405
1943
|
timers.clear();
|
|
1406
1944
|
clearTimeout(landing);
|
|
1945
|
+
clearTimeout(clearTimer);
|
|
1407
1946
|
flights.forEach((a) => a.cancel());
|
|
1408
1947
|
cancelAnimationFrame(raf);
|
|
1409
1948
|
cancelAnimationFrame(tw);
|
|
@@ -1422,5 +1961,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1422
1961
|
bingoLines,
|
|
1423
1962
|
createPackingBingo,
|
|
1424
1963
|
en,
|
|
1425
|
-
|
|
1964
|
+
formatTime,
|
|
1965
|
+
ja,
|
|
1966
|
+
speedBonus
|
|
1426
1967
|
});
|