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
|
@@ -103,11 +103,27 @@ var ja = {
|
|
|
103
103
|
shuffle: "\u{1F500} \u30B7\u30E3\u30C3\u30D5\u30EB",
|
|
104
104
|
soundOn: "\u{1F50A} \u97F3\u3042\u308A",
|
|
105
105
|
soundOff: "\u{1F507} \u97F3\u306A\u3057",
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
readyTitle: "Are you ready?",
|
|
107
|
+
readyPrompt: "",
|
|
108
|
+
soundHint: "\u7AEF\u672B\u306E\u97F3\u91CF\u3092MAX\u306B",
|
|
109
|
+
vibrationHint: "\u304A\u3084\u3059\u307F\u30E2\u30FC\u30C9\u3092\u30AA\u30D5",
|
|
110
|
+
readyTap: "\u30BF\u30C3\u30D7\u3067\u30B9\u30BF\u30FC\u30C8",
|
|
111
|
+
readyGo: "START!",
|
|
112
|
+
time: "\u30BF\u30A4\u30E0",
|
|
113
|
+
statTime: "\u30AF\u30EA\u30A2\u30BF\u30A4\u30E0",
|
|
114
|
+
modeLabel: "\u30E2\u30FC\u30C9",
|
|
115
|
+
modeNormal: "\u901A\u5E38",
|
|
116
|
+
modeInfinite: "\u221E \u7121\u9650",
|
|
117
|
+
normalTitle: "NORMAL",
|
|
118
|
+
normalSub: "\u901A\u5E38\u30E2\u30FC\u30C9",
|
|
119
|
+
infiniteTitle: "INFINITE",
|
|
120
|
+
infiniteSub: "\u7121\u9650\u30D3\u30F3\u30B4",
|
|
121
|
+
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",
|
|
122
|
+
finish: "\u{1F3C1} FINISH",
|
|
123
|
+
finishTitle: "\u30D5\u30A3\u30CB\u30C3\u30B7\u30E5\uFF01",
|
|
124
|
+
finishStamp: "\u221E",
|
|
125
|
+
finishTime: "\u30D7\u30EC\u30A4\u30BF\u30A4\u30E0",
|
|
126
|
+
again: "\u3082\u3046\u4E00\u5EA6",
|
|
111
127
|
stamp: "OK",
|
|
112
128
|
free: "FREE",
|
|
113
129
|
reachBadge: "\u30EA\u30FC\u30C1",
|
|
@@ -118,15 +134,15 @@ var ja = {
|
|
|
118
134
|
bannerNth: (n) => `${n} \u30E9\u30A4\u30F3\u76EE\uFF01`,
|
|
119
135
|
bannerMulti: (n) => `\u4E00\u6C17\u306B ${n} \u30E9\u30A4\u30F3\uFF01`,
|
|
120
136
|
statusStart: () => "",
|
|
121
|
-
statusLines: (
|
|
137
|
+
statusLines: () => "",
|
|
122
138
|
statusReach: () => "",
|
|
123
|
-
statusDone: "
|
|
139
|
+
statusDone: "",
|
|
124
140
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
125
141
|
perfectStamp: "\u6E96\u5099\n\u5B8C\u4E86",
|
|
126
142
|
perfectTitle: "\u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01",
|
|
127
|
-
perfectBody:
|
|
128
|
-
\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u3001\u3044\u3063\u3066\u3089\u3063\u3057\u3083\u3044\uFF01`,
|
|
143
|
+
perfectBody: "",
|
|
129
144
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
145
|
+
speedBonus: (n) => `SPEED BONUS +${n.toLocaleString()}`,
|
|
130
146
|
statScore: "SCORE",
|
|
131
147
|
statCombo: "MAX COMBO",
|
|
132
148
|
statLines: "BINGO",
|
|
@@ -149,11 +165,27 @@ var en = {
|
|
|
149
165
|
shuffle: "\u{1F500} Shuffle",
|
|
150
166
|
soundOn: "\u{1F50A} Sound on",
|
|
151
167
|
soundOff: "\u{1F507} Muted",
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
168
|
+
readyTitle: "Are you ready?",
|
|
169
|
+
readyPrompt: "",
|
|
170
|
+
soundHint: "Turn your volume to MAX",
|
|
171
|
+
vibrationHint: "Turn off Do Not Disturb",
|
|
172
|
+
readyTap: "Tap to start",
|
|
173
|
+
readyGo: "GO!",
|
|
174
|
+
time: "Time",
|
|
175
|
+
statTime: "Time",
|
|
176
|
+
modeLabel: "Mode",
|
|
177
|
+
modeNormal: "Normal",
|
|
178
|
+
modeInfinite: "\u221E Infinite",
|
|
179
|
+
normalTitle: "NORMAL",
|
|
180
|
+
normalSub: "Normal mode",
|
|
181
|
+
infiniteTitle: "INFINITE",
|
|
182
|
+
infiniteSub: "Infinite bingo",
|
|
183
|
+
infiniteRule: "Completed lines clear, so you can fill them again. Bingo forever!",
|
|
184
|
+
finish: "\u{1F3C1} FINISH",
|
|
185
|
+
finishTitle: "Finish!",
|
|
186
|
+
finishStamp: "\u221E",
|
|
187
|
+
finishTime: "Time played",
|
|
188
|
+
again: "Play again",
|
|
157
189
|
stamp: "OK",
|
|
158
190
|
free: "FREE",
|
|
159
191
|
reachBadge: "REACH",
|
|
@@ -164,15 +196,15 @@ var en = {
|
|
|
164
196
|
bannerNth: (n) => `Line ${n}!`,
|
|
165
197
|
bannerMulti: (n) => `${n} lines at once!`,
|
|
166
198
|
statusStart: () => "",
|
|
167
|
-
statusLines: (
|
|
199
|
+
statusLines: () => "",
|
|
168
200
|
statusReach: () => "",
|
|
169
|
-
statusDone: "
|
|
201
|
+
statusDone: "",
|
|
170
202
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
171
203
|
perfectStamp: "ALL\nSET",
|
|
172
204
|
perfectTitle: "Perfect!",
|
|
173
|
-
perfectBody:
|
|
174
|
-
Have a great trip!`,
|
|
205
|
+
perfectBody: "",
|
|
175
206
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
207
|
+
speedBonus: (n) => `SPEED BONUS +${n.toLocaleString()}`,
|
|
176
208
|
statScore: "SCORE",
|
|
177
209
|
statCombo: "MAX COMBO",
|
|
178
210
|
statLines: "BINGO",
|
|
@@ -313,6 +345,36 @@ function createSound(isMuted) {
|
|
|
313
345
|
tone(hz(7), 0.34, 0.12, "triangle", 0.14);
|
|
314
346
|
tone(hz(12), 0.42, 0.2, "triangle", 0.14);
|
|
315
347
|
},
|
|
348
|
+
count(go) {
|
|
349
|
+
if (go) {
|
|
350
|
+
tone(hz(12), 0, 0.5, "square", 0.07);
|
|
351
|
+
tone(hz(24), 0, 0.5, "triangle", 0.14);
|
|
352
|
+
noise(0, 0.2, 0.12, 3e3);
|
|
353
|
+
} else {
|
|
354
|
+
tone(hz(5), 0, 0.14, "square", 0.07);
|
|
355
|
+
tone(hz(17), 0, 0.14, "triangle", 0.12);
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
mode(infinite) {
|
|
359
|
+
if (infinite) {
|
|
360
|
+
tone(196, 0, 0.42, "sine", 0.12, 784);
|
|
361
|
+
[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));
|
|
362
|
+
[12, 16, 19, 24].forEach((s) => tone(hz(s), 0.38, 1.1, "sine", 0.07));
|
|
363
|
+
tone(hz(31), 0.38, 0.9, "triangle", 0.03);
|
|
364
|
+
noise(0, 0.5, 0.07, 6500);
|
|
365
|
+
noise(0.36, 0.7, 0.05, 9e3);
|
|
366
|
+
} else {
|
|
367
|
+
tone(hz(7), 0, 0.1, "square", 0.05);
|
|
368
|
+
tone(hz(0), 0.1, 0.28, "triangle", 0.14);
|
|
369
|
+
tone(160, 0.1, 0.2, "sine", 0.26, 55);
|
|
370
|
+
noise(0.1, 0.12, 0.14, 1400);
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
clear(squares) {
|
|
374
|
+
const n = Math.max(1, Math.min(8, squares));
|
|
375
|
+
for (let i = 0; i < n; i++) tone(step(SCALE.length - 1 - i * 2), i * 0.035, 0.18, "sine", 0.07);
|
|
376
|
+
noise(0, 0.3, 0.08, 7e3);
|
|
377
|
+
},
|
|
316
378
|
stop,
|
|
317
379
|
close() {
|
|
318
380
|
stop();
|
|
@@ -355,7 +417,10 @@ function createHaptics(enabled) {
|
|
|
355
417
|
const current = ++generation;
|
|
356
418
|
const tier = Math.min(3, Math.floor(combo / 3));
|
|
357
419
|
const lead = Math.max(80, impactAt - 40);
|
|
358
|
-
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] :
|
|
420
|
+
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 : (
|
|
421
|
+
// Switching modes: a quick rising flutter into a hold, or one firm stamp.
|
|
422
|
+
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
|
|
423
|
+
);
|
|
359
424
|
if (!send(pattern)) return;
|
|
360
425
|
return (beat) => {
|
|
361
426
|
if (generation === current && owner === token) send([...beat]);
|
|
@@ -406,24 +471,17 @@ var CSS = `
|
|
|
406
471
|
.pkb-board.pkb-punch{animation:pkb-punch .4s cubic-bezier(.3,1.6,.5,1)}
|
|
407
472
|
@keyframes pkb-punch{30%{transform:scale(1.018)}}
|
|
408
473
|
.pkb-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px 16px;margin-bottom:18px}
|
|
409
|
-
.pkb-
|
|
410
|
-
.pkb-play-dialog:not([open]){display:none}
|
|
411
|
-
.pkb-play-dialog::backdrop{background:rgba(42,31,20,.6)}
|
|
412
|
-
.pkb-play-dialog h3{margin-bottom:16px;font-size:22px;font-weight:800;line-height:1.4}
|
|
413
|
-
.pkb-play-dialog p{margin-bottom:12px;font-size:15px;font-weight:700;line-height:1.7;overflow-wrap:anywhere}
|
|
414
|
-
.pkb-play-actions{display:grid;gap:10px;margin-top:22px}
|
|
415
|
-
.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}
|
|
416
|
-
.pkb-play-start{background:var(--pk-sun)}
|
|
417
|
-
.pkb-play-skip{background:transparent}
|
|
418
|
-
.pkb-play-dialog button:focus-visible{outline:3px solid var(--pk-ink);outline-offset:3px}
|
|
419
|
-
.pkb-play-dialog button:hover{filter:brightness(.96)}
|
|
420
|
-
.pkb-tags{display:flex;gap:8px;align-items:center}
|
|
474
|
+
.pkb-tags{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
|
|
421
475
|
.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}
|
|
422
476
|
.pkb-kicker:empty{display:none}
|
|
423
477
|
.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}
|
|
424
478
|
@keyframes pkb-rb{to{background-position:-200% 0}}
|
|
425
479
|
.pkb button{font-family:var(--pk-body)}
|
|
426
480
|
.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}
|
|
481
|
+
.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}
|
|
482
|
+
.pkb-watch::before{content:"\u23F1 ";font-family:var(--pk-body)}
|
|
483
|
+
.pkb-watch.pkb-run{color:var(--pk-sun)}
|
|
484
|
+
.pkb-watch.pkb-done{background:var(--pk-red);color:#fff}
|
|
427
485
|
.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}
|
|
428
486
|
.pkb-sub{font-weight:800;font-size:13px;margin-top:4px;word-break:auto-phrase}
|
|
429
487
|
.pkb-side{display:flex;flex-direction:column;gap:8px;flex:none}
|
|
@@ -446,7 +504,7 @@ var CSS = `
|
|
|
446
504
|
.pkb-gw{position:relative}
|
|
447
505
|
.pkb-grid{display:grid;grid-template-columns:repeat(var(--pk-n),minmax(0,1fr));gap:calc(12px*var(--pk-d))}
|
|
448
506
|
.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}
|
|
449
|
-
.pkb-tile:active:not(.pkb-free){transform:translate(3px,3px);box-shadow:1px 1px 0 var(--pk-ink)}
|
|
507
|
+
.pkb-tile:active:not(.pkb-free):not(.pkb-lock){transform:translate(3px,3px);box-shadow:1px 1px 0 var(--pk-ink)}
|
|
450
508
|
.pkb-tile input{position:absolute;opacity:0;pointer-events:none;width:1px;height:1px}
|
|
451
509
|
.pkb-tile:has(input:focus-visible){outline:3px solid var(--pk-ink);outline-offset:3px}
|
|
452
510
|
.pkb-emo{font-size:max(14px,calc(32px*var(--pk-s)));line-height:1;transition:opacity .2s}
|
|
@@ -454,6 +512,7 @@ var CSS = `
|
|
|
454
512
|
.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}
|
|
455
513
|
.pkb-tile.pkb-on .pkb-stamp{opacity:.9;transform:translate(-50%,-50%) rotate(-16deg) scale(1)}
|
|
456
514
|
.pkb-tile.pkb-on .pkb-emo,.pkb-tile.pkb-on .pkb-lbl{opacity:.35}
|
|
515
|
+
.pkb-tile.pkb-lock{cursor:default}
|
|
457
516
|
.pkb-tile.pkb-free{cursor:default;container-type:inline-size}
|
|
458
517
|
.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)}
|
|
459
518
|
.pkb-tile.pkb-line{background-color:var(--pk-mint)}
|
|
@@ -481,10 +540,11 @@ var CSS = `
|
|
|
481
540
|
.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}
|
|
482
541
|
.pkb-foot>span{flex:1 1 14em}
|
|
483
542
|
.pkb-foot>span:empty{flex-basis:0}
|
|
484
|
-
.pkb-acts{display:flex;gap:8px;flex:
|
|
543
|
+
.pkb-acts{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;flex:0 1 auto;max-width:100%;margin-left:auto}
|
|
485
544
|
.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}
|
|
486
545
|
.pkb-shuf{background:var(--pk-paper);box-shadow:2px 2px 0 var(--pk-ink)}
|
|
487
|
-
.pkb-shuf:active{transform:translate(2px,2px);box-shadow:none}
|
|
546
|
+
.pkb-shuf:active:not(:disabled){transform:translate(2px,2px);box-shadow:none}
|
|
547
|
+
.pkb-shuf:disabled{cursor:default;opacity:.4}
|
|
488
548
|
.pkb-flash{position:absolute;inset:0;z-index:7;background:#fff;opacity:0;pointer-events:none}
|
|
489
549
|
.pkb-flash.pkb-go{animation:pkb-flash .4s ease-out}
|
|
490
550
|
@keyframes pkb-flash{0%{opacity:.8}100%{opacity:0}}
|
|
@@ -501,8 +561,33 @@ var CSS = `
|
|
|
501
561
|
.pkb-float.pkb-big{font-size:clamp(14px,5.6cqi,34px);color:#FFE066}
|
|
502
562
|
.pkb-float.pkb-rch{font-size:20px;color:var(--pk-red);-webkit-text-stroke:4px var(--pk-paper)}
|
|
503
563
|
@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)}}
|
|
564
|
+
.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}
|
|
565
|
+
.pkb-ready[hidden]{display:none}
|
|
566
|
+
.pkb-ready:focus-visible{outline:3px solid var(--pk-ink);outline-offset:-6px}
|
|
567
|
+
.pkb-ready.pkb-gone{pointer-events:none;background-color:transparent}
|
|
568
|
+
.pkb-board.pkb-waiting .pkb-snd{position:relative;z-index:13}
|
|
569
|
+
.pkb-ready-in,.pkb-count{grid-area:1/1}
|
|
570
|
+
.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)}
|
|
571
|
+
.pkb-ready.pkb-counting .pkb-ready-in{visibility:hidden}
|
|
572
|
+
.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)}
|
|
573
|
+
.pkb-ready-q{margin-top:6px;font-weight:800;font-size:18px;line-height:1.4}
|
|
574
|
+
.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}
|
|
575
|
+
.pkb-ready-l>span::before{content:"\u30FB"}
|
|
576
|
+
.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}
|
|
577
|
+
@keyframes pkb-beat{50%{transform:scale(1.06)}}
|
|
578
|
+
.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}
|
|
579
|
+
.pkb-count.pkb-go{font-size:clamp(48px,17cqi,120px);color:var(--pk-red);white-space:nowrap}
|
|
580
|
+
.pkb-count.pkb-tick{animation:pkb-cnt 1s cubic-bezier(.2,1.4,.4,1) forwards}
|
|
581
|
+
.pkb-count.pkb-go.pkb-tick{animation:pkb-cnt-go .75s cubic-bezier(.2,1.4,.4,1) forwards}
|
|
582
|
+
@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)}}
|
|
583
|
+
@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)}}
|
|
584
|
+
.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)}
|
|
585
|
+
.pkb-ptime[hidden]{display:none}
|
|
586
|
+
.pkb-ptime small{font-size:11px;font-weight:800;letter-spacing:.08em;color:#fff}
|
|
587
|
+
.pkb-ptime b{font-family:var(--pk-display);font-weight:400;font-size:22px;line-height:1.2;font-variant-numeric:tabular-nums}
|
|
504
588
|
.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}
|
|
505
|
-
.
|
|
589
|
+
/* Visible at once when opening (focus can't land on a hidden dialog); hidden only after the fade when closing. */
|
|
590
|
+
.pkb-perfect.pkb-show{opacity:1;visibility:visible;transition:opacity .3s,visibility 0s}
|
|
506
591
|
.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)}
|
|
507
592
|
.pkb-perfect.pkb-show .pkb-card{transform:none}
|
|
508
593
|
.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)}
|
|
@@ -512,6 +597,7 @@ var CSS = `
|
|
|
512
597
|
@keyframes pkb-slam{to{opacity:1;transform:rotate(-14deg) scale(1)}}
|
|
513
598
|
.pkb-ptitle{font-family:var(--pk-display);font-weight:400;font-size:30px;line-height:1.3;color:inherit}
|
|
514
599
|
.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}
|
|
600
|
+
.pkb-pbody:empty{margin:0 0 14px}
|
|
515
601
|
.pkb-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:16px}
|
|
516
602
|
.pkb-stats>div{border:2px solid var(--pk-ink);border-radius:12px;padding:6px 4px 5px;background:#fff}
|
|
517
603
|
.pkb-stats small{display:block;font-size:9.5px;font-weight:800;letter-spacing:.1em}
|
|
@@ -521,10 +607,72 @@ var CSS = `
|
|
|
521
607
|
.pkb-fx{position:absolute;inset:0;width:100%;height:100%;z-index:10;pointer-events:none}
|
|
522
608
|
.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}
|
|
523
609
|
.pkb-demo button{appearance:none;border:1px solid #ccc;background:#f3f3f3;color:#222;border-radius:999px;padding:5px 12px;font-size:12px;cursor:pointer}
|
|
524
|
-
|
|
525
|
-
|
|
610
|
+
.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}
|
|
611
|
+
.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}
|
|
612
|
+
.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}
|
|
613
|
+
.pkb-mode button[aria-checked="true"]{opacity:1;color:var(--pk-ink);cursor:default}
|
|
614
|
+
.pkb-mode button:focus-visible{outline:2px solid var(--pk-paper);outline-offset:-4px}
|
|
615
|
+
.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)}
|
|
616
|
+
.pkb.pkb-inf .pkb-mode{box-shadow:0 0 14px 1px rgba(155,93,229,.6)}
|
|
617
|
+
.pkb.pkb-inf .pkb-mode::after{opacity:1;animation:pkb-glint 3.2s ease-in-out infinite}
|
|
618
|
+
.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}
|
|
619
|
+
.pkb.pkb-inf .pkb-mode button[data-m="infinite"]{color:#fff;text-shadow:0 1px 1px rgba(42,31,20,.55)}
|
|
620
|
+
.pkb-board.pkb-waiting .pkb-mode,.pkb-board.pkb-switching .pkb-mode{z-index:15}
|
|
621
|
+
.pkb-board.pkb-switching .pkb-fx{z-index:16}
|
|
622
|
+
.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)}
|
|
623
|
+
.pkb.pkb-inf .pkb-demo [data-r="dP"],.pkb.pkb-inf .pkb-demo [data-r="dR"]{display:none}
|
|
624
|
+
.pkb-ready-b,.pkb-ready-r{display:none}
|
|
625
|
+
.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)}
|
|
626
|
+
.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}
|
|
627
|
+
.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}
|
|
628
|
+
.pkb-tile.pkb-clr{animation:pkb-clr .62s var(--pkb-cd,0ms) both}
|
|
629
|
+
@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)}}
|
|
630
|
+
.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}
|
|
631
|
+
.pkb-mfx.pkb-show{visibility:visible;animation:pkb-mfx 1.7s ease-out forwards}
|
|
632
|
+
@keyframes pkb-mfx{0%{opacity:0}9%{opacity:1}72%{opacity:1}100%{opacity:0}}
|
|
633
|
+
.pkb-mfx-inf{background:radial-gradient(120% 95% at 50% 42%,#33205c 0%,#170f2e 52%,#08060f 100%)}
|
|
634
|
+
.pkb-mfx-n{background:var(--pk-sun) radial-gradient(rgba(42,31,20,.16) 1.3px,transparent 1.6px) 0 0/16px 16px}
|
|
635
|
+
.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}
|
|
636
|
+
.pkb-mfx.pkb-show::after{animation:pkb-beam 1s cubic-bezier(.4,0,.2,1) .22s both}
|
|
637
|
+
@keyframes pkb-beam{0%{opacity:1;transform:skewX(-18deg) translateX(0)}100%{opacity:1;transform:skewX(-18deg) translateX(400%)}}
|
|
638
|
+
.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}
|
|
639
|
+
.pkb-mfx.pkb-show .pkb-mfx-glow{animation:pkb-spin 3s linear infinite,pkb-glow 1.7s ease-out both}
|
|
640
|
+
@keyframes pkb-glow{0%{opacity:0;scale:.35}30%{opacity:.8;scale:1}100%{opacity:.5;scale:1.12}}
|
|
641
|
+
.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}
|
|
642
|
+
.pkb-mfx.pkb-show i{animation:pkb-twk .85s ease-in-out var(--t) both}
|
|
643
|
+
@keyframes pkb-twk{0%{opacity:0;scale:0;rotate:0deg}45%{opacity:1;scale:1;rotate:70deg}100%{opacity:0;scale:.2;rotate:160deg}}
|
|
644
|
+
.pkb-mfx-in{position:relative;display:flex;flex-direction:column;align-items:center;padding:0 16px}
|
|
645
|
+
.pkb-mfx-sym{display:block;font-family:var(--pk-display);font-weight:400;line-height:1}
|
|
646
|
+
.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))}
|
|
647
|
+
.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}
|
|
648
|
+
@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)}}
|
|
649
|
+
.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)}
|
|
650
|
+
.pkb-mfx.pkb-show.pkb-mfx-n .pkb-mfx-sym{animation:pkb-mslam .42s cubic-bezier(.5,0,.75,0) .08s both}
|
|
651
|
+
@keyframes pkb-mslam{from{opacity:0;transform:rotate(-14deg) scale(2.6)}to{opacity:1;transform:rotate(-14deg) scale(1)}}
|
|
652
|
+
.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}
|
|
653
|
+
.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)}
|
|
654
|
+
.pkb-mfx-n .pkb-mfx-t{margin-top:16px;color:var(--pk-ink)}
|
|
655
|
+
.pkb-mfx.pkb-show .pkb-mfx-t{animation:pkb-track 1.7s cubic-bezier(.16,1,.3,1) .12s both}
|
|
656
|
+
@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}}
|
|
657
|
+
.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}
|
|
658
|
+
.pkb-mfx-inf .pkb-mfx-s{color:#fff;background:rgba(255,255,255,.12);border:1.5px solid rgba(255,255,255,.4)}
|
|
659
|
+
.pkb-mfx-n .pkb-mfx-s{background:var(--pk-ink);color:var(--pk-sun)}
|
|
660
|
+
.pkb-mfx.pkb-show .pkb-mfx-s{animation:pkb-up .6s cubic-bezier(.2,1.3,.4,1) .4s both}
|
|
661
|
+
@keyframes pkb-up{from{opacity:0;transform:translateY(12px)}}
|
|
662
|
+
.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}
|
|
663
|
+
.pkb.pkb-inf .pkb-finbtn{display:inline-block}
|
|
664
|
+
.pkb-finbtn:active:not(:disabled){transform:translate(2px,2px);box-shadow:none}
|
|
665
|
+
.pkb-finbtn:disabled{cursor:default;opacity:.4;animation:none;box-shadow:2px 2px 0 var(--pk-ink)}
|
|
666
|
+
.pkb-board.pkb-over .pkb-grid{opacity:.55;filter:saturate(.5);transition:opacity .4s,filter .4s}
|
|
667
|
+
.pkb-pbtns{display:flex;flex-wrap:wrap;justify-content:center;gap:10px}
|
|
668
|
+
.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)}
|
|
669
|
+
.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)}
|
|
670
|
+
.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}
|
|
671
|
+
.pkb-perfect.pkb-fin .pkb-close:not(.pkb-again){background:var(--pk-paper)}
|
|
672
|
+
@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}}
|
|
673
|
+
@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}}
|
|
526
674
|
@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}}
|
|
527
|
-
@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}}
|
|
675
|
+
@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}}
|
|
528
676
|
@keyframes pkb-fade{0%,100%{opacity:0}15%,80%{opacity:1}}
|
|
529
677
|
`;
|
|
530
678
|
|
|
@@ -534,7 +682,6 @@ var CONFETTI = ["#E8473B", "#2F6FED", "#1FA971", "#FFFDF6", "#2A1F14", "#FF9F1C"
|
|
|
534
682
|
var COMBO_COLORS = ["#FF9F1C", "#1FA971", "#2F6FED", "#9B5DE5", "#E8473B"];
|
|
535
683
|
var COMBO_MS = 2500;
|
|
536
684
|
var MUTE_KEY = "packing-bingo-muted";
|
|
537
|
-
var playReady = /* @__PURE__ */ new WeakSet();
|
|
538
685
|
var ORDER_PREFIX = "packing-bingo-order:";
|
|
539
686
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
540
687
|
function readStore(key) {
|
|
@@ -552,7 +699,22 @@ function writeStore(key, value) {
|
|
|
552
699
|
}
|
|
553
700
|
var glintDelay = (c, n) => `${+((Math.floor(c / n) + c % n) * 0.36 / Math.max(1, n - 1)).toFixed(3)}s`;
|
|
554
701
|
var lineKey = (l) => l.slice().sort((a, b) => a - b).join(",");
|
|
702
|
+
var COUNT_MS = 1e3;
|
|
703
|
+
var GO_MS = 750;
|
|
704
|
+
function formatTime(ms, digits = 1) {
|
|
705
|
+
const unit = 10 ** digits;
|
|
706
|
+
const t = Math.floor(Math.max(0, ms) / (1e3 / unit));
|
|
707
|
+
const frac = digits ? "." + String(t % unit).padStart(digits, "0") : "";
|
|
708
|
+
const sec = Math.floor(t / unit), s = sec % 60, m = Math.floor(sec / 60) % 60, h = Math.floor(sec / 3600);
|
|
709
|
+
const pad = (v) => String(v).padStart(2, "0");
|
|
710
|
+
return (h ? `${h}:${pad(m)}` : String(m)) + `:${pad(s)}${frac}`;
|
|
711
|
+
}
|
|
712
|
+
function speedBonus(items, ms) {
|
|
713
|
+
return Math.round(items * 1e6 / Math.max(ms, 1));
|
|
714
|
+
}
|
|
555
715
|
var FLY_MS = 560;
|
|
716
|
+
var CLEAR_MS = 1e3;
|
|
717
|
+
var SPARK = ["#FFFFFF", "#FFE066", "#FF9F1C", "#E8473B", "#9B5DE5", "#2F6FED", "#1FA971"];
|
|
556
718
|
function ensureStyles() {
|
|
557
719
|
if (document.getElementById("pkb-styles")) return;
|
|
558
720
|
const s = document.createElement("style");
|
|
@@ -560,7 +722,8 @@ function ensureStyles() {
|
|
|
560
722
|
s.textContent = CSS;
|
|
561
723
|
document.head.appendChild(s);
|
|
562
724
|
}
|
|
563
|
-
|
|
725
|
+
var opt = (v, fallback) => v ?? fallback ?? "";
|
|
726
|
+
function template(L, M, { demo, shuffle, vibrate, ready, modes }) {
|
|
564
727
|
const n = L.size;
|
|
565
728
|
const input = vibrate ? `<input type="checkbox" switch role="checkbox">` : `<input type="checkbox">`;
|
|
566
729
|
const tiles = Array.from({ length: n * n }, (_, c) => {
|
|
@@ -572,16 +735,15 @@ function template(L, M, { demo, shuffle, vibrate }) {
|
|
|
572
735
|
}).join("");
|
|
573
736
|
const scale = `--pk-n:${n};--pk-s:${+Math.min(1.35, 4 / n).toFixed(3)};--pk-d:${+Math.min(1, 4 / n).toFixed(3)}`;
|
|
574
737
|
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>` : "";
|
|
575
|
-
const hints = [M.soundHint
|
|
576
|
-
const
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
<button type="button" class="pkb-play-skip">${esc(M.playSkip?.trim() || ja.playSkip || M.close)}</button></div></dialog>` : "";
|
|
738
|
+
const hints = [opt(M.soundHint, ja.soundHint), vibrate ? opt(M.vibrationHint, ja.vibrationHint) : ""].filter((h) => h.trim());
|
|
739
|
+
const prompt = opt(M.readyPrompt, ja.readyPrompt);
|
|
740
|
+
const rule = opt(M.infiniteRule, ja.infiniteRule);
|
|
741
|
+
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>` : "";
|
|
742
|
+
const timeLabel = M.time?.trim() || ja.time;
|
|
743
|
+
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>` : "";
|
|
582
744
|
return `<div class="pkb" style="${scale}"><div class="pkb-board">
|
|
583
745
|
<div class="pkb-head"><div>
|
|
584
|
-
<div class="pkb-tags"><span class="pkb-kicker" data-r="kick">${esc(M.tag)}</span
|
|
746
|
+
<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>
|
|
585
747
|
<h2 class="pkb-title">${esc(M.title)}</h2>${M.subtitle ? `<p class="pkb-sub">${esc(M.subtitle)}</p>` : ""}</div>
|
|
586
748
|
<div class="pkb-side"><div class="pkb-score">
|
|
587
749
|
<div class="pkb-pill"><small>${esc(M.packed)}</small><b data-r="cnt"></b></div>
|
|
@@ -590,13 +752,14 @@ ${hints.map((hint) => `<p>${esc(hint)}</p>`).join("")}
|
|
|
590
752
|
<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>
|
|
591
753
|
<div class="pkb-gw"><div class="pkb-grid">${tiles}</div><svg class="pkb-lines" aria-hidden="true"></svg>
|
|
592
754
|
<div class="pkb-banner" data-r="banner" aria-hidden="true"><b data-r="bT"></b><span data-r="bS"></span></div></div>
|
|
593
|
-
<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>
|
|
755
|
+
<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>
|
|
594
756
|
<div class="pkb-flash" data-r="flash"></div>
|
|
595
|
-
<div class="pkb-perfect" data-r="perfect"><div class="pkb-card" role="dialog" aria-label="${esc(M.perfectTitle)}">
|
|
596
|
-
<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>
|
|
757
|
+
<div class="pkb-perfect" data-r="perfect"><div class="pkb-card" data-r="pcard" role="dialog" aria-label="${esc(M.perfectTitle)}">
|
|
758
|
+
<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>
|
|
759
|
+
<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>
|
|
597
760
|
<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>
|
|
598
|
-
<button type="button" class="pkb-close" data-r="close">${esc(M.close)}</button></div></div>
|
|
599
|
-
<canvas class="pkb-fx"></canvas></div>${demoBar}
|
|
761
|
+
<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>
|
|
762
|
+
<canvas class="pkb-fx"></canvas>${readyScreen}<div class="pkb-mfx" data-r="mfx" aria-hidden="true"></div></div>${demoBar}</div>`;
|
|
600
763
|
}
|
|
601
764
|
var perfectText = (M, total) => typeof M.perfectBody === "function" ? M.perfectBody(total) : M.perfectBody;
|
|
602
765
|
function createPackingBingo(container, options = {}) {
|
|
@@ -628,8 +791,15 @@ function createPackingBingo(container, options = {}) {
|
|
|
628
791
|
const orderKey = storageKey && ORDER_PREFIX + storageKey;
|
|
629
792
|
const demo = !!options.demo;
|
|
630
793
|
const vibrate = options.vibrate !== false;
|
|
794
|
+
const uncheck = options.uncheck === true;
|
|
631
795
|
const calm = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
632
796
|
const haptics = createHaptics(vibrate);
|
|
797
|
+
let mode = options.mode === "infinite" ? "infinite" : "normal";
|
|
798
|
+
const inf = () => mode === "infinite";
|
|
799
|
+
let packing = null;
|
|
800
|
+
let cleared = 0;
|
|
801
|
+
const pending = /* @__PURE__ */ new Set();
|
|
802
|
+
let clearTimer = 0, clearAt = 0;
|
|
633
803
|
let dead = false;
|
|
634
804
|
const timers = /* @__PURE__ */ new Set();
|
|
635
805
|
const later = (fn, ms) => {
|
|
@@ -642,12 +812,19 @@ function createPackingBingo(container, options = {}) {
|
|
|
642
812
|
const wait = (ms) => new Promise((r) => window.setTimeout(r, ms));
|
|
643
813
|
let gen = 0;
|
|
644
814
|
ensureStyles();
|
|
645
|
-
container.innerHTML = template(L, M, {
|
|
815
|
+
container.innerHTML = template(L, M, {
|
|
816
|
+
demo,
|
|
817
|
+
shuffle: options.shuffle !== false,
|
|
818
|
+
vibrate,
|
|
819
|
+
ready: options.ready !== false,
|
|
820
|
+
modes: options.modeSwitch !== false
|
|
821
|
+
});
|
|
646
822
|
const $ = (r) => {
|
|
647
823
|
const el = container.querySelector(`[data-r="${r}"]`);
|
|
648
824
|
if (!el) throw new Error(`[packing-bingo] internal: missing element "${r}"`);
|
|
649
825
|
return el;
|
|
650
826
|
};
|
|
827
|
+
const root = container.querySelector(".pkb");
|
|
651
828
|
const board = container.querySelector(".pkb-board");
|
|
652
829
|
const gw = container.querySelector(".pkb-gw");
|
|
653
830
|
const grid = container.querySelector(".pkb-grid");
|
|
@@ -659,21 +836,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
659
836
|
const freeTile = L.center >= 0 ? cellTiles[L.center] : null;
|
|
660
837
|
const ins = tiles.map((t) => t.querySelector("input"));
|
|
661
838
|
const stamps = tiles.map((t) => t.querySelector(".pkb-stamp"));
|
|
662
|
-
const kick = $("kick"), snd = $("snd"), perfectEl = $("perfect");
|
|
663
|
-
const playDialog = container.querySelector('[data-r="play"]');
|
|
664
|
-
const beforePlay = (event) => {
|
|
665
|
-
if (!playDialog || playReady.has(container.ownerDocument) || typeof playDialog.showModal !== "function" || !(event.target instanceof Element) || !event.target.closest(".pkb-tile:not(.pkb-free)")) return;
|
|
666
|
-
event.preventDefault();
|
|
667
|
-
event.stopImmediatePropagation();
|
|
668
|
-
playDialog.showModal();
|
|
669
|
-
};
|
|
670
|
-
grid.addEventListener("click", beforePlay, true);
|
|
671
|
-
playDialog?.querySelectorAll("button").forEach((button) => {
|
|
672
|
-
button.addEventListener("click", () => playDialog.close());
|
|
673
|
-
});
|
|
674
|
-
playDialog?.addEventListener("close", () => {
|
|
675
|
-
if (!dead) playReady.add(container.ownerDocument);
|
|
676
|
-
});
|
|
839
|
+
const kick = $("kick"), snd = $("snd"), perfectEl = $("perfect"), mfx = $("mfx");
|
|
677
840
|
let checked = normalize(void 0);
|
|
678
841
|
let cur = [];
|
|
679
842
|
let prevLines = /* @__PURE__ */ new Set();
|
|
@@ -709,7 +872,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
709
872
|
cv.width = Math.round(BW * dpr);
|
|
710
873
|
cv.height = Math.round(BH * dpr);
|
|
711
874
|
};
|
|
712
|
-
const piece = (x, y, vx, vy, g, s, life, shape2) => ({
|
|
875
|
+
const piece = (x, y, vx, vy, g, s, life, shape2, colors = CONFETTI) => ({
|
|
713
876
|
x,
|
|
714
877
|
y,
|
|
715
878
|
vx,
|
|
@@ -721,7 +884,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
721
884
|
h: (3 + Math.random() * 5) * s,
|
|
722
885
|
r: Math.random() * 6,
|
|
723
886
|
vr: (Math.random() - 0.5) * 0.35,
|
|
724
|
-
c:
|
|
887
|
+
c: colors[Math.random() * colors.length | 0]
|
|
725
888
|
});
|
|
726
889
|
const go = () => {
|
|
727
890
|
if (!raf && ctx) raf = requestAnimationFrame(tick);
|
|
@@ -732,7 +895,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
732
895
|
const a = (o.angle ?? -Math.PI / 2) + (Math.random() - 0.5) * (o.spread ?? Math.PI * 2);
|
|
733
896
|
const v = (o.speed ?? 10) * (0.35 + Math.random() * 0.85);
|
|
734
897
|
const sh = Math.random() < (o.star ?? 0.15) ? "star" : Math.random() < 0.3 ? "round" : "rect";
|
|
735
|
-
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));
|
|
898
|
+
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));
|
|
736
899
|
}
|
|
737
900
|
go();
|
|
738
901
|
}
|
|
@@ -830,6 +993,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
830
993
|
if (e.animationName === "pkb-stamp-hit") t.classList.remove("pkb-hit");
|
|
831
994
|
if (t.classList.contains("pkb-tile") && (e.animationName === "pkb-hop" || e.animationName === "pkb-squash"))
|
|
832
995
|
t.classList.remove(e.animationName);
|
|
996
|
+
if (t.classList.contains("pkb-tile") && e.animationName === "pkb-clr") t.classList.remove("pkb-clr");
|
|
833
997
|
});
|
|
834
998
|
const ring = (t) => {
|
|
835
999
|
const r = document.createElement("span");
|
|
@@ -848,20 +1012,20 @@ function createPackingBingo(container, options = {}) {
|
|
|
848
1012
|
board.appendChild(el);
|
|
849
1013
|
el.addEventListener("animationend", () => el.remove());
|
|
850
1014
|
};
|
|
851
|
-
const prog = (t,
|
|
1015
|
+
const prog = (t, t02, d) => Math.max(0, Math.min(1, (t - t02) / d));
|
|
852
1016
|
const ease = (k) => 1 - Math.pow(1 - k, 3);
|
|
853
1017
|
let tw = 0;
|
|
854
1018
|
function countScore() {
|
|
855
1019
|
cancelAnimationFrame(tw);
|
|
856
|
-
const a = shown, b = score,
|
|
1020
|
+
const a = shown, b = score, t02 = performance.now();
|
|
857
1021
|
const st = (t) => {
|
|
858
1022
|
if (dead) return;
|
|
859
|
-
const k = prog(t,
|
|
1023
|
+
const k = prog(t, t02, 450);
|
|
860
1024
|
shown = Math.round(a + (b - a) * ease(k));
|
|
861
1025
|
$("sc").textContent = shown.toLocaleString();
|
|
862
1026
|
if (k < 1) tw = requestAnimationFrame(st);
|
|
863
1027
|
};
|
|
864
|
-
st(
|
|
1028
|
+
st(t02);
|
|
865
1029
|
if (!calm) replay($("sp"), "pkb-bump");
|
|
866
1030
|
}
|
|
867
1031
|
const showGain = (pts, x, y, cls = "pt") => {
|
|
@@ -869,12 +1033,12 @@ function createPackingBingo(container, options = {}) {
|
|
|
869
1033
|
countScore();
|
|
870
1034
|
};
|
|
871
1035
|
function countUp(el, a, b, d, fmt, ticks = false) {
|
|
872
|
-
const
|
|
1036
|
+
const t02 = performance.now();
|
|
873
1037
|
const g = gen;
|
|
874
1038
|
let lt = 0;
|
|
875
1039
|
const st = (t) => {
|
|
876
1040
|
if (dead || g !== gen) return;
|
|
877
|
-
const k = prog(t,
|
|
1041
|
+
const k = prog(t, t02, d);
|
|
878
1042
|
el.textContent = fmt(Math.round(a + (b - a) * ease(k)));
|
|
879
1043
|
if (ticks && k < 1 && t - lt > 55) {
|
|
880
1044
|
sfx.tick();
|
|
@@ -883,7 +1047,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
883
1047
|
if (k < 1) requestAnimationFrame(st);
|
|
884
1048
|
else if (!calm) replay(el, "pkb-bump");
|
|
885
1049
|
};
|
|
886
|
-
st(
|
|
1050
|
+
st(t02);
|
|
887
1051
|
}
|
|
888
1052
|
const ctr = (t) => {
|
|
889
1053
|
return { x: t.offsetLeft + t.offsetWidth / 2, y: t.offsetTop + t.offsetHeight / 2 };
|
|
@@ -902,6 +1066,120 @@ function createPackingBingo(container, options = {}) {
|
|
|
902
1066
|
return `<g${fresh.includes(k) ? ' class="pkb-draw"' : ""}><line class="pkb-u" ${at}/><line class="pkb-o" ${at}/></g>`;
|
|
903
1067
|
}).join("");
|
|
904
1068
|
}
|
|
1069
|
+
const readyEl = container.querySelector('[data-r="ready"]');
|
|
1070
|
+
const watch = $("watch");
|
|
1071
|
+
let phase = "idle";
|
|
1072
|
+
let t0 = 0, watchTimer = 0;
|
|
1073
|
+
let finalMs = null;
|
|
1074
|
+
const cdTimers = /* @__PURE__ */ new Set();
|
|
1075
|
+
const cd = (fn, ms) => {
|
|
1076
|
+
const id = window.setTimeout(() => {
|
|
1077
|
+
cdTimers.delete(id);
|
|
1078
|
+
if (!dead) fn();
|
|
1079
|
+
}, ms);
|
|
1080
|
+
cdTimers.add(id);
|
|
1081
|
+
};
|
|
1082
|
+
const stopTimers = () => {
|
|
1083
|
+
cdTimers.forEach((id) => clearTimeout(id));
|
|
1084
|
+
cdTimers.clear();
|
|
1085
|
+
clearInterval(watchTimer);
|
|
1086
|
+
};
|
|
1087
|
+
const elapsed = () => phase === "play" ? performance.now() - t0 : finalMs ?? 0;
|
|
1088
|
+
const showWatch = () => {
|
|
1089
|
+
watch.textContent = formatTime(elapsed());
|
|
1090
|
+
};
|
|
1091
|
+
function startWatch() {
|
|
1092
|
+
phase = "play";
|
|
1093
|
+
t0 = performance.now();
|
|
1094
|
+
finalMs = null;
|
|
1095
|
+
clearInterval(watchTimer);
|
|
1096
|
+
watchTimer = window.setInterval(showWatch, 100);
|
|
1097
|
+
watch.classList.remove("pkb-done");
|
|
1098
|
+
watch.classList.add("pkb-run");
|
|
1099
|
+
showWatch();
|
|
1100
|
+
syncFin();
|
|
1101
|
+
}
|
|
1102
|
+
const finBtn = $("fin");
|
|
1103
|
+
const shufBtn = container.querySelector('[data-r="shuf"]');
|
|
1104
|
+
const syncFin = () => {
|
|
1105
|
+
finBtn.disabled = !(inf() && phase === "play");
|
|
1106
|
+
if (shufBtn) shufBtn.disabled = inf() && phase === "done";
|
|
1107
|
+
};
|
|
1108
|
+
function finish() {
|
|
1109
|
+
if (phase === "play") {
|
|
1110
|
+
finalMs = performance.now() - t0;
|
|
1111
|
+
watch.classList.replace("pkb-run", "pkb-done");
|
|
1112
|
+
} else if (phase !== "done") {
|
|
1113
|
+
stopTimers();
|
|
1114
|
+
openGrid(false);
|
|
1115
|
+
}
|
|
1116
|
+
phase = "done";
|
|
1117
|
+
clearInterval(watchTimer);
|
|
1118
|
+
showWatch();
|
|
1119
|
+
}
|
|
1120
|
+
function openGrid(fade) {
|
|
1121
|
+
grid.removeAttribute("inert");
|
|
1122
|
+
if (!readyEl || readyEl.hidden) return;
|
|
1123
|
+
const focused = document.activeElement === readyEl;
|
|
1124
|
+
readyEl.classList.add("pkb-gone");
|
|
1125
|
+
board.classList.remove("pkb-waiting");
|
|
1126
|
+
readyEl.tabIndex = -1;
|
|
1127
|
+
if (focused) grid.querySelector("input")?.focus({ preventScroll: true });
|
|
1128
|
+
if (fade) cd(() => readyEl.hidden = true, GO_MS);
|
|
1129
|
+
else readyEl.hidden = true;
|
|
1130
|
+
}
|
|
1131
|
+
function toReady() {
|
|
1132
|
+
stopTimers();
|
|
1133
|
+
finalMs = null;
|
|
1134
|
+
watch.classList.remove("pkb-run", "pkb-done");
|
|
1135
|
+
phase = readyEl ? "ready" : "idle";
|
|
1136
|
+
showWatch();
|
|
1137
|
+
syncFin();
|
|
1138
|
+
board.classList.remove("pkb-over");
|
|
1139
|
+
grid.removeAttribute("inert");
|
|
1140
|
+
if (!readyEl) return;
|
|
1141
|
+
readyEl.hidden = false;
|
|
1142
|
+
readyEl.removeAttribute("tabindex");
|
|
1143
|
+
readyEl.classList.remove("pkb-gone", "pkb-counting");
|
|
1144
|
+
board.classList.add("pkb-waiting");
|
|
1145
|
+
$("count").textContent = "";
|
|
1146
|
+
grid.setAttribute("inert", "");
|
|
1147
|
+
}
|
|
1148
|
+
let resetAsked = false;
|
|
1149
|
+
const restartGame = () => {
|
|
1150
|
+
if (!controlled || !checked.some(Boolean)) toReady();
|
|
1151
|
+
else resetAsked = true;
|
|
1152
|
+
};
|
|
1153
|
+
function skipReady() {
|
|
1154
|
+
if (phase === "play" || phase === "done") return;
|
|
1155
|
+
stopTimers();
|
|
1156
|
+
openGrid(false);
|
|
1157
|
+
startWatch();
|
|
1158
|
+
}
|
|
1159
|
+
function countdown() {
|
|
1160
|
+
if (phase !== "ready") return;
|
|
1161
|
+
phase = "count";
|
|
1162
|
+
readyEl.classList.add("pkb-counting");
|
|
1163
|
+
const count = $("count");
|
|
1164
|
+
const words = ["3", "2", "1", M.readyGo?.trim() || ja.readyGo];
|
|
1165
|
+
words.forEach((word, k) => {
|
|
1166
|
+
const go2 = k === words.length - 1;
|
|
1167
|
+
const step = () => {
|
|
1168
|
+
count.textContent = word;
|
|
1169
|
+
count.classList.toggle("pkb-go", go2);
|
|
1170
|
+
replay(count, "pkb-tick");
|
|
1171
|
+
sfx.count(go2);
|
|
1172
|
+
haptics.play(go2 ? "go" : "count");
|
|
1173
|
+
if (go2) {
|
|
1174
|
+
startWatch();
|
|
1175
|
+
openGrid(true);
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
if (k) cd(step, k * COUNT_MS);
|
|
1179
|
+
else step();
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
readyEl?.addEventListener("click", countdown);
|
|
905
1183
|
function tapFx(i, on, moment, fresh) {
|
|
906
1184
|
const p = rel(tiles[i]);
|
|
907
1185
|
if (!on) {
|
|
@@ -953,7 +1231,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
953
1231
|
});
|
|
954
1232
|
later(() => sfx.reach(), 150);
|
|
955
1233
|
}
|
|
956
|
-
function bingo(fresh, pts) {
|
|
1234
|
+
function bingo(fresh, pts, total) {
|
|
957
1235
|
const nl = fresh.length;
|
|
958
1236
|
const seq = [...new Set(fresh.flatMap((k) => cellLines[k]))].map((c) => cellTiles[c]);
|
|
959
1237
|
const step = Math.min(70, 300 / seq.length);
|
|
@@ -967,7 +1245,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
967
1245
|
);
|
|
968
1246
|
later(() => {
|
|
969
1247
|
$("bT").textContent = nl >= 3 ? M.bannerTriple : nl === 2 ? M.bannerDouble : M.bannerSingle;
|
|
970
|
-
$("bS").textContent = nl > 1 ? M.bannerMulti(nl) : M.bannerNth(
|
|
1248
|
+
$("bS").textContent = nl > 1 ? M.bannerMulti(nl) : M.bannerNth(total);
|
|
971
1249
|
replay($("banner"), "pkb-show");
|
|
972
1250
|
if (!calm) {
|
|
973
1251
|
replay($("flash"), "pkb-go");
|
|
@@ -984,12 +1262,16 @@ function createPackingBingo(container, options = {}) {
|
|
|
984
1262
|
perfectPending = true;
|
|
985
1263
|
perfectEl.classList.remove("pkb-settled");
|
|
986
1264
|
const bonus = gotP ? 0 : 1e3;
|
|
1265
|
+
const speed = gotP || finalMs == null ? 0 : speedBonus(N, finalMs);
|
|
987
1266
|
gotP = true;
|
|
988
|
-
score += bonus;
|
|
1267
|
+
score += bonus + speed;
|
|
989
1268
|
cellTiles.forEach((t, c) => hop(t, far(c) * 180 / Math.max(1, n - 1)));
|
|
990
1269
|
sfx.perfect();
|
|
991
1270
|
if (!calm) replay($("flash"), "pkb-go");
|
|
1271
|
+
setResult(false);
|
|
992
1272
|
$("pbody").textContent = perfectText(M, N);
|
|
1273
|
+
$("ptime").hidden = finalMs == null;
|
|
1274
|
+
$("pT").textContent = formatTime(finalMs ?? 0, 2);
|
|
993
1275
|
$("sScore").textContent = "0";
|
|
994
1276
|
$("sCombo").textContent = "\xD70";
|
|
995
1277
|
$("sBingo").textContent = "0";
|
|
@@ -1005,9 +1287,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
1005
1287
|
burst(BW, BH, 90, { angle: -Math.PI * 2 / 3, spread: 0.8, speed: 22, g: 0.32, star: 0.3 });
|
|
1006
1288
|
rain(3500);
|
|
1007
1289
|
if (bonus) floatText(M.perfectBonus(bonus), BW / 2, BH * 0.16, "big");
|
|
1290
|
+
const gap = Math.min(34, Math.max(14, BW * 0.056)) * 1.3 + 6;
|
|
1291
|
+
if (speed) floatText((M.speedBonus ?? ja.speedBonus)(speed), BW / 2, Math.max(gap / 2 + 4, BH * 0.16 - gap), "big");
|
|
1008
1292
|
countUp($("sScore"), 0, score, 1300, (v) => v.toLocaleString(), true);
|
|
1009
1293
|
countUp($("sCombo"), 0, maxCombo, 700, (v) => "\xD7" + v);
|
|
1010
|
-
countUp($("sBingo"), 0,
|
|
1294
|
+
countUp($("sBingo"), 0, resultLines(), 700, String);
|
|
1011
1295
|
countScore();
|
|
1012
1296
|
}, PERFECT_STAMP_DELAY_MS);
|
|
1013
1297
|
later(() => emojiRain(3e3), 1500);
|
|
@@ -1018,10 +1302,67 @@ function createPackingBingo(container, options = {}) {
|
|
|
1018
1302
|
beat?.(pulse);
|
|
1019
1303
|
}, at)
|
|
1020
1304
|
);
|
|
1021
|
-
const e = { score, maxCombo };
|
|
1305
|
+
const e = { score, maxCombo, time: finalMs == null ? null : Math.round(finalMs), speedBonus: speed };
|
|
1022
1306
|
events.push(() => options.onPerfect?.(e));
|
|
1023
1307
|
}
|
|
1308
|
+
function clearNow(animate) {
|
|
1309
|
+
clearTimeout(clearTimer);
|
|
1310
|
+
clearTimer = 0;
|
|
1311
|
+
if (!pending.size) return;
|
|
1312
|
+
const gone = [...pending].filter((i) => checked[i]);
|
|
1313
|
+
pending.clear();
|
|
1314
|
+
checked = checked.slice();
|
|
1315
|
+
gone.forEach((i) => {
|
|
1316
|
+
checked[i] = false;
|
|
1317
|
+
ins[i].checked = false;
|
|
1318
|
+
got.delete(i);
|
|
1319
|
+
});
|
|
1320
|
+
gotL.clear();
|
|
1321
|
+
const left = completeLines();
|
|
1322
|
+
left.forEach((k) => gotL.add(lineKey(lines[k])));
|
|
1323
|
+
prevLines = new Set(left);
|
|
1324
|
+
render(null, true);
|
|
1325
|
+
if (!animate || !gone.length) return;
|
|
1326
|
+
sfx.clear(gone.length);
|
|
1327
|
+
gone.forEach((i, j) => {
|
|
1328
|
+
const t = tiles[i];
|
|
1329
|
+
if (!calm) {
|
|
1330
|
+
t.style.setProperty("--pkb-cd", Math.min(j * 28, 280) + "ms");
|
|
1331
|
+
t.classList.remove("pkb-squash", "pkb-hop");
|
|
1332
|
+
replay(t, "pkb-clr");
|
|
1333
|
+
}
|
|
1334
|
+
const p = rel(t);
|
|
1335
|
+
burst(p.x, p.y, 9, { speed: 4.5, g: 0.06, life: 45, size: 0.75, star: 1, colors: SPARK.slice(1) });
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
function scheduleClear(ms) {
|
|
1339
|
+
const at = performance.now() + ms;
|
|
1340
|
+
if (clearTimer && at <= clearAt) return;
|
|
1341
|
+
clearTimeout(clearTimer);
|
|
1342
|
+
clearAt = at;
|
|
1343
|
+
clearTimer = window.setTimeout(() => {
|
|
1344
|
+
clearTimer = 0;
|
|
1345
|
+
if (!dead) clearNow(true);
|
|
1346
|
+
}, ms);
|
|
1347
|
+
}
|
|
1348
|
+
const dropClear = () => {
|
|
1349
|
+
clearTimeout(clearTimer);
|
|
1350
|
+
clearTimer = 0;
|
|
1351
|
+
pending.clear();
|
|
1352
|
+
};
|
|
1353
|
+
function setResult(fin) {
|
|
1354
|
+
const title = fin ? M.finishTitle?.trim() || ja.finishTitle : M.perfectTitle;
|
|
1355
|
+
perfectEl.classList.toggle("pkb-fin", fin);
|
|
1356
|
+
$("pcard").setAttribute("aria-label", title);
|
|
1357
|
+
$("medal").textContent = fin ? M.finishStamp?.trim() || ja.finishStamp : M.perfectStamp;
|
|
1358
|
+
$("ptitle").textContent = title;
|
|
1359
|
+
$("pTl").textContent = fin ? M.finishTime?.trim() || ja.finishTime : M.statTime?.trim() || ja.statTime;
|
|
1360
|
+
$("again").hidden = !fin;
|
|
1361
|
+
}
|
|
1362
|
+
const resultLines = () => inf() ? cleared : cur.length;
|
|
1363
|
+
const resultFocus = () => $(perfectEl.classList.contains("pkb-fin") ? "again" : "close");
|
|
1024
1364
|
function cancelFx() {
|
|
1365
|
+
if (pending.size) clearNow(false);
|
|
1025
1366
|
gen++;
|
|
1026
1367
|
perfectPending = false;
|
|
1027
1368
|
timers.forEach((id) => clearTimeout(id));
|
|
@@ -1042,21 +1383,32 @@ function createPackingBingo(container, options = {}) {
|
|
|
1042
1383
|
$("flash").classList.remove("pkb-go");
|
|
1043
1384
|
$("combo").classList.remove("pkb-on");
|
|
1044
1385
|
$("cbar").classList.remove("pkb-draining");
|
|
1045
|
-
cellTiles.forEach((t) => t.classList.remove("pkb-squash", "pkb-hop"));
|
|
1386
|
+
cellTiles.forEach((t) => t.classList.remove("pkb-squash", "pkb-hop", "pkb-clr"));
|
|
1046
1387
|
stamps.forEach((s) => s.classList.remove("pkb-hit"));
|
|
1047
|
-
board.classList.remove("pkb-punch", "pkb-shake");
|
|
1388
|
+
board.classList.remove("pkb-punch", "pkb-shake", "pkb-switching");
|
|
1389
|
+
mfx.classList.remove("pkb-show");
|
|
1048
1390
|
if (!keepPerfect) perfectEl.classList.remove("pkb-show", "pkb-settled");
|
|
1049
1391
|
}
|
|
1050
1392
|
const completeLines = () => lines.flatMap((l, k) => l.every((i) => checked[i]) ? [k] : []);
|
|
1051
1393
|
function render(src, quiet) {
|
|
1052
1394
|
const events = [];
|
|
1053
1395
|
const on = checked, c = on.filter(Boolean).length;
|
|
1396
|
+
const endless = inf();
|
|
1397
|
+
const all = c === N && !endless;
|
|
1054
1398
|
cur = completeLines();
|
|
1055
1399
|
const fresh = cur.filter((k) => !prevLines.has(k));
|
|
1056
1400
|
if ([...prevLines].some((k) => !cur.includes(k))) {
|
|
1057
1401
|
cancelFx();
|
|
1058
1402
|
if (c < N) perfectEl.classList.remove("pkb-show");
|
|
1059
1403
|
}
|
|
1404
|
+
if (endless) {
|
|
1405
|
+
if (!quiet) cleared += fresh.length;
|
|
1406
|
+
if (cur.length) {
|
|
1407
|
+
const known = pending.size;
|
|
1408
|
+
cur.forEach((k) => lines[k].forEach((i) => pending.add(i)));
|
|
1409
|
+
if (pending.size > known || !clearTimer) scheduleClear(!quiet && fresh.length ? CLEAR_MS : FLY_MS + 200);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1060
1412
|
const hit = new Set(cur.flatMap((k) => cellLines[k]));
|
|
1061
1413
|
const reach = /* @__PURE__ */ new Set();
|
|
1062
1414
|
for (const l of lines) {
|
|
@@ -1065,26 +1417,31 @@ function createPackingBingo(container, options = {}) {
|
|
|
1065
1417
|
}
|
|
1066
1418
|
tiles.forEach((t, i) => {
|
|
1067
1419
|
t.classList.toggle("pkb-on", on[i]);
|
|
1420
|
+
const lock = on[i] && (!uncheck || pending.has(i));
|
|
1421
|
+
t.classList.toggle("pkb-lock", lock);
|
|
1422
|
+
if (lock) ins[i].setAttribute("aria-readonly", "true");
|
|
1423
|
+
else ins[i].removeAttribute("aria-readonly");
|
|
1068
1424
|
t.classList.toggle("pkb-line", hit.has(L.itemCell[slotOf[i]]));
|
|
1069
1425
|
t.classList.toggle("pkb-reach", reach.has(i));
|
|
1070
1426
|
});
|
|
1071
1427
|
freeTile?.classList.toggle("pkb-line", hit.has(L.center));
|
|
1072
|
-
board.classList.toggle("pkb-all",
|
|
1073
|
-
kick.classList.toggle("pkb-gold",
|
|
1074
|
-
kick.textContent =
|
|
1428
|
+
board.classList.toggle("pkb-all", all);
|
|
1429
|
+
kick.classList.toggle("pkb-gold", all);
|
|
1430
|
+
kick.textContent = all ? M.perfectTag : M.tag;
|
|
1075
1431
|
$("cnt").textContent = `${c}/${N}`;
|
|
1076
|
-
|
|
1432
|
+
const total = endless ? cleared : cur.length;
|
|
1433
|
+
$("bng").textContent = String(total);
|
|
1077
1434
|
const bp = $("bp");
|
|
1078
|
-
if (
|
|
1435
|
+
if (total > lastN) {
|
|
1079
1436
|
bp.classList.remove("pkb-hot");
|
|
1080
1437
|
void bp.offsetWidth;
|
|
1081
1438
|
}
|
|
1082
|
-
bp.classList.toggle("pkb-hot",
|
|
1083
|
-
lastN =
|
|
1439
|
+
bp.classList.toggle("pkb-hot", total > 0);
|
|
1440
|
+
lastN = total;
|
|
1084
1441
|
drawLines(fresh);
|
|
1085
1442
|
if (src !== null) {
|
|
1086
1443
|
const newReach = [...reach].some((i) => !prevReach.has(i));
|
|
1087
|
-
const moment = !quiet &&
|
|
1444
|
+
const moment = !quiet && all && !prevAll ? "perfect" : !quiet && fresh.length ? "bingo" : !quiet && newReach ? "reach" : "tap";
|
|
1088
1445
|
tapFx(src, on[src], moment, fresh);
|
|
1089
1446
|
}
|
|
1090
1447
|
const newL = fresh.map((k) => lineKey(lines[k])).filter((key) => !gotL.has(key));
|
|
@@ -1093,10 +1450,10 @@ function createPackingBingo(container, options = {}) {
|
|
|
1093
1450
|
score += linePts;
|
|
1094
1451
|
if (quiet) {
|
|
1095
1452
|
if (linePts) countScore();
|
|
1096
|
-
} else if (
|
|
1453
|
+
} else if (all && !prevAll) perfect(events);
|
|
1097
1454
|
else if (fresh.length) {
|
|
1098
|
-
bingo(fresh, linePts);
|
|
1099
|
-
const e = { newLines: fresh, totalLines:
|
|
1455
|
+
bingo(fresh, linePts, total);
|
|
1456
|
+
const e = { newLines: fresh, totalLines: total, score, mode };
|
|
1100
1457
|
events.push(() => options.onBingo?.(e));
|
|
1101
1458
|
} else if (src !== null && on[src]) {
|
|
1102
1459
|
const nr = [...reach].filter((i) => !prevReach.has(i));
|
|
@@ -1104,9 +1461,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
1104
1461
|
}
|
|
1105
1462
|
prevLines = new Set(cur);
|
|
1106
1463
|
prevReach = reach;
|
|
1107
|
-
prevAll =
|
|
1464
|
+
prevAll = all;
|
|
1108
1465
|
const labels = [...reach].map((i) => items[i].label);
|
|
1109
|
-
$("msg").textContent =
|
|
1466
|
+
$("msg").textContent = all ? M.statusDone : labels.length ? M.statusReach(labels) : cur.length ? M.statusLines(cur.length, N - c) : M.statusStart(N - c);
|
|
1110
1467
|
return events;
|
|
1111
1468
|
}
|
|
1112
1469
|
const flush = (events) => {
|
|
@@ -1116,9 +1473,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
1116
1473
|
}
|
|
1117
1474
|
};
|
|
1118
1475
|
const save = () => {
|
|
1119
|
-
if (storageKey) writeStore(storageKey, JSON.stringify(checked));
|
|
1476
|
+
if (storageKey && !inf()) writeStore(storageKey, JSON.stringify(checked));
|
|
1120
1477
|
};
|
|
1121
1478
|
function apply(next, src, quiet) {
|
|
1479
|
+
if (phase === "idle" && src !== null && next[src]) startWatch();
|
|
1480
|
+
if (!inf() && next.every(Boolean)) finish();
|
|
1122
1481
|
checked = next;
|
|
1123
1482
|
ins.forEach((x, i) => x.checked = checked[i]);
|
|
1124
1483
|
const events = render(src, quiet);
|
|
@@ -1126,8 +1485,12 @@ function createPackingBingo(container, options = {}) {
|
|
|
1126
1485
|
return events;
|
|
1127
1486
|
}
|
|
1128
1487
|
function baseline(next) {
|
|
1488
|
+
dropClear();
|
|
1489
|
+
cleared = 0;
|
|
1129
1490
|
cancelFx();
|
|
1130
1491
|
clearVisuals();
|
|
1492
|
+
if (!inf() && next.every(Boolean)) finish();
|
|
1493
|
+
else if ((phase === "play" || phase === "done") && !next.some(Boolean)) toReady();
|
|
1131
1494
|
checked = next;
|
|
1132
1495
|
ins.forEach((x, i) => x.checked = checked[i]);
|
|
1133
1496
|
got.clear();
|
|
@@ -1137,7 +1500,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1137
1500
|
done.forEach((k) => gotL.add(lineKey(lines[k])));
|
|
1138
1501
|
prevLines = new Set(done);
|
|
1139
1502
|
prevReach = /* @__PURE__ */ new Set();
|
|
1140
|
-
prevAll = checked.every(Boolean);
|
|
1503
|
+
prevAll = !inf() && checked.every(Boolean);
|
|
1141
1504
|
gotP = prevAll;
|
|
1142
1505
|
score = shown = got.size * 10 + gotL.size * 100;
|
|
1143
1506
|
combo = 0;
|
|
@@ -1147,7 +1510,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
1147
1510
|
save();
|
|
1148
1511
|
}
|
|
1149
1512
|
function request(next, src, opts = {}) {
|
|
1150
|
-
|
|
1513
|
+
const game = inf();
|
|
1514
|
+
if (controlled && !game) {
|
|
1515
|
+
resetAsked = !!opts.reset && resetAsked;
|
|
1151
1516
|
ins.forEach((x, i) => x.checked = checked[i]);
|
|
1152
1517
|
options.onChange?.(next.slice());
|
|
1153
1518
|
return;
|
|
@@ -1155,7 +1520,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1155
1520
|
let events = [];
|
|
1156
1521
|
if (opts.reset) baseline(next);
|
|
1157
1522
|
else events = apply(next, src, !!opts.quiet);
|
|
1158
|
-
options.onChange?.(checked.slice());
|
|
1523
|
+
if (!game) options.onChange?.(checked.slice());
|
|
1159
1524
|
flush(events);
|
|
1160
1525
|
}
|
|
1161
1526
|
function place(next) {
|
|
@@ -1174,12 +1539,12 @@ function createPackingBingo(container, options = {}) {
|
|
|
1174
1539
|
focused.focus({ preventScroll: true });
|
|
1175
1540
|
}
|
|
1176
1541
|
const saveOrder = () => {
|
|
1177
|
-
if (!orderKey) return;
|
|
1542
|
+
if (!orderKey || inf()) return;
|
|
1178
1543
|
writeStore(orderKey, JSON.stringify(order));
|
|
1179
1544
|
};
|
|
1180
1545
|
let flights = [];
|
|
1181
1546
|
function arrange(next) {
|
|
1182
|
-
if (!checked.every(Boolean)) cancelFx();
|
|
1547
|
+
if (inf() || !checked.every(Boolean)) cancelFx();
|
|
1183
1548
|
const from = calm ? null : tiles.map((t) => t.getBoundingClientRect());
|
|
1184
1549
|
flights.forEach((a) => a.cancel());
|
|
1185
1550
|
flights = [];
|
|
@@ -1215,6 +1580,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1215
1580
|
svg.innerHTML = "";
|
|
1216
1581
|
}
|
|
1217
1582
|
function shuffleCard() {
|
|
1583
|
+
if (inf() && phase === "done") return;
|
|
1218
1584
|
let next = order.slice();
|
|
1219
1585
|
do {
|
|
1220
1586
|
for (let s = N - 1; s > 0; s--) {
|
|
@@ -1229,26 +1595,93 @@ function createPackingBingo(container, options = {}) {
|
|
|
1229
1595
|
const unshuffle = () => {
|
|
1230
1596
|
if (!isIdentity(order)) arrange(identity());
|
|
1231
1597
|
};
|
|
1598
|
+
const locked = (i) => checked[i] && (!uncheck || pending.has(i));
|
|
1599
|
+
grid.addEventListener("click", (e) => {
|
|
1600
|
+
const i = ins.indexOf(e.target);
|
|
1601
|
+
if (i >= 0 && locked(i)) e.preventDefault();
|
|
1602
|
+
});
|
|
1232
1603
|
grid.addEventListener("change", (e) => {
|
|
1233
1604
|
const i = ins.indexOf(e.target);
|
|
1234
1605
|
if (i < 0) return;
|
|
1606
|
+
if (locked(i)) {
|
|
1607
|
+
ins[i].checked = true;
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1235
1610
|
const next = checked.slice();
|
|
1236
1611
|
next[i] = !next[i];
|
|
1237
1612
|
request(next, i);
|
|
1238
1613
|
});
|
|
1239
1614
|
container.querySelector('[data-r="shuf"]')?.addEventListener("click", shuffleCard);
|
|
1615
|
+
const restartInfinite = () => {
|
|
1616
|
+
baseline(normalize(void 0));
|
|
1617
|
+
toReady();
|
|
1618
|
+
};
|
|
1240
1619
|
$("reset").addEventListener("click", () => {
|
|
1620
|
+
if (inf()) return restartInfinite();
|
|
1241
1621
|
unshuffle();
|
|
1622
|
+
restartGame();
|
|
1242
1623
|
request(normalize(void 0), null, { reset: true });
|
|
1243
1624
|
});
|
|
1244
1625
|
$("close").addEventListener("click", () => perfectEl.classList.remove("pkb-show"));
|
|
1626
|
+
$("again").addEventListener("click", () => {
|
|
1627
|
+
if (!inf()) return;
|
|
1628
|
+
restartInfinite();
|
|
1629
|
+
(readyEl && !readyEl.hidden ? readyEl : grid.querySelector("input"))?.focus({ preventScroll: true });
|
|
1630
|
+
});
|
|
1631
|
+
function finishGame() {
|
|
1632
|
+
if (dead || !inf() || phase !== "play") return;
|
|
1633
|
+
cancelFx();
|
|
1634
|
+
clearVisuals();
|
|
1635
|
+
finalMs = performance.now() - t0;
|
|
1636
|
+
phase = "done";
|
|
1637
|
+
clearInterval(watchTimer);
|
|
1638
|
+
watch.classList.replace("pkb-run", "pkb-done");
|
|
1639
|
+
showWatch();
|
|
1640
|
+
syncFin();
|
|
1641
|
+
combo = 0;
|
|
1642
|
+
grid.setAttribute("inert", "");
|
|
1643
|
+
board.classList.add("pkb-over");
|
|
1644
|
+
setResult(true);
|
|
1645
|
+
$("pbody").textContent = "";
|
|
1646
|
+
$("ptime").hidden = false;
|
|
1647
|
+
$("pT").textContent = formatTime(finalMs, 2);
|
|
1648
|
+
$("sScore").textContent = "0";
|
|
1649
|
+
$("sCombo").textContent = "\xD70";
|
|
1650
|
+
$("sBingo").textContent = "0";
|
|
1651
|
+
perfectEl.classList.remove("pkb-settled");
|
|
1652
|
+
replay(perfectEl, "pkb-show");
|
|
1653
|
+
resultFocus().focus({ preventScroll: true });
|
|
1654
|
+
sfx.perfect();
|
|
1655
|
+
const beat = haptics.play("finish");
|
|
1656
|
+
if (!calm) replay($("flash"), "pkb-go");
|
|
1657
|
+
later(() => {
|
|
1658
|
+
if (!calm) replay(board, "pkb-shake");
|
|
1659
|
+
sfx.boom();
|
|
1660
|
+
beat?.(PERFECT_STAMP_BEAT);
|
|
1661
|
+
burst(0, BH, 80, { angle: -Math.PI / 3, spread: 0.8, speed: 21, g: 0.32, star: 0.5, colors: SPARK });
|
|
1662
|
+
burst(BW, BH, 80, { angle: -Math.PI * 2 / 3, spread: 0.8, speed: 21, g: 0.32, star: 0.5, colors: SPARK });
|
|
1663
|
+
rain(2500);
|
|
1664
|
+
countUp($("sScore"), 0, score, 1300, (v) => v.toLocaleString(), true);
|
|
1665
|
+
countUp($("sCombo"), 0, maxCombo, 700, (v) => "\xD7" + v);
|
|
1666
|
+
countUp($("sBingo"), 0, cleared, 700, String);
|
|
1667
|
+
}, PERFECT_STAMP_DELAY_MS);
|
|
1668
|
+
[1250, 1700, 2200].forEach(
|
|
1669
|
+
(at, k) => later(() => {
|
|
1670
|
+
firework(BW * (0.15 + Math.random() * 0.7), BH * (0.12 + Math.random() * 0.3));
|
|
1671
|
+
beat?.(PERFECT_FIREWORKS[k * 2].beat);
|
|
1672
|
+
}, at)
|
|
1673
|
+
);
|
|
1674
|
+
const e = { score, maxCombo, lines: cleared, time: Math.round(finalMs) };
|
|
1675
|
+
options.onFinish?.(e);
|
|
1676
|
+
}
|
|
1677
|
+
finBtn.addEventListener("click", finishGame);
|
|
1245
1678
|
perfectEl.addEventListener("click", (e) => {
|
|
1246
1679
|
if (e.target === perfectEl) perfectEl.classList.remove("pkb-show");
|
|
1247
1680
|
});
|
|
1248
1681
|
if (demo) {
|
|
1249
1682
|
let busy = false;
|
|
1250
1683
|
const prepare = () => {
|
|
1251
|
-
if (checked.every(Boolean)) request(normalize(void 0), null, { reset: true });
|
|
1684
|
+
if (!inf() && checked.every(Boolean)) request(normalize(void 0), null, { reset: true });
|
|
1252
1685
|
else perfectEl.classList.remove("pkb-show");
|
|
1253
1686
|
};
|
|
1254
1687
|
const run = async (steps) => {
|
|
@@ -1256,6 +1689,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1256
1689
|
busy = true;
|
|
1257
1690
|
try {
|
|
1258
1691
|
prepare();
|
|
1692
|
+
skipReady();
|
|
1259
1693
|
const g = gen;
|
|
1260
1694
|
for (const s of steps) {
|
|
1261
1695
|
await wait(s.delay);
|
|
@@ -1269,21 +1703,100 @@ function createPackingBingo(container, options = {}) {
|
|
|
1269
1703
|
}
|
|
1270
1704
|
};
|
|
1271
1705
|
$("dB").addEventListener("click", () => {
|
|
1706
|
+
if (inf() && phase === "done") return;
|
|
1272
1707
|
const open = () => lines.map((l) => l.filter((i) => !checked[i])).filter((m) => m.length).sort((a, b) => a.length - b.length)[0] ?? [];
|
|
1273
|
-
const pick = checked.every(Boolean) ? [...lines[0]] : open();
|
|
1708
|
+
const pick = !inf() && checked.every(Boolean) ? [...lines[0]] : open();
|
|
1274
1709
|
void run(pick.map((i) => ({ i, delay: 320 })));
|
|
1275
1710
|
});
|
|
1276
1711
|
$("dP").addEventListener("click", () => {
|
|
1712
|
+
if (inf()) return;
|
|
1277
1713
|
const all = items.map((_, i) => i);
|
|
1278
1714
|
const miss = checked.every(Boolean) ? all : all.filter((i) => !checked[i]);
|
|
1279
1715
|
void run(miss.map((i, k) => k < miss.length - 1 ? { i, delay: 170, quiet: true } : { i, delay: 450 }));
|
|
1280
1716
|
});
|
|
1281
1717
|
$("dR").addEventListener("click", () => {
|
|
1282
|
-
if (busy) return;
|
|
1718
|
+
if (busy || inf()) return;
|
|
1283
1719
|
unshuffle();
|
|
1720
|
+
restartGame();
|
|
1284
1721
|
request(demoStart(), null, { reset: true });
|
|
1285
1722
|
});
|
|
1286
1723
|
}
|
|
1724
|
+
const modeEl = container.querySelector('[data-r="mode"]');
|
|
1725
|
+
const radios = modeEl ? Array.from(modeEl.querySelectorAll("button[data-m]")) : [];
|
|
1726
|
+
const syncMode = () => {
|
|
1727
|
+
root.classList.toggle("pkb-inf", inf());
|
|
1728
|
+
radios.forEach((b) => {
|
|
1729
|
+
const on = b.dataset.m === mode;
|
|
1730
|
+
b.setAttribute("aria-checked", String(on));
|
|
1731
|
+
b.tabIndex = on ? 0 : -1;
|
|
1732
|
+
});
|
|
1733
|
+
};
|
|
1734
|
+
function modeFx() {
|
|
1735
|
+
const endless = inf();
|
|
1736
|
+
const title = endless ? M.infiniteTitle?.trim() || ja.infiniteTitle : M.normalTitle?.trim() || ja.normalTitle;
|
|
1737
|
+
const sub = endless ? M.infiniteSub?.trim() || ja.infiniteSub : M.normalSub?.trim() || ja.normalSub;
|
|
1738
|
+
const sparks = endless && !calm ? Array.from({ length: 18 }, (_, k) => {
|
|
1739
|
+
const x = (5 + Math.random() * 90).toFixed(1), y = (6 + Math.random() * 88).toFixed(1);
|
|
1740
|
+
const s = (0.45 + Math.random() * 0.95).toFixed(2), t = Math.round(100 + Math.random() * 950);
|
|
1741
|
+
return `<i style="--x:${x}%;--y:${y}%;--s:${s};--t:${t}ms;--c:${SPARK[k % SPARK.length]}"></i>`;
|
|
1742
|
+
}).join("") : "";
|
|
1743
|
+
mfx.className = "pkb-mfx " + (endless ? "pkb-mfx-inf" : "pkb-mfx-n");
|
|
1744
|
+
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>`;
|
|
1745
|
+
replay(mfx, "pkb-show");
|
|
1746
|
+
board.classList.add("pkb-switching");
|
|
1747
|
+
if (modeEl && !calm) replay(modeEl, "pkb-bump");
|
|
1748
|
+
sfx.mode(endless);
|
|
1749
|
+
haptics.play(endless ? "infinite" : "normal");
|
|
1750
|
+
const picked = radios.find((b) => b.dataset.m === mode);
|
|
1751
|
+
if (picked) {
|
|
1752
|
+
const p = rel(picked);
|
|
1753
|
+
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 });
|
|
1754
|
+
}
|
|
1755
|
+
later(() => {
|
|
1756
|
+
const c = rel(gw);
|
|
1757
|
+
if (endless) burst(c.x, c.y, 90, { speed: 12, g: 0.07, life: 90, star: 0.75, colors: SPARK });
|
|
1758
|
+
else {
|
|
1759
|
+
if (!calm) replay(board, "pkb-punch");
|
|
1760
|
+
burst(c.x, c.y, 70, { speed: 11, star: 0.2 });
|
|
1761
|
+
}
|
|
1762
|
+
}, endless ? 260 : 430);
|
|
1763
|
+
}
|
|
1764
|
+
mfx.addEventListener("animationend", (e) => {
|
|
1765
|
+
if (e.target !== mfx) return;
|
|
1766
|
+
mfx.classList.remove("pkb-show");
|
|
1767
|
+
board.classList.remove("pkb-switching");
|
|
1768
|
+
mfx.innerHTML = "";
|
|
1769
|
+
});
|
|
1770
|
+
function setMode(next) {
|
|
1771
|
+
if (dead || next === mode || next !== "normal" && next !== "infinite") return;
|
|
1772
|
+
const back = packing;
|
|
1773
|
+
if (next === "infinite") packing = { checked: checked.slice(), order: order.slice() };
|
|
1774
|
+
else packing = null;
|
|
1775
|
+
mode = next;
|
|
1776
|
+
resetAsked = false;
|
|
1777
|
+
if (back && back.order.some((v, s) => v !== order[s])) {
|
|
1778
|
+
flights.forEach((a) => a.cancel());
|
|
1779
|
+
flights = [];
|
|
1780
|
+
clearTimeout(landing);
|
|
1781
|
+
landing = 0;
|
|
1782
|
+
place(back.order);
|
|
1783
|
+
}
|
|
1784
|
+
syncMode();
|
|
1785
|
+
toReady();
|
|
1786
|
+
baseline(back ? back.checked : normalize(void 0));
|
|
1787
|
+
modeFx();
|
|
1788
|
+
options.onModeChange?.(mode);
|
|
1789
|
+
}
|
|
1790
|
+
modeEl?.addEventListener("click", (e) => {
|
|
1791
|
+
const b = e.target.closest("button[data-m]");
|
|
1792
|
+
if (b) setMode(b.dataset.m);
|
|
1793
|
+
});
|
|
1794
|
+
modeEl?.addEventListener("keydown", (e) => {
|
|
1795
|
+
if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(e.key)) return;
|
|
1796
|
+
e.preventDefault();
|
|
1797
|
+
setMode(inf() ? "normal" : "infinite");
|
|
1798
|
+
radios.find((b) => b.dataset.m === mode)?.focus();
|
|
1799
|
+
});
|
|
1287
1800
|
let startOrder = null;
|
|
1288
1801
|
if (orderKey) {
|
|
1289
1802
|
try {
|
|
@@ -1305,7 +1818,10 @@ function createPackingBingo(container, options = {}) {
|
|
|
1305
1818
|
if (!initial && options.defaultChecked) initial = normalize(options.defaultChecked);
|
|
1306
1819
|
if (!initial && demo) initial = demoStart();
|
|
1307
1820
|
fit();
|
|
1308
|
-
|
|
1821
|
+
if (inf()) packing = { checked: initial ?? normalize(void 0), order: order.slice() };
|
|
1822
|
+
syncMode();
|
|
1823
|
+
baseline(inf() ? normalize(void 0) : initial ?? normalize(void 0));
|
|
1824
|
+
if (phase !== "done") toReady();
|
|
1309
1825
|
const ro = typeof ResizeObserver === "function" ? new ResizeObserver(() => {
|
|
1310
1826
|
fit();
|
|
1311
1827
|
drawLines();
|
|
@@ -1314,7 +1830,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1314
1830
|
ro?.observe(gw);
|
|
1315
1831
|
const onVisibility = () => {
|
|
1316
1832
|
if (!document.hidden) {
|
|
1317
|
-
if (restorePerfectFocus && perfectEl.classList.contains("pkb-show"))
|
|
1833
|
+
if (restorePerfectFocus && perfectEl.classList.contains("pkb-show")) resultFocus().focus({ preventScroll: true });
|
|
1318
1834
|
restorePerfectFocus = false;
|
|
1319
1835
|
return;
|
|
1320
1836
|
}
|
|
@@ -1325,7 +1841,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1325
1841
|
if (keepPerfect) {
|
|
1326
1842
|
$("sScore").textContent = score.toLocaleString();
|
|
1327
1843
|
$("sCombo").textContent = "\xD7" + maxCombo;
|
|
1328
|
-
$("sBingo").textContent = String(
|
|
1844
|
+
$("sBingo").textContent = String(resultLines());
|
|
1329
1845
|
perfectEl.classList.add("pkb-show", "pkb-settled");
|
|
1330
1846
|
}
|
|
1331
1847
|
combo = 0;
|
|
@@ -1335,6 +1851,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
1335
1851
|
setChecked(next, opts = {}) {
|
|
1336
1852
|
if (dead) return;
|
|
1337
1853
|
const n2 = normalize(next);
|
|
1854
|
+
if (packing) {
|
|
1855
|
+
packing.checked = n2;
|
|
1856
|
+
resetAsked = false;
|
|
1857
|
+
if (storageKey) writeStore(storageKey, JSON.stringify(n2));
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
if (resetAsked && !n2.some(Boolean)) toReady();
|
|
1861
|
+
resetAsked = false;
|
|
1338
1862
|
const changed = n2.flatMap((v, i) => v !== checked[i] ? [i] : []);
|
|
1339
1863
|
if (!changed.length) return;
|
|
1340
1864
|
if (!opts.animate || changed.length !== 1) {
|
|
@@ -1345,12 +1869,20 @@ function createPackingBingo(container, options = {}) {
|
|
|
1345
1869
|
},
|
|
1346
1870
|
reset() {
|
|
1347
1871
|
if (dead) return;
|
|
1872
|
+
if (inf()) return restartInfinite();
|
|
1348
1873
|
unshuffle();
|
|
1874
|
+
restartGame();
|
|
1349
1875
|
request(normalize(void 0), null, { reset: true });
|
|
1350
1876
|
},
|
|
1351
1877
|
shuffle() {
|
|
1352
1878
|
if (!dead) shuffleCard();
|
|
1353
1879
|
},
|
|
1880
|
+
setMode(next) {
|
|
1881
|
+
setMode(next);
|
|
1882
|
+
},
|
|
1883
|
+
finish() {
|
|
1884
|
+
finishGame();
|
|
1885
|
+
},
|
|
1354
1886
|
refresh() {
|
|
1355
1887
|
if (!dead) render(null, true);
|
|
1356
1888
|
},
|
|
@@ -1360,20 +1892,25 @@ function createPackingBingo(container, options = {}) {
|
|
|
1360
1892
|
lines: cur.slice(),
|
|
1361
1893
|
score,
|
|
1362
1894
|
maxCombo,
|
|
1363
|
-
complete: checked.every(Boolean),
|
|
1895
|
+
complete: !inf() && checked.every(Boolean),
|
|
1896
|
+
mode,
|
|
1897
|
+
cleared,
|
|
1898
|
+
finished: inf() && phase === "done",
|
|
1899
|
+
packing: packing && { checked: packing.checked.slice(), order: packing.order.slice() },
|
|
1364
1900
|
size: n,
|
|
1365
1901
|
free: L.free,
|
|
1366
|
-
order: order.slice()
|
|
1902
|
+
order: order.slice(),
|
|
1903
|
+
time: Math.round(elapsed())
|
|
1367
1904
|
};
|
|
1368
1905
|
},
|
|
1369
1906
|
destroy() {
|
|
1370
1907
|
if (dead) return;
|
|
1371
1908
|
dead = true;
|
|
1372
|
-
|
|
1373
|
-
if (playDialog?.open) playDialog.close();
|
|
1909
|
+
stopTimers();
|
|
1374
1910
|
timers.forEach((id) => clearTimeout(id));
|
|
1375
1911
|
timers.clear();
|
|
1376
1912
|
clearTimeout(landing);
|
|
1913
|
+
clearTimeout(clearTimer);
|
|
1377
1914
|
flights.forEach((a) => a.cancel());
|
|
1378
1915
|
cancelAnimationFrame(raf);
|
|
1379
1916
|
cancelAnimationFrame(tw);
|
|
@@ -1393,5 +1930,7 @@ export {
|
|
|
1393
1930
|
en,
|
|
1394
1931
|
resolveStorageKey,
|
|
1395
1932
|
LINES,
|
|
1933
|
+
formatTime,
|
|
1934
|
+
speedBonus,
|
|
1396
1935
|
createPackingBingo
|
|
1397
1936
|
};
|