packing-bingo 0.4.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 +114 -24
- package/dist/{chunk-BAA2CFUA.js → chunk-A5JVNVWS.js} +821 -95
- package/dist/core.cjs +825 -97
- package/dist/core.d.cts +141 -5
- package/dist/core.d.ts +141 -5
- package/dist/core.js +7 -3
- package/dist/index.cjs +842 -103
- 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 +127 -30
- package/package.json +4 -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,6 +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",
|
|
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",
|
|
136
159
|
stamp: "OK",
|
|
137
160
|
free: "FREE",
|
|
138
161
|
reachBadge: "\u30EA\u30FC\u30C1",
|
|
@@ -143,15 +166,15 @@ var ja = {
|
|
|
143
166
|
bannerNth: (n) => `${n} \u30E9\u30A4\u30F3\u76EE\uFF01`,
|
|
144
167
|
bannerMulti: (n) => `\u4E00\u6C17\u306B ${n} \u30E9\u30A4\u30F3\uFF01`,
|
|
145
168
|
statusStart: () => "",
|
|
146
|
-
statusLines: (
|
|
169
|
+
statusLines: () => "",
|
|
147
170
|
statusReach: () => "",
|
|
148
|
-
statusDone: "
|
|
171
|
+
statusDone: "",
|
|
149
172
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
150
173
|
perfectStamp: "\u6E96\u5099\n\u5B8C\u4E86",
|
|
151
174
|
perfectTitle: "\u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01",
|
|
152
|
-
perfectBody:
|
|
153
|
-
\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u3001\u3044\u3063\u3066\u3089\u3063\u3057\u3083\u3044\uFF01`,
|
|
175
|
+
perfectBody: "",
|
|
154
176
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
177
|
+
speedBonus: (n) => `SPEED BONUS +${n.toLocaleString()}`,
|
|
155
178
|
statScore: "SCORE",
|
|
156
179
|
statCombo: "MAX COMBO",
|
|
157
180
|
statLines: "BINGO",
|
|
@@ -174,6 +197,27 @@ var en = {
|
|
|
174
197
|
shuffle: "\u{1F500} Shuffle",
|
|
175
198
|
soundOn: "\u{1F50A} Sound on",
|
|
176
199
|
soundOff: "\u{1F507} Muted",
|
|
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",
|
|
177
221
|
stamp: "OK",
|
|
178
222
|
free: "FREE",
|
|
179
223
|
reachBadge: "REACH",
|
|
@@ -184,15 +228,15 @@ var en = {
|
|
|
184
228
|
bannerNth: (n) => `Line ${n}!`,
|
|
185
229
|
bannerMulti: (n) => `${n} lines at once!`,
|
|
186
230
|
statusStart: () => "",
|
|
187
|
-
statusLines: (
|
|
231
|
+
statusLines: () => "",
|
|
188
232
|
statusReach: () => "",
|
|
189
|
-
statusDone: "
|
|
233
|
+
statusDone: "",
|
|
190
234
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
191
235
|
perfectStamp: "ALL\nSET",
|
|
192
236
|
perfectTitle: "Perfect!",
|
|
193
|
-
perfectBody:
|
|
194
|
-
Have a great trip!`,
|
|
237
|
+
perfectBody: "",
|
|
195
238
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
239
|
+
speedBonus: (n) => `SPEED BONUS +${n.toLocaleString()}`,
|
|
196
240
|
statScore: "SCORE",
|
|
197
241
|
statCombo: "MAX COMBO",
|
|
198
242
|
statLines: "BINGO",
|
|
@@ -209,8 +253,40 @@ var hz = (semitones) => 523.25 * Math.pow(2, semitones / 12);
|
|
|
209
253
|
function createSound(isMuted) {
|
|
210
254
|
let ac = null;
|
|
211
255
|
let out = null;
|
|
256
|
+
const voices = /* @__PURE__ */ new Map();
|
|
257
|
+
function track(source, gain, start, nodes) {
|
|
258
|
+
voices.set(source, { gain, start });
|
|
259
|
+
source.onended = () => {
|
|
260
|
+
voices.delete(source);
|
|
261
|
+
source.disconnect();
|
|
262
|
+
nodes.forEach((n) => n.disconnect());
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function stop() {
|
|
266
|
+
const now = ac?.currentTime ?? 0;
|
|
267
|
+
voices.forEach(({ gain, start }, source) => {
|
|
268
|
+
try {
|
|
269
|
+
if (start > now) {
|
|
270
|
+
gain.gain.cancelScheduledValues(now);
|
|
271
|
+
gain.gain.setValueAtTime(0, now);
|
|
272
|
+
source.stop(now);
|
|
273
|
+
} else {
|
|
274
|
+
if (typeof gain.gain.cancelAndHoldAtTime === "function") gain.gain.cancelAndHoldAtTime(now);
|
|
275
|
+
else {
|
|
276
|
+
const value = gain.gain.value;
|
|
277
|
+
gain.gain.cancelScheduledValues(now);
|
|
278
|
+
gain.gain.setValueAtTime(value, now);
|
|
279
|
+
}
|
|
280
|
+
gain.gain.linearRampToValueAtTime(0, now + 0.01);
|
|
281
|
+
source.stop(now + 0.01);
|
|
282
|
+
}
|
|
283
|
+
} catch {
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
voices.clear();
|
|
287
|
+
}
|
|
212
288
|
function audio() {
|
|
213
|
-
if (isMuted() || typeof window === "undefined") return null;
|
|
289
|
+
if (isMuted() || typeof window === "undefined" || document.hidden) return null;
|
|
214
290
|
const ua = navigator.userActivation;
|
|
215
291
|
if (!ac && ua && !ua.hasBeenActive) return null;
|
|
216
292
|
if (!ac) {
|
|
@@ -238,6 +314,7 @@ function createSound(isMuted) {
|
|
|
238
314
|
g.gain.exponentialRampToValueAtTime(vol, t + 0.01);
|
|
239
315
|
g.gain.exponentialRampToValueAtTime(1e-4, t + dur);
|
|
240
316
|
o.connect(g).connect(out);
|
|
317
|
+
track(o, g, t, [g]);
|
|
241
318
|
o.start(t);
|
|
242
319
|
o.stop(t + dur + 0.05);
|
|
243
320
|
}
|
|
@@ -258,15 +335,19 @@ function createSound(isMuted) {
|
|
|
258
335
|
f.Q.value = 0.7;
|
|
259
336
|
g.gain.value = vol;
|
|
260
337
|
s.connect(f).connect(g).connect(out);
|
|
338
|
+
track(s, g, t, [f, g]);
|
|
261
339
|
s.start(t);
|
|
262
340
|
}
|
|
263
341
|
const step = (k) => hz(SCALE[Math.min(Math.max(k, 0), SCALE.length - 1)]);
|
|
264
342
|
return {
|
|
265
343
|
pop(k) {
|
|
266
344
|
const f = step(k);
|
|
345
|
+
const energy = Math.min(3, Math.floor((k + 1) / 3));
|
|
267
346
|
tone(f, 0, 0.16, "triangle", 0.22);
|
|
268
347
|
tone(f * 2, 0, 0.08, "sine", 0.06);
|
|
269
|
-
tone(
|
|
348
|
+
tone(170 + energy * 15, 0, 0.11, "sine", 0.28, 48);
|
|
349
|
+
noise(0, 0.035, 0.1 + energy * 0.025, 1800);
|
|
350
|
+
if (energy) tone(f * 1.5, 0.035, 0.11, "triangle", 0.04 + energy * 0.015);
|
|
270
351
|
},
|
|
271
352
|
off: () => tone(392, 0, 0.1, "sine", 0.1, 262),
|
|
272
353
|
reach() {
|
|
@@ -296,7 +377,39 @@ function createSound(isMuted) {
|
|
|
296
377
|
tone(hz(7), 0.34, 0.12, "triangle", 0.14);
|
|
297
378
|
tone(hz(12), 0.42, 0.2, "triangle", 0.14);
|
|
298
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
|
+
},
|
|
410
|
+
stop,
|
|
299
411
|
close() {
|
|
412
|
+
stop();
|
|
300
413
|
if (ac) void ac.close().catch(() => {
|
|
301
414
|
});
|
|
302
415
|
ac = null;
|
|
@@ -305,6 +418,64 @@ function createSound(isMuted) {
|
|
|
305
418
|
};
|
|
306
419
|
}
|
|
307
420
|
|
|
421
|
+
// src/haptics.ts
|
|
422
|
+
var owner = null;
|
|
423
|
+
function createHaptics(enabled) {
|
|
424
|
+
const token = {};
|
|
425
|
+
let generation = 0;
|
|
426
|
+
const stop = () => {
|
|
427
|
+
generation++;
|
|
428
|
+
if (owner !== token) return;
|
|
429
|
+
owner = null;
|
|
430
|
+
try {
|
|
431
|
+
navigator.vibrate?.(0);
|
|
432
|
+
} catch {
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
const send = (pattern) => {
|
|
436
|
+
if (!enabled || document.hidden || typeof navigator.vibrate !== "function") return false;
|
|
437
|
+
if (navigator.userActivation && !navigator.userActivation.hasBeenActive) return false;
|
|
438
|
+
try {
|
|
439
|
+
if (navigator.vibrate(pattern)) {
|
|
440
|
+
owner = token;
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
} catch {
|
|
444
|
+
}
|
|
445
|
+
return false;
|
|
446
|
+
};
|
|
447
|
+
return {
|
|
448
|
+
play(moment, combo = 1, lines = 1, impactAt = 360) {
|
|
449
|
+
const current = ++generation;
|
|
450
|
+
const tier = Math.min(3, Math.floor(combo / 3));
|
|
451
|
+
const lead = Math.max(80, impactAt - 40);
|
|
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
|
+
);
|
|
456
|
+
if (!send(pattern)) return;
|
|
457
|
+
return (beat) => {
|
|
458
|
+
if (generation === current && owner === token) send([...beat]);
|
|
459
|
+
};
|
|
460
|
+
},
|
|
461
|
+
stop
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// src/celebration.ts
|
|
466
|
+
var PERFECT_REVEAL_MS = 520;
|
|
467
|
+
var PERFECT_STAMP_DELAY_MS = 720;
|
|
468
|
+
var PERFECT_STAMP_BEAT = [260, 45, 160];
|
|
469
|
+
var PERFECT_FIREWORKS = [
|
|
470
|
+
{ at: 1820, beat: [200] },
|
|
471
|
+
{ at: 2170, beat: [100, 35, 160] },
|
|
472
|
+
{ at: 2520, beat: [260] },
|
|
473
|
+
{ at: 3e3, beat: [110, 40, 110] },
|
|
474
|
+
{ at: 3430, beat: [320] },
|
|
475
|
+
{ at: 3950, beat: [140, 40, 200] },
|
|
476
|
+
{ at: 4540, beat: [460] }
|
|
477
|
+
];
|
|
478
|
+
|
|
308
479
|
// src/storage-key.ts
|
|
309
480
|
function resolveStorageKey(options) {
|
|
310
481
|
if (options.controlled) return null;
|
|
@@ -332,13 +503,17 @@ var CSS = `
|
|
|
332
503
|
.pkb-board.pkb-punch{animation:pkb-punch .4s cubic-bezier(.3,1.6,.5,1)}
|
|
333
504
|
@keyframes pkb-punch{30%{transform:scale(1.018)}}
|
|
334
505
|
.pkb-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px 16px;margin-bottom:18px}
|
|
335
|
-
.pkb-tags{display:flex;gap:8px;align-items:center}
|
|
506
|
+
.pkb-tags{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
|
|
336
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}
|
|
337
508
|
.pkb-kicker:empty{display:none}
|
|
338
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}
|
|
339
510
|
@keyframes pkb-rb{to{background-position:-200% 0}}
|
|
340
511
|
.pkb button{font-family:var(--pk-body)}
|
|
341
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}
|
|
342
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}
|
|
343
518
|
.pkb-sub{font-weight:800;font-size:13px;margin-top:4px;word-break:auto-phrase}
|
|
344
519
|
.pkb-side{display:flex;flex-direction:column;gap:8px;flex:none}
|
|
@@ -356,12 +531,12 @@ var CSS = `
|
|
|
356
531
|
.pkb-combo b{font-family:var(--pk-display);font-weight:400;font-size:13px;color:var(--pk-red);white-space:nowrap}
|
|
357
532
|
.pkb-bar{flex:1;height:9px;border:2px solid var(--pk-ink);border-radius:999px;background:var(--pk-paper);overflow:hidden}
|
|
358
533
|
.pkb-bar i{display:block;height:100%;background:var(--pk-red);transform-origin:left}
|
|
359
|
-
.pkb-combo.pkb-on .pkb-bar i{animation:pkb-drain 2.5s linear forwards}
|
|
534
|
+
.pkb-combo.pkb-on .pkb-bar i.pkb-draining{animation:pkb-drain 2.5s linear forwards}
|
|
360
535
|
@keyframes pkb-drain{from{transform:scaleX(1)}to{transform:scaleX(0)}}
|
|
361
536
|
.pkb-gw{position:relative}
|
|
362
537
|
.pkb-grid{display:grid;grid-template-columns:repeat(var(--pk-n),minmax(0,1fr));gap:calc(12px*var(--pk-d))}
|
|
363
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}
|
|
364
|
-
.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)}
|
|
365
540
|
.pkb-tile input{position:absolute;opacity:0;pointer-events:none;width:1px;height:1px}
|
|
366
541
|
.pkb-tile:has(input:focus-visible){outline:3px solid var(--pk-ink);outline-offset:3px}
|
|
367
542
|
.pkb-emo{font-size:max(14px,calc(32px*var(--pk-s)));line-height:1;transition:opacity .2s}
|
|
@@ -369,6 +544,7 @@ var CSS = `
|
|
|
369
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}
|
|
370
545
|
.pkb-tile.pkb-on .pkb-stamp{opacity:.9;transform:translate(-50%,-50%) rotate(-16deg) scale(1)}
|
|
371
546
|
.pkb-tile.pkb-on .pkb-emo,.pkb-tile.pkb-on .pkb-lbl{opacity:.35}
|
|
547
|
+
.pkb-tile.pkb-lock{cursor:default}
|
|
372
548
|
.pkb-tile.pkb-free{cursor:default;container-type:inline-size}
|
|
373
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)}
|
|
374
550
|
.pkb-tile.pkb-line{background-color:var(--pk-mint)}
|
|
@@ -378,12 +554,14 @@ var CSS = `
|
|
|
378
554
|
@keyframes pkb-aura{50%{box-shadow:4px 4px 0 var(--pk-ink),0 0 0 7px rgba(232,71,59,.35)}}
|
|
379
555
|
.pkb-tile.pkb-reach::after{content:attr(data-reach);position:absolute;top:-11px;right:-8px;z-index:3;background:var(--pk-red);color:#fff;font-size:10.5px;font-weight:800;letter-spacing:.06em;padding:3px 8px;border-radius:999px;border:2px solid var(--pk-ink);transform:rotate(6deg);animation:pkb-bob 1.1s ease-in-out infinite}
|
|
380
556
|
@keyframes pkb-bob{50%{transform:rotate(6deg) translateY(-4px)}}
|
|
381
|
-
.pkb-tile.pkb-squash{animation:pkb-squash .
|
|
382
|
-
@keyframes pkb-squash{0%{transform:scale(.
|
|
557
|
+
.pkb-tile.pkb-squash{z-index:1;animation:pkb-squash .38s cubic-bezier(.16,1,.3,1)}
|
|
558
|
+
@keyframes pkb-squash{0%{transform:translate(3px,4px) scale(calc(.9 - var(--pkb-impact,0)*.018));box-shadow:0 0 0 var(--pk-ink)}38%{transform:translateY(calc(-2px - var(--pkb-impact,0)*1px)) scale(calc(1.045 + var(--pkb-impact,0)*.012))}70%{transform:translateY(1px) scale(.985)}100%{transform:none}}
|
|
559
|
+
.pkb-tile.pkb-on .pkb-stamp.pkb-hit{animation:pkb-stamp-hit .32s cubic-bezier(.16,1,.3,1) both}
|
|
560
|
+
@keyframes pkb-stamp-hit{0%{opacity:0;transform:translate(-50%,-50%) rotate(-26deg) scale(2.1)}28%{opacity:1;transform:translate(-50%,-50%) rotate(-12deg) scale(.88)}52%{transform:translate(-50%,-50%) rotate(-18deg) scale(1.08)}100%{opacity:.9;transform:translate(-50%,-50%) rotate(-16deg) scale(1)}}
|
|
383
561
|
.pkb-tile.pkb-hop{animation:pkb-hop .55s cubic-bezier(.3,1.6,.5,1) var(--pkb-d,0ms) both}
|
|
384
562
|
@keyframes pkb-hop{35%{transform:translateY(-14px) scale(1.07)}}
|
|
385
563
|
.pkb-ring{position:absolute;left:50%;top:50%;width:70%;aspect-ratio:1;border:4px solid var(--pk-red);border-radius:50%;pointer-events:none;z-index:4;animation:pkb-ring .5s ease-out forwards}
|
|
386
|
-
@keyframes pkb-ring{from{transform:translate(-50%,-50%) scale(.3);opacity:.9}to{transform:translate(-50%,-50%) scale(1.9);opacity:0}}
|
|
564
|
+
@keyframes pkb-ring{from{transform:translate(-50%,-50%) scale(.3);opacity:.9}to{transform:translate(-50%,-50%) scale(calc(1.9 + var(--pkb-impact,0)*.18));opacity:0}}
|
|
387
565
|
.pkb-lines{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible;z-index:2;transition:opacity .4s}
|
|
388
566
|
.pkb-board.pkb-all .pkb-lines{opacity:0}
|
|
389
567
|
.pkb-lines line{fill:none;stroke-linecap:round;stroke-dasharray:1;stroke-dashoffset:0}
|
|
@@ -394,10 +572,11 @@ var CSS = `
|
|
|
394
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}
|
|
395
573
|
.pkb-foot>span{flex:1 1 14em}
|
|
396
574
|
.pkb-foot>span:empty{flex-basis:0}
|
|
397
|
-
.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}
|
|
398
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}
|
|
399
577
|
.pkb-shuf{background:var(--pk-paper);box-shadow:2px 2px 0 var(--pk-ink)}
|
|
400
|
-
.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}
|
|
401
580
|
.pkb-flash{position:absolute;inset:0;z-index:7;background:#fff;opacity:0;pointer-events:none}
|
|
402
581
|
.pkb-flash.pkb-go{animation:pkb-flash .4s ease-out}
|
|
403
582
|
@keyframes pkb-flash{0%{opacity:.8}100%{opacity:0}}
|
|
@@ -414,15 +593,43 @@ var CSS = `
|
|
|
414
593
|
.pkb-float.pkb-big{font-size:clamp(14px,5.6cqi,34px);color:#FFE066}
|
|
415
594
|
.pkb-float.pkb-rch{font-size:20px;color:var(--pk-red);-webkit-text-stroke:4px var(--pk-paper)}
|
|
416
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}
|
|
417
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}
|
|
418
|
-
.
|
|
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}
|
|
419
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)}
|
|
420
624
|
.pkb-perfect.pkb-show .pkb-card{transform:none}
|
|
421
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)}
|
|
422
626
|
.pkb-perfect.pkb-show .pkb-medal{animation:pkb-slam .38s cubic-bezier(.5,0,.75,0) .35s forwards}
|
|
627
|
+
.pkb-perfect.pkb-settled,.pkb-perfect.pkb-settled .pkb-card{transition:none}
|
|
628
|
+
.pkb-perfect.pkb-settled .pkb-medal{animation:none;opacity:1;transform:rotate(-14deg) scale(1)}
|
|
423
629
|
@keyframes pkb-slam{to{opacity:1;transform:rotate(-14deg) scale(1)}}
|
|
424
630
|
.pkb-ptitle{font-family:var(--pk-display);font-weight:400;font-size:30px;line-height:1.3;color:inherit}
|
|
425
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}
|
|
426
633
|
.pkb-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:16px}
|
|
427
634
|
.pkb-stats>div{border:2px solid var(--pk-ink);border-radius:12px;padding:6px 4px 5px;background:#fff}
|
|
428
635
|
.pkb-stats small{display:block;font-size:9.5px;font-weight:800;letter-spacing:.1em}
|
|
@@ -432,10 +639,72 @@ var CSS = `
|
|
|
432
639
|
.pkb-fx{position:absolute;inset:0;width:100%;height:100%;z-index:10;pointer-events:none}
|
|
433
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}
|
|
434
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}
|
|
435
|
-
|
|
436
|
-
|
|
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}}
|
|
437
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}}
|
|
438
|
-
@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-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{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}}
|
|
439
708
|
@keyframes pkb-fade{0%,100%{opacity:0}15%,80%{opacity:1}}
|
|
440
709
|
`;
|
|
441
710
|
|
|
@@ -462,7 +731,22 @@ function writeStore(key, value) {
|
|
|
462
731
|
}
|
|
463
732
|
var glintDelay = (c, n) => `${+((Math.floor(c / n) + c % n) * 0.36 / Math.max(1, n - 1)).toFixed(3)}s`;
|
|
464
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
|
+
}
|
|
465
747
|
var FLY_MS = 560;
|
|
748
|
+
var CLEAR_MS = 1e3;
|
|
749
|
+
var SPARK = ["#FFFFFF", "#FFE066", "#FF9F1C", "#E8473B", "#9B5DE5", "#2F6FED", "#1FA971"];
|
|
466
750
|
function ensureStyles() {
|
|
467
751
|
if (document.getElementById("pkb-styles")) return;
|
|
468
752
|
const s = document.createElement("style");
|
|
@@ -470,7 +754,8 @@ function ensureStyles() {
|
|
|
470
754
|
s.textContent = CSS;
|
|
471
755
|
document.head.appendChild(s);
|
|
472
756
|
}
|
|
473
|
-
|
|
757
|
+
var opt = (v, fallback) => v ?? fallback ?? "";
|
|
758
|
+
function template(L, M, { demo, shuffle, vibrate, ready, modes }) {
|
|
474
759
|
const n = L.size;
|
|
475
760
|
const input = vibrate ? `<input type="checkbox" switch role="checkbox">` : `<input type="checkbox">`;
|
|
476
761
|
const tiles = Array.from({ length: n * n }, (_, c) => {
|
|
@@ -482,9 +767,15 @@ function template(L, M, { demo, shuffle, vibrate }) {
|
|
|
482
767
|
}).join("");
|
|
483
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)}`;
|
|
484
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>` : "";
|
|
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>` : "";
|
|
485
776
|
return `<div class="pkb" style="${scale}"><div class="pkb-board">
|
|
486
777
|
<div class="pkb-head"><div>
|
|
487
|
-
<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>
|
|
488
779
|
<h2 class="pkb-title">${esc(M.title)}</h2>${M.subtitle ? `<p class="pkb-sub">${esc(M.subtitle)}</p>` : ""}</div>
|
|
489
780
|
<div class="pkb-side"><div class="pkb-score">
|
|
490
781
|
<div class="pkb-pill"><small>${esc(M.packed)}</small><b data-r="cnt"></b></div>
|
|
@@ -493,13 +784,14 @@ function template(L, M, { demo, shuffle, vibrate }) {
|
|
|
493
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>
|
|
494
785
|
<div class="pkb-gw"><div class="pkb-grid">${tiles}</div><svg class="pkb-lines" aria-hidden="true"></svg>
|
|
495
786
|
<div class="pkb-banner" data-r="banner" aria-hidden="true"><b data-r="bT"></b><span data-r="bS"></span></div></div>
|
|
496
|
-
<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>
|
|
497
788
|
<div class="pkb-flash" data-r="flash"></div>
|
|
498
|
-
<div class="pkb-perfect" data-r="perfect"><div class="pkb-card" role="dialog" aria-label="${esc(M.perfectTitle)}">
|
|
499
|
-
<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>
|
|
500
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>
|
|
501
|
-
<button type="button" class="pkb-close" data-r="close">${esc(M.close)}</button></div></div>
|
|
502
|
-
<canvas class="pkb-fx"></canvas></div>${demoBar}</div>`;
|
|
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>`;
|
|
503
795
|
}
|
|
504
796
|
var perfectText = (M, total) => typeof M.perfectBody === "function" ? M.perfectBody(total) : M.perfectBody;
|
|
505
797
|
function createPackingBingo(container, options = {}) {
|
|
@@ -531,7 +823,15 @@ function createPackingBingo(container, options = {}) {
|
|
|
531
823
|
const orderKey = storageKey && ORDER_PREFIX + storageKey;
|
|
532
824
|
const demo = !!options.demo;
|
|
533
825
|
const vibrate = options.vibrate !== false;
|
|
826
|
+
const uncheck = options.uncheck === true;
|
|
534
827
|
const calm = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
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;
|
|
535
835
|
let dead = false;
|
|
536
836
|
const timers = /* @__PURE__ */ new Set();
|
|
537
837
|
const later = (fn, ms) => {
|
|
@@ -544,12 +844,19 @@ function createPackingBingo(container, options = {}) {
|
|
|
544
844
|
const wait = (ms) => new Promise((r) => window.setTimeout(r, ms));
|
|
545
845
|
let gen = 0;
|
|
546
846
|
ensureStyles();
|
|
547
|
-
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
|
+
});
|
|
548
854
|
const $ = (r) => {
|
|
549
855
|
const el = container.querySelector(`[data-r="${r}"]`);
|
|
550
856
|
if (!el) throw new Error(`[packing-bingo] internal: missing element "${r}"`);
|
|
551
857
|
return el;
|
|
552
858
|
};
|
|
859
|
+
const root = container.querySelector(".pkb");
|
|
553
860
|
const board = container.querySelector(".pkb-board");
|
|
554
861
|
const gw = container.querySelector(".pkb-gw");
|
|
555
862
|
const grid = container.querySelector(".pkb-grid");
|
|
@@ -560,12 +867,15 @@ function createPackingBingo(container, options = {}) {
|
|
|
560
867
|
const tiles = L.itemCell.map((c) => cellTiles[c]);
|
|
561
868
|
const freeTile = L.center >= 0 ? cellTiles[L.center] : null;
|
|
562
869
|
const ins = tiles.map((t) => t.querySelector("input"));
|
|
563
|
-
const
|
|
870
|
+
const stamps = tiles.map((t) => t.querySelector(".pkb-stamp"));
|
|
871
|
+
const kick = $("kick"), snd = $("snd"), perfectEl = $("perfect"), mfx = $("mfx");
|
|
564
872
|
let checked = normalize(void 0);
|
|
565
873
|
let cur = [];
|
|
566
874
|
let prevLines = /* @__PURE__ */ new Set();
|
|
567
875
|
let prevReach = /* @__PURE__ */ new Set();
|
|
568
876
|
let prevAll = false;
|
|
877
|
+
let perfectPending = false, restorePerfectFocus = false;
|
|
878
|
+
let perfectBeat;
|
|
569
879
|
let lastN = 0;
|
|
570
880
|
let score = 0, shown = 0, combo = 0, maxCombo = 0, lastT = 0, gotP = false;
|
|
571
881
|
const got = /* @__PURE__ */ new Set(), gotL = /* @__PURE__ */ new Set();
|
|
@@ -579,6 +889,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
579
889
|
snd.addEventListener("click", () => {
|
|
580
890
|
muted = !muted;
|
|
581
891
|
writeStore(MUTE_KEY, muted ? "1" : "0");
|
|
892
|
+
if (muted) sfx.stop();
|
|
582
893
|
setSnd();
|
|
583
894
|
if (!muted) sfx.pop(4);
|
|
584
895
|
});
|
|
@@ -593,7 +904,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
593
904
|
cv.width = Math.round(BW * dpr);
|
|
594
905
|
cv.height = Math.round(BH * dpr);
|
|
595
906
|
};
|
|
596
|
-
const piece = (x, y, vx, vy, g, s, life, shape2) => ({
|
|
907
|
+
const piece = (x, y, vx, vy, g, s, life, shape2, colors = CONFETTI) => ({
|
|
597
908
|
x,
|
|
598
909
|
y,
|
|
599
910
|
vx,
|
|
@@ -605,7 +916,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
605
916
|
h: (3 + Math.random() * 5) * s,
|
|
606
917
|
r: Math.random() * 6,
|
|
607
918
|
vr: (Math.random() - 0.5) * 0.35,
|
|
608
|
-
c:
|
|
919
|
+
c: colors[Math.random() * colors.length | 0]
|
|
609
920
|
});
|
|
610
921
|
const go = () => {
|
|
611
922
|
if (!raf && ctx) raf = requestAnimationFrame(tick);
|
|
@@ -616,7 +927,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
616
927
|
const a = (o.angle ?? -Math.PI / 2) + (Math.random() - 0.5) * (o.spread ?? Math.PI * 2);
|
|
617
928
|
const v = (o.speed ?? 10) * (0.35 + Math.random() * 0.85);
|
|
618
929
|
const sh = Math.random() < (o.star ?? 0.15) ? "star" : Math.random() < 0.3 ? "round" : "rect";
|
|
619
|
-
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));
|
|
620
931
|
}
|
|
621
932
|
go();
|
|
622
933
|
}
|
|
@@ -711,8 +1022,10 @@ function createPackingBingo(container, options = {}) {
|
|
|
711
1022
|
};
|
|
712
1023
|
grid.addEventListener("animationend", (e) => {
|
|
713
1024
|
const t = e.target;
|
|
1025
|
+
if (e.animationName === "pkb-stamp-hit") t.classList.remove("pkb-hit");
|
|
714
1026
|
if (t.classList.contains("pkb-tile") && (e.animationName === "pkb-hop" || e.animationName === "pkb-squash"))
|
|
715
1027
|
t.classList.remove(e.animationName);
|
|
1028
|
+
if (t.classList.contains("pkb-tile") && e.animationName === "pkb-clr") t.classList.remove("pkb-clr");
|
|
716
1029
|
});
|
|
717
1030
|
const ring = (t) => {
|
|
718
1031
|
const r = document.createElement("span");
|
|
@@ -720,15 +1033,6 @@ function createPackingBingo(container, options = {}) {
|
|
|
720
1033
|
t.appendChild(r);
|
|
721
1034
|
r.addEventListener("animationend", () => r.remove());
|
|
722
1035
|
};
|
|
723
|
-
const buzz = (pattern) => {
|
|
724
|
-
if (!vibrate) return;
|
|
725
|
-
const ua = navigator.userActivation;
|
|
726
|
-
if (ua && !ua.hasBeenActive) return;
|
|
727
|
-
try {
|
|
728
|
-
navigator.vibrate?.(pattern);
|
|
729
|
-
} catch {
|
|
730
|
-
}
|
|
731
|
-
};
|
|
732
1036
|
const floatText = (txt, x, y, cls, delay = 0, color) => {
|
|
733
1037
|
const el = document.createElement("div");
|
|
734
1038
|
el.className = "pkb-float pkb-" + cls;
|
|
@@ -740,20 +1044,20 @@ function createPackingBingo(container, options = {}) {
|
|
|
740
1044
|
board.appendChild(el);
|
|
741
1045
|
el.addEventListener("animationend", () => el.remove());
|
|
742
1046
|
};
|
|
743
|
-
const prog = (t,
|
|
1047
|
+
const prog = (t, t02, d) => Math.max(0, Math.min(1, (t - t02) / d));
|
|
744
1048
|
const ease = (k) => 1 - Math.pow(1 - k, 3);
|
|
745
1049
|
let tw = 0;
|
|
746
1050
|
function countScore() {
|
|
747
1051
|
cancelAnimationFrame(tw);
|
|
748
|
-
const a = shown, b = score,
|
|
1052
|
+
const a = shown, b = score, t02 = performance.now();
|
|
749
1053
|
const st = (t) => {
|
|
750
1054
|
if (dead) return;
|
|
751
|
-
const k = prog(t,
|
|
1055
|
+
const k = prog(t, t02, 450);
|
|
752
1056
|
shown = Math.round(a + (b - a) * ease(k));
|
|
753
1057
|
$("sc").textContent = shown.toLocaleString();
|
|
754
1058
|
if (k < 1) tw = requestAnimationFrame(st);
|
|
755
1059
|
};
|
|
756
|
-
st(
|
|
1060
|
+
st(t02);
|
|
757
1061
|
if (!calm) replay($("sp"), "pkb-bump");
|
|
758
1062
|
}
|
|
759
1063
|
const showGain = (pts, x, y, cls = "pt") => {
|
|
@@ -761,12 +1065,12 @@ function createPackingBingo(container, options = {}) {
|
|
|
761
1065
|
countScore();
|
|
762
1066
|
};
|
|
763
1067
|
function countUp(el, a, b, d, fmt, ticks = false) {
|
|
764
|
-
const
|
|
1068
|
+
const t02 = performance.now();
|
|
765
1069
|
const g = gen;
|
|
766
1070
|
let lt = 0;
|
|
767
1071
|
const st = (t) => {
|
|
768
1072
|
if (dead || g !== gen) return;
|
|
769
|
-
const k = prog(t,
|
|
1073
|
+
const k = prog(t, t02, d);
|
|
770
1074
|
el.textContent = fmt(Math.round(a + (b - a) * ease(k)));
|
|
771
1075
|
if (ticks && k < 1 && t - lt > 55) {
|
|
772
1076
|
sfx.tick();
|
|
@@ -775,7 +1079,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
775
1079
|
if (k < 1) requestAnimationFrame(st);
|
|
776
1080
|
else if (!calm) replay(el, "pkb-bump");
|
|
777
1081
|
};
|
|
778
|
-
st(
|
|
1082
|
+
st(t02);
|
|
779
1083
|
}
|
|
780
1084
|
const ctr = (t) => {
|
|
781
1085
|
return { x: t.offsetLeft + t.offsetWidth / 2, y: t.offsetTop + t.offsetHeight / 2 };
|
|
@@ -794,25 +1098,147 @@ function createPackingBingo(container, options = {}) {
|
|
|
794
1098
|
return `<g${fresh.includes(k) ? ' class="pkb-draw"' : ""}><line class="pkb-u" ${at}/><line class="pkb-o" ${at}/></g>`;
|
|
795
1099
|
}).join("");
|
|
796
1100
|
}
|
|
797
|
-
|
|
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);
|
|
1215
|
+
function tapFx(i, on, moment, fresh) {
|
|
798
1216
|
const p = rel(tiles[i]);
|
|
799
1217
|
if (!on) {
|
|
1218
|
+
stamps[i].classList.remove("pkb-hit");
|
|
800
1219
|
combo = 0;
|
|
801
1220
|
$("combo").classList.remove("pkb-on");
|
|
802
1221
|
sfx.off();
|
|
803
|
-
|
|
1222
|
+
haptics.play("off");
|
|
804
1223
|
return;
|
|
805
1224
|
}
|
|
806
1225
|
const now = performance.now();
|
|
807
1226
|
combo = now - lastT < COMBO_MS ? combo + 1 : 1;
|
|
808
1227
|
lastT = now;
|
|
809
1228
|
maxCombo = Math.max(maxCombo, combo);
|
|
1229
|
+
const seqLength = new Set(fresh.flatMap((k) => cellLines[k])).size;
|
|
1230
|
+
const impactAt = Math.min(70 * seqLength, 300) + 60;
|
|
1231
|
+
const beat = haptics.play(moment, combo, fresh.length, impactAt);
|
|
1232
|
+
perfectBeat = moment === "perfect" ? beat : void 0;
|
|
1233
|
+
const energy = Math.min(3, Math.floor(combo / 3));
|
|
810
1234
|
sfx.pop(combo - 1);
|
|
811
1235
|
if (!calm) {
|
|
1236
|
+
tiles[i].style.setProperty("--pkb-impact", String(energy));
|
|
812
1237
|
replay(tiles[i], "pkb-squash");
|
|
1238
|
+
replay(stamps[i], "pkb-hit");
|
|
813
1239
|
ring(tiles[i]);
|
|
814
1240
|
}
|
|
815
|
-
burst(p.x, p.y, 18, { speed: 6, g: 0.2, life: 30, size: 0.6, star: 0.5 });
|
|
1241
|
+
burst(p.x, p.y, 18 + energy * 6, { speed: 6 + energy, g: 0.2, life: 30, size: 0.6, star: 0.5 });
|
|
816
1242
|
if (!got.has(i)) {
|
|
817
1243
|
got.add(i);
|
|
818
1244
|
score += 10 * combo;
|
|
@@ -821,9 +1247,10 @@ function createPackingBingo(container, options = {}) {
|
|
|
821
1247
|
if (combo >= 2) {
|
|
822
1248
|
floatText(M.comboPop(combo), p.x, p.y + 22, "cmb", 90, COMBO_COLORS[(combo - 2) % COMBO_COLORS.length]);
|
|
823
1249
|
$("cmbN").textContent = M.combo(combo);
|
|
824
|
-
|
|
1250
|
+
$("combo").classList.add("pkb-on");
|
|
1251
|
+
replay($("cbar"), "pkb-draining");
|
|
1252
|
+
if (!calm) replay($("cmbN"), "pkb-bump");
|
|
825
1253
|
}
|
|
826
|
-
buzz(18);
|
|
827
1254
|
}
|
|
828
1255
|
$("cbar").addEventListener("animationend", () => {
|
|
829
1256
|
$("combo").classList.remove("pkb-on");
|
|
@@ -836,7 +1263,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
836
1263
|
});
|
|
837
1264
|
later(() => sfx.reach(), 150);
|
|
838
1265
|
}
|
|
839
|
-
function bingo(fresh, pts) {
|
|
1266
|
+
function bingo(fresh, pts, total) {
|
|
840
1267
|
const nl = fresh.length;
|
|
841
1268
|
const seq = [...new Set(fresh.flatMap((k) => cellLines[k]))].map((c) => cellTiles[c]);
|
|
842
1269
|
const step = Math.min(70, 300 / seq.length);
|
|
@@ -850,7 +1277,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
850
1277
|
);
|
|
851
1278
|
later(() => {
|
|
852
1279
|
$("bT").textContent = nl >= 3 ? M.bannerTriple : nl === 2 ? M.bannerDouble : M.bannerSingle;
|
|
853
|
-
$("bS").textContent = nl > 1 ? M.bannerMulti(nl) : M.bannerNth(
|
|
1280
|
+
$("bS").textContent = nl > 1 ? M.bannerMulti(nl) : M.bannerNth(total);
|
|
854
1281
|
replay($("banner"), "pkb-show");
|
|
855
1282
|
if (!calm) {
|
|
856
1283
|
replay($("flash"), "pkb-go");
|
|
@@ -859,54 +1286,126 @@ function createPackingBingo(container, options = {}) {
|
|
|
859
1286
|
const c = rel(gw);
|
|
860
1287
|
burst(c.x, c.y, nl > 1 ? 180 : 110, { speed: nl > 1 ? 16 : 13, star: 0.3 });
|
|
861
1288
|
sfx.bingo(nl);
|
|
862
|
-
buzz(nl > 1 ? [40, 60, 40, 60, 90] : [40, 60, 40]);
|
|
863
1289
|
if (pts) later(() => showGain(pts, c.x, c.y + 105, "big"), 350);
|
|
864
1290
|
}, seq.length * step + 60);
|
|
865
1291
|
}
|
|
866
1292
|
function perfect(events) {
|
|
1293
|
+
const beat = perfectBeat;
|
|
1294
|
+
perfectPending = true;
|
|
1295
|
+
perfectEl.classList.remove("pkb-settled");
|
|
867
1296
|
const bonus = gotP ? 0 : 1e3;
|
|
1297
|
+
const speed = gotP || finalMs == null ? 0 : speedBonus(N, finalMs);
|
|
868
1298
|
gotP = true;
|
|
869
|
-
score += bonus;
|
|
1299
|
+
score += bonus + speed;
|
|
870
1300
|
cellTiles.forEach((t, c) => hop(t, far(c) * 180 / Math.max(1, n - 1)));
|
|
871
1301
|
sfx.perfect();
|
|
872
1302
|
if (!calm) replay($("flash"), "pkb-go");
|
|
1303
|
+
setResult(false);
|
|
873
1304
|
$("pbody").textContent = perfectText(M, N);
|
|
1305
|
+
$("ptime").hidden = finalMs == null;
|
|
1306
|
+
$("pT").textContent = formatTime(finalMs ?? 0, 2);
|
|
874
1307
|
$("sScore").textContent = "0";
|
|
875
1308
|
$("sCombo").textContent = "\xD70";
|
|
876
1309
|
$("sBingo").textContent = "0";
|
|
877
1310
|
later(() => {
|
|
1311
|
+
perfectPending = false;
|
|
878
1312
|
replay(perfectEl, "pkb-show");
|
|
879
1313
|
$("close").focus({ preventScroll: true });
|
|
880
1314
|
later(() => {
|
|
881
1315
|
if (!calm) replay(board, "pkb-shake");
|
|
882
1316
|
sfx.boom();
|
|
1317
|
+
beat?.(PERFECT_STAMP_BEAT);
|
|
883
1318
|
burst(0, BH, 90, { angle: -Math.PI / 3, spread: 0.8, speed: 22, g: 0.32, star: 0.3 });
|
|
884
1319
|
burst(BW, BH, 90, { angle: -Math.PI * 2 / 3, spread: 0.8, speed: 22, g: 0.32, star: 0.3 });
|
|
885
1320
|
rain(3500);
|
|
886
|
-
buzz([60, 80, 60, 80, 200]);
|
|
887
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");
|
|
888
1324
|
countUp($("sScore"), 0, score, 1300, (v) => v.toLocaleString(), true);
|
|
889
1325
|
countUp($("sCombo"), 0, maxCombo, 700, (v) => "\xD7" + v);
|
|
890
|
-
countUp($("sBingo"), 0,
|
|
1326
|
+
countUp($("sBingo"), 0, resultLines(), 700, String);
|
|
891
1327
|
countScore();
|
|
892
|
-
},
|
|
893
|
-
[1300, 1650, 2e3, 2350, 2700].forEach(
|
|
894
|
-
(t) => later(() => firework(BW * (0.15 + Math.random() * 0.7), BH * (0.12 + Math.random() * 0.3)), t)
|
|
895
|
-
);
|
|
1328
|
+
}, PERFECT_STAMP_DELAY_MS);
|
|
896
1329
|
later(() => emojiRain(3e3), 1500);
|
|
897
|
-
},
|
|
898
|
-
|
|
1330
|
+
}, PERFECT_REVEAL_MS);
|
|
1331
|
+
PERFECT_FIREWORKS.forEach(
|
|
1332
|
+
({ at, beat: pulse }) => later(() => {
|
|
1333
|
+
firework(BW * (0.15 + Math.random() * 0.7), BH * (0.12 + Math.random() * 0.3));
|
|
1334
|
+
beat?.(pulse);
|
|
1335
|
+
}, at)
|
|
1336
|
+
);
|
|
1337
|
+
const e = { score, maxCombo, time: finalMs == null ? null : Math.round(finalMs), speedBonus: speed };
|
|
899
1338
|
events.push(() => options.onPerfect?.(e));
|
|
900
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");
|
|
901
1396
|
function cancelFx() {
|
|
1397
|
+
if (pending.size) clearNow(false);
|
|
902
1398
|
gen++;
|
|
1399
|
+
perfectPending = false;
|
|
903
1400
|
timers.forEach((id) => clearTimeout(id));
|
|
904
1401
|
timers.clear();
|
|
1402
|
+
haptics.stop();
|
|
1403
|
+
sfx.stop();
|
|
905
1404
|
cancelAnimationFrame(tw);
|
|
906
1405
|
shown = score;
|
|
907
1406
|
$("sc").textContent = score.toLocaleString();
|
|
908
1407
|
}
|
|
909
|
-
function clearVisuals() {
|
|
1408
|
+
function clearVisuals(keepPerfect = false) {
|
|
910
1409
|
P = [];
|
|
911
1410
|
cancelAnimationFrame(raf);
|
|
912
1411
|
raf = 0;
|
|
@@ -915,18 +1414,33 @@ function createPackingBingo(container, options = {}) {
|
|
|
915
1414
|
$("banner").classList.remove("pkb-show");
|
|
916
1415
|
$("flash").classList.remove("pkb-go");
|
|
917
1416
|
$("combo").classList.remove("pkb-on");
|
|
918
|
-
|
|
1417
|
+
$("cbar").classList.remove("pkb-draining");
|
|
1418
|
+
cellTiles.forEach((t) => t.classList.remove("pkb-squash", "pkb-hop", "pkb-clr"));
|
|
1419
|
+
stamps.forEach((s) => s.classList.remove("pkb-hit"));
|
|
1420
|
+
board.classList.remove("pkb-punch", "pkb-shake", "pkb-switching");
|
|
1421
|
+
mfx.classList.remove("pkb-show");
|
|
1422
|
+
if (!keepPerfect) perfectEl.classList.remove("pkb-show", "pkb-settled");
|
|
919
1423
|
}
|
|
920
1424
|
const completeLines = () => lines.flatMap((l, k) => l.every((i) => checked[i]) ? [k] : []);
|
|
921
1425
|
function render(src, quiet) {
|
|
922
1426
|
const events = [];
|
|
923
1427
|
const on = checked, c = on.filter(Boolean).length;
|
|
1428
|
+
const endless = inf();
|
|
1429
|
+
const all = c === N && !endless;
|
|
924
1430
|
cur = completeLines();
|
|
925
1431
|
const fresh = cur.filter((k) => !prevLines.has(k));
|
|
926
1432
|
if ([...prevLines].some((k) => !cur.includes(k))) {
|
|
927
1433
|
cancelFx();
|
|
928
1434
|
if (c < N) perfectEl.classList.remove("pkb-show");
|
|
929
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
|
+
}
|
|
930
1444
|
const hit = new Set(cur.flatMap((k) => cellLines[k]));
|
|
931
1445
|
const reach = /* @__PURE__ */ new Set();
|
|
932
1446
|
for (const l of lines) {
|
|
@@ -935,34 +1449,43 @@ function createPackingBingo(container, options = {}) {
|
|
|
935
1449
|
}
|
|
936
1450
|
tiles.forEach((t, i) => {
|
|
937
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");
|
|
938
1456
|
t.classList.toggle("pkb-line", hit.has(L.itemCell[slotOf[i]]));
|
|
939
1457
|
t.classList.toggle("pkb-reach", reach.has(i));
|
|
940
1458
|
});
|
|
941
1459
|
freeTile?.classList.toggle("pkb-line", hit.has(L.center));
|
|
942
|
-
board.classList.toggle("pkb-all",
|
|
943
|
-
kick.classList.toggle("pkb-gold",
|
|
944
|
-
kick.textContent =
|
|
1460
|
+
board.classList.toggle("pkb-all", all);
|
|
1461
|
+
kick.classList.toggle("pkb-gold", all);
|
|
1462
|
+
kick.textContent = all ? M.perfectTag : M.tag;
|
|
945
1463
|
$("cnt").textContent = `${c}/${N}`;
|
|
946
|
-
|
|
1464
|
+
const total = endless ? cleared : cur.length;
|
|
1465
|
+
$("bng").textContent = String(total);
|
|
947
1466
|
const bp = $("bp");
|
|
948
|
-
if (
|
|
1467
|
+
if (total > lastN) {
|
|
949
1468
|
bp.classList.remove("pkb-hot");
|
|
950
1469
|
void bp.offsetWidth;
|
|
951
1470
|
}
|
|
952
|
-
bp.classList.toggle("pkb-hot",
|
|
953
|
-
lastN =
|
|
1471
|
+
bp.classList.toggle("pkb-hot", total > 0);
|
|
1472
|
+
lastN = total;
|
|
954
1473
|
drawLines(fresh);
|
|
955
|
-
if (src !== null)
|
|
1474
|
+
if (src !== null) {
|
|
1475
|
+
const newReach = [...reach].some((i) => !prevReach.has(i));
|
|
1476
|
+
const moment = !quiet && all && !prevAll ? "perfect" : !quiet && fresh.length ? "bingo" : !quiet && newReach ? "reach" : "tap";
|
|
1477
|
+
tapFx(src, on[src], moment, fresh);
|
|
1478
|
+
}
|
|
956
1479
|
const newL = fresh.map((k) => lineKey(lines[k])).filter((key) => !gotL.has(key));
|
|
957
1480
|
newL.forEach((key) => gotL.add(key));
|
|
958
1481
|
const linePts = 100 * newL.length * newL.length;
|
|
959
1482
|
score += linePts;
|
|
960
1483
|
if (quiet) {
|
|
961
1484
|
if (linePts) countScore();
|
|
962
|
-
} else if (
|
|
1485
|
+
} else if (all && !prevAll) perfect(events);
|
|
963
1486
|
else if (fresh.length) {
|
|
964
|
-
bingo(fresh, linePts);
|
|
965
|
-
const e = { newLines: fresh, totalLines:
|
|
1487
|
+
bingo(fresh, linePts, total);
|
|
1488
|
+
const e = { newLines: fresh, totalLines: total, score, mode };
|
|
966
1489
|
events.push(() => options.onBingo?.(e));
|
|
967
1490
|
} else if (src !== null && on[src]) {
|
|
968
1491
|
const nr = [...reach].filter((i) => !prevReach.has(i));
|
|
@@ -970,9 +1493,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
970
1493
|
}
|
|
971
1494
|
prevLines = new Set(cur);
|
|
972
1495
|
prevReach = reach;
|
|
973
|
-
prevAll =
|
|
1496
|
+
prevAll = all;
|
|
974
1497
|
const labels = [...reach].map((i) => items[i].label);
|
|
975
|
-
$("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);
|
|
976
1499
|
return events;
|
|
977
1500
|
}
|
|
978
1501
|
const flush = (events) => {
|
|
@@ -982,9 +1505,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
982
1505
|
}
|
|
983
1506
|
};
|
|
984
1507
|
const save = () => {
|
|
985
|
-
if (storageKey) writeStore(storageKey, JSON.stringify(checked));
|
|
1508
|
+
if (storageKey && !inf()) writeStore(storageKey, JSON.stringify(checked));
|
|
986
1509
|
};
|
|
987
1510
|
function apply(next, src, quiet) {
|
|
1511
|
+
if (phase === "idle" && src !== null && next[src]) startWatch();
|
|
1512
|
+
if (!inf() && next.every(Boolean)) finish();
|
|
988
1513
|
checked = next;
|
|
989
1514
|
ins.forEach((x, i) => x.checked = checked[i]);
|
|
990
1515
|
const events = render(src, quiet);
|
|
@@ -992,8 +1517,12 @@ function createPackingBingo(container, options = {}) {
|
|
|
992
1517
|
return events;
|
|
993
1518
|
}
|
|
994
1519
|
function baseline(next) {
|
|
1520
|
+
dropClear();
|
|
1521
|
+
cleared = 0;
|
|
995
1522
|
cancelFx();
|
|
996
1523
|
clearVisuals();
|
|
1524
|
+
if (!inf() && next.every(Boolean)) finish();
|
|
1525
|
+
else if ((phase === "play" || phase === "done") && !next.some(Boolean)) toReady();
|
|
997
1526
|
checked = next;
|
|
998
1527
|
ins.forEach((x, i) => x.checked = checked[i]);
|
|
999
1528
|
got.clear();
|
|
@@ -1003,7 +1532,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1003
1532
|
done.forEach((k) => gotL.add(lineKey(lines[k])));
|
|
1004
1533
|
prevLines = new Set(done);
|
|
1005
1534
|
prevReach = /* @__PURE__ */ new Set();
|
|
1006
|
-
prevAll = checked.every(Boolean);
|
|
1535
|
+
prevAll = !inf() && checked.every(Boolean);
|
|
1007
1536
|
gotP = prevAll;
|
|
1008
1537
|
score = shown = got.size * 10 + gotL.size * 100;
|
|
1009
1538
|
combo = 0;
|
|
@@ -1013,7 +1542,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
1013
1542
|
save();
|
|
1014
1543
|
}
|
|
1015
1544
|
function request(next, src, opts = {}) {
|
|
1016
|
-
|
|
1545
|
+
const game = inf();
|
|
1546
|
+
if (controlled && !game) {
|
|
1547
|
+
resetAsked = !!opts.reset && resetAsked;
|
|
1017
1548
|
ins.forEach((x, i) => x.checked = checked[i]);
|
|
1018
1549
|
options.onChange?.(next.slice());
|
|
1019
1550
|
return;
|
|
@@ -1021,7 +1552,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1021
1552
|
let events = [];
|
|
1022
1553
|
if (opts.reset) baseline(next);
|
|
1023
1554
|
else events = apply(next, src, !!opts.quiet);
|
|
1024
|
-
options.onChange?.(checked.slice());
|
|
1555
|
+
if (!game) options.onChange?.(checked.slice());
|
|
1025
1556
|
flush(events);
|
|
1026
1557
|
}
|
|
1027
1558
|
function place(next) {
|
|
@@ -1040,12 +1571,12 @@ function createPackingBingo(container, options = {}) {
|
|
|
1040
1571
|
focused.focus({ preventScroll: true });
|
|
1041
1572
|
}
|
|
1042
1573
|
const saveOrder = () => {
|
|
1043
|
-
if (!orderKey) return;
|
|
1574
|
+
if (!orderKey || inf()) return;
|
|
1044
1575
|
writeStore(orderKey, JSON.stringify(order));
|
|
1045
1576
|
};
|
|
1046
1577
|
let flights = [];
|
|
1047
1578
|
function arrange(next) {
|
|
1048
|
-
if (!checked.every(Boolean)) cancelFx();
|
|
1579
|
+
if (inf() || !checked.every(Boolean)) cancelFx();
|
|
1049
1580
|
const from = calm ? null : tiles.map((t) => t.getBoundingClientRect());
|
|
1050
1581
|
flights.forEach((a) => a.cancel());
|
|
1051
1582
|
flights = [];
|
|
@@ -1081,6 +1612,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1081
1612
|
svg.innerHTML = "";
|
|
1082
1613
|
}
|
|
1083
1614
|
function shuffleCard() {
|
|
1615
|
+
if (inf() && phase === "done") return;
|
|
1084
1616
|
let next = order.slice();
|
|
1085
1617
|
do {
|
|
1086
1618
|
for (let s = N - 1; s > 0; s--) {
|
|
@@ -1090,31 +1622,98 @@ function createPackingBingo(container, options = {}) {
|
|
|
1090
1622
|
} while (next.every((v, s) => v === order[s]));
|
|
1091
1623
|
arrange(next);
|
|
1092
1624
|
sfx.shuffle();
|
|
1093
|
-
|
|
1625
|
+
haptics.play("shuffle");
|
|
1094
1626
|
}
|
|
1095
1627
|
const unshuffle = () => {
|
|
1096
1628
|
if (!isIdentity(order)) arrange(identity());
|
|
1097
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
|
+
});
|
|
1098
1635
|
grid.addEventListener("change", (e) => {
|
|
1099
1636
|
const i = ins.indexOf(e.target);
|
|
1100
1637
|
if (i < 0) return;
|
|
1638
|
+
if (locked(i)) {
|
|
1639
|
+
ins[i].checked = true;
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1101
1642
|
const next = checked.slice();
|
|
1102
1643
|
next[i] = !next[i];
|
|
1103
1644
|
request(next, i);
|
|
1104
1645
|
});
|
|
1105
1646
|
container.querySelector('[data-r="shuf"]')?.addEventListener("click", shuffleCard);
|
|
1647
|
+
const restartInfinite = () => {
|
|
1648
|
+
baseline(normalize(void 0));
|
|
1649
|
+
toReady();
|
|
1650
|
+
};
|
|
1106
1651
|
$("reset").addEventListener("click", () => {
|
|
1652
|
+
if (inf()) return restartInfinite();
|
|
1107
1653
|
unshuffle();
|
|
1654
|
+
restartGame();
|
|
1108
1655
|
request(normalize(void 0), null, { reset: true });
|
|
1109
1656
|
});
|
|
1110
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);
|
|
1111
1710
|
perfectEl.addEventListener("click", (e) => {
|
|
1112
1711
|
if (e.target === perfectEl) perfectEl.classList.remove("pkb-show");
|
|
1113
1712
|
});
|
|
1114
1713
|
if (demo) {
|
|
1115
1714
|
let busy = false;
|
|
1116
1715
|
const prepare = () => {
|
|
1117
|
-
if (checked.every(Boolean)) request(normalize(void 0), null, { reset: true });
|
|
1716
|
+
if (!inf() && checked.every(Boolean)) request(normalize(void 0), null, { reset: true });
|
|
1118
1717
|
else perfectEl.classList.remove("pkb-show");
|
|
1119
1718
|
};
|
|
1120
1719
|
const run = async (steps) => {
|
|
@@ -1122,6 +1721,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1122
1721
|
busy = true;
|
|
1123
1722
|
try {
|
|
1124
1723
|
prepare();
|
|
1724
|
+
skipReady();
|
|
1125
1725
|
const g = gen;
|
|
1126
1726
|
for (const s of steps) {
|
|
1127
1727
|
await wait(s.delay);
|
|
@@ -1135,21 +1735,100 @@ function createPackingBingo(container, options = {}) {
|
|
|
1135
1735
|
}
|
|
1136
1736
|
};
|
|
1137
1737
|
$("dB").addEventListener("click", () => {
|
|
1738
|
+
if (inf() && phase === "done") return;
|
|
1138
1739
|
const open = () => lines.map((l) => l.filter((i) => !checked[i])).filter((m) => m.length).sort((a, b) => a.length - b.length)[0] ?? [];
|
|
1139
|
-
const pick = checked.every(Boolean) ? [...lines[0]] : open();
|
|
1740
|
+
const pick = !inf() && checked.every(Boolean) ? [...lines[0]] : open();
|
|
1140
1741
|
void run(pick.map((i) => ({ i, delay: 320 })));
|
|
1141
1742
|
});
|
|
1142
1743
|
$("dP").addEventListener("click", () => {
|
|
1744
|
+
if (inf()) return;
|
|
1143
1745
|
const all = items.map((_, i) => i);
|
|
1144
1746
|
const miss = checked.every(Boolean) ? all : all.filter((i) => !checked[i]);
|
|
1145
1747
|
void run(miss.map((i, k) => k < miss.length - 1 ? { i, delay: 170, quiet: true } : { i, delay: 450 }));
|
|
1146
1748
|
});
|
|
1147
1749
|
$("dR").addEventListener("click", () => {
|
|
1148
|
-
if (busy) return;
|
|
1750
|
+
if (busy || inf()) return;
|
|
1149
1751
|
unshuffle();
|
|
1752
|
+
restartGame();
|
|
1150
1753
|
request(demoStart(), null, { reset: true });
|
|
1151
1754
|
});
|
|
1152
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
|
+
});
|
|
1153
1832
|
let startOrder = null;
|
|
1154
1833
|
if (orderKey) {
|
|
1155
1834
|
try {
|
|
@@ -1171,17 +1850,47 @@ function createPackingBingo(container, options = {}) {
|
|
|
1171
1850
|
if (!initial && options.defaultChecked) initial = normalize(options.defaultChecked);
|
|
1172
1851
|
if (!initial && demo) initial = demoStart();
|
|
1173
1852
|
fit();
|
|
1174
|
-
|
|
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();
|
|
1175
1857
|
const ro = typeof ResizeObserver === "function" ? new ResizeObserver(() => {
|
|
1176
1858
|
fit();
|
|
1177
1859
|
drawLines();
|
|
1178
1860
|
}) : null;
|
|
1179
1861
|
ro?.observe(board);
|
|
1180
1862
|
ro?.observe(gw);
|
|
1863
|
+
const onVisibility = () => {
|
|
1864
|
+
if (!document.hidden) {
|
|
1865
|
+
if (restorePerfectFocus && perfectEl.classList.contains("pkb-show")) resultFocus().focus({ preventScroll: true });
|
|
1866
|
+
restorePerfectFocus = false;
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
const keepPerfect = perfectPending || perfectEl.classList.contains("pkb-show");
|
|
1870
|
+
restorePerfectFocus = keepPerfect;
|
|
1871
|
+
cancelFx();
|
|
1872
|
+
clearVisuals(keepPerfect);
|
|
1873
|
+
if (keepPerfect) {
|
|
1874
|
+
$("sScore").textContent = score.toLocaleString();
|
|
1875
|
+
$("sCombo").textContent = "\xD7" + maxCombo;
|
|
1876
|
+
$("sBingo").textContent = String(resultLines());
|
|
1877
|
+
perfectEl.classList.add("pkb-show", "pkb-settled");
|
|
1878
|
+
}
|
|
1879
|
+
combo = 0;
|
|
1880
|
+
};
|
|
1881
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
1181
1882
|
return {
|
|
1182
1883
|
setChecked(next, opts = {}) {
|
|
1183
1884
|
if (dead) return;
|
|
1184
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;
|
|
1185
1894
|
const changed = n2.flatMap((v, i) => v !== checked[i] ? [i] : []);
|
|
1186
1895
|
if (!changed.length) return;
|
|
1187
1896
|
if (!opts.animate || changed.length !== 1) {
|
|
@@ -1192,12 +1901,20 @@ function createPackingBingo(container, options = {}) {
|
|
|
1192
1901
|
},
|
|
1193
1902
|
reset() {
|
|
1194
1903
|
if (dead) return;
|
|
1904
|
+
if (inf()) return restartInfinite();
|
|
1195
1905
|
unshuffle();
|
|
1906
|
+
restartGame();
|
|
1196
1907
|
request(normalize(void 0), null, { reset: true });
|
|
1197
1908
|
},
|
|
1198
1909
|
shuffle() {
|
|
1199
1910
|
if (!dead) shuffleCard();
|
|
1200
1911
|
},
|
|
1912
|
+
setMode(next) {
|
|
1913
|
+
setMode(next);
|
|
1914
|
+
},
|
|
1915
|
+
finish() {
|
|
1916
|
+
finishGame();
|
|
1917
|
+
},
|
|
1201
1918
|
refresh() {
|
|
1202
1919
|
if (!dead) render(null, true);
|
|
1203
1920
|
},
|
|
@@ -1207,22 +1924,31 @@ function createPackingBingo(container, options = {}) {
|
|
|
1207
1924
|
lines: cur.slice(),
|
|
1208
1925
|
score,
|
|
1209
1926
|
maxCombo,
|
|
1210
|
-
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() },
|
|
1211
1932
|
size: n,
|
|
1212
1933
|
free: L.free,
|
|
1213
|
-
order: order.slice()
|
|
1934
|
+
order: order.slice(),
|
|
1935
|
+
time: Math.round(elapsed())
|
|
1214
1936
|
};
|
|
1215
1937
|
},
|
|
1216
1938
|
destroy() {
|
|
1217
1939
|
if (dead) return;
|
|
1218
1940
|
dead = true;
|
|
1941
|
+
stopTimers();
|
|
1219
1942
|
timers.forEach((id) => clearTimeout(id));
|
|
1220
1943
|
timers.clear();
|
|
1221
1944
|
clearTimeout(landing);
|
|
1945
|
+
clearTimeout(clearTimer);
|
|
1222
1946
|
flights.forEach((a) => a.cancel());
|
|
1223
1947
|
cancelAnimationFrame(raf);
|
|
1224
1948
|
cancelAnimationFrame(tw);
|
|
1225
1949
|
ro?.disconnect();
|
|
1950
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
1951
|
+
haptics.stop();
|
|
1226
1952
|
sfx.close();
|
|
1227
1953
|
container.innerHTML = "";
|
|
1228
1954
|
}
|
|
@@ -1235,5 +1961,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1235
1961
|
bingoLines,
|
|
1236
1962
|
createPackingBingo,
|
|
1237
1963
|
en,
|
|
1238
|
-
|
|
1964
|
+
formatTime,
|
|
1965
|
+
ja,
|
|
1966
|
+
speedBonus
|
|
1239
1967
|
});
|