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/index.cjs
CHANGED
|
@@ -27,7 +27,8 @@ __export(src_exports, {
|
|
|
27
27
|
bingoLines: () => bingoLines,
|
|
28
28
|
createPackingBingo: () => createPackingBingo,
|
|
29
29
|
en: () => en,
|
|
30
|
-
ja: () => ja
|
|
30
|
+
ja: () => ja,
|
|
31
|
+
speedBonus: () => speedBonus
|
|
31
32
|
});
|
|
32
33
|
module.exports = __toCommonJS(src_exports);
|
|
33
34
|
|
|
@@ -137,6 +138,27 @@ var ja = {
|
|
|
137
138
|
shuffle: "\u{1F500} \u30B7\u30E3\u30C3\u30D5\u30EB",
|
|
138
139
|
soundOn: "\u{1F50A} \u97F3\u3042\u308A",
|
|
139
140
|
soundOff: "\u{1F507} \u97F3\u306A\u3057",
|
|
141
|
+
readyTitle: "Are you ready?",
|
|
142
|
+
readyPrompt: "",
|
|
143
|
+
soundHint: "\u7AEF\u672B\u306E\u97F3\u91CF\u3092MAX\u306B",
|
|
144
|
+
vibrationHint: "\u304A\u3084\u3059\u307F\u30E2\u30FC\u30C9\u3092\u30AA\u30D5",
|
|
145
|
+
readyTap: "\u30BF\u30C3\u30D7\u3067\u30B9\u30BF\u30FC\u30C8",
|
|
146
|
+
readyGo: "START!",
|
|
147
|
+
time: "\u30BF\u30A4\u30E0",
|
|
148
|
+
statTime: "\u30AF\u30EA\u30A2\u30BF\u30A4\u30E0",
|
|
149
|
+
modeLabel: "\u30E2\u30FC\u30C9",
|
|
150
|
+
modeNormal: "\u901A\u5E38",
|
|
151
|
+
modeInfinite: "\u221E \u7121\u9650",
|
|
152
|
+
normalTitle: "NORMAL",
|
|
153
|
+
normalSub: "\u901A\u5E38\u30E2\u30FC\u30C9",
|
|
154
|
+
infiniteTitle: "INFINITE",
|
|
155
|
+
infiniteSub: "\u7121\u9650\u30D3\u30F3\u30B4",
|
|
156
|
+
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",
|
|
157
|
+
finish: "\u{1F3C1} FINISH",
|
|
158
|
+
finishTitle: "\u30D5\u30A3\u30CB\u30C3\u30B7\u30E5\uFF01",
|
|
159
|
+
finishStamp: "\u221E",
|
|
160
|
+
finishTime: "\u30D7\u30EC\u30A4\u30BF\u30A4\u30E0",
|
|
161
|
+
again: "\u3082\u3046\u4E00\u5EA6",
|
|
140
162
|
stamp: "OK",
|
|
141
163
|
free: "FREE",
|
|
142
164
|
reachBadge: "\u30EA\u30FC\u30C1",
|
|
@@ -147,15 +169,15 @@ var ja = {
|
|
|
147
169
|
bannerNth: (n) => `${n} \u30E9\u30A4\u30F3\u76EE\uFF01`,
|
|
148
170
|
bannerMulti: (n) => `\u4E00\u6C17\u306B ${n} \u30E9\u30A4\u30F3\uFF01`,
|
|
149
171
|
statusStart: () => "",
|
|
150
|
-
statusLines: (
|
|
172
|
+
statusLines: () => "",
|
|
151
173
|
statusReach: () => "",
|
|
152
|
-
statusDone: "
|
|
174
|
+
statusDone: "",
|
|
153
175
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
154
176
|
perfectStamp: "\u6E96\u5099\n\u5B8C\u4E86",
|
|
155
177
|
perfectTitle: "\u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01",
|
|
156
|
-
perfectBody:
|
|
157
|
-
\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u3001\u3044\u3063\u3066\u3089\u3063\u3057\u3083\u3044\uFF01`,
|
|
178
|
+
perfectBody: "",
|
|
158
179
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
180
|
+
speedBonus: (n) => `SPEED BONUS +${n.toLocaleString()}`,
|
|
159
181
|
statScore: "SCORE",
|
|
160
182
|
statCombo: "MAX COMBO",
|
|
161
183
|
statLines: "BINGO",
|
|
@@ -178,6 +200,27 @@ var en = {
|
|
|
178
200
|
shuffle: "\u{1F500} Shuffle",
|
|
179
201
|
soundOn: "\u{1F50A} Sound on",
|
|
180
202
|
soundOff: "\u{1F507} Muted",
|
|
203
|
+
readyTitle: "Are you ready?",
|
|
204
|
+
readyPrompt: "",
|
|
205
|
+
soundHint: "Turn your volume to MAX",
|
|
206
|
+
vibrationHint: "Turn off Do Not Disturb",
|
|
207
|
+
readyTap: "Tap to start",
|
|
208
|
+
readyGo: "GO!",
|
|
209
|
+
time: "Time",
|
|
210
|
+
statTime: "Time",
|
|
211
|
+
modeLabel: "Mode",
|
|
212
|
+
modeNormal: "Normal",
|
|
213
|
+
modeInfinite: "\u221E Infinite",
|
|
214
|
+
normalTitle: "NORMAL",
|
|
215
|
+
normalSub: "Normal mode",
|
|
216
|
+
infiniteTitle: "INFINITE",
|
|
217
|
+
infiniteSub: "Infinite bingo",
|
|
218
|
+
infiniteRule: "Completed lines clear, so you can fill them again. Bingo forever!",
|
|
219
|
+
finish: "\u{1F3C1} FINISH",
|
|
220
|
+
finishTitle: "Finish!",
|
|
221
|
+
finishStamp: "\u221E",
|
|
222
|
+
finishTime: "Time played",
|
|
223
|
+
again: "Play again",
|
|
181
224
|
stamp: "OK",
|
|
182
225
|
free: "FREE",
|
|
183
226
|
reachBadge: "REACH",
|
|
@@ -188,15 +231,15 @@ var en = {
|
|
|
188
231
|
bannerNth: (n) => `Line ${n}!`,
|
|
189
232
|
bannerMulti: (n) => `${n} lines at once!`,
|
|
190
233
|
statusStart: () => "",
|
|
191
|
-
statusLines: (
|
|
234
|
+
statusLines: () => "",
|
|
192
235
|
statusReach: () => "",
|
|
193
|
-
statusDone: "
|
|
236
|
+
statusDone: "",
|
|
194
237
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
195
238
|
perfectStamp: "ALL\nSET",
|
|
196
239
|
perfectTitle: "Perfect!",
|
|
197
|
-
perfectBody:
|
|
198
|
-
Have a great trip!`,
|
|
240
|
+
perfectBody: "",
|
|
199
241
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
242
|
+
speedBonus: (n) => `SPEED BONUS +${n.toLocaleString()}`,
|
|
200
243
|
statScore: "SCORE",
|
|
201
244
|
statCombo: "MAX COMBO",
|
|
202
245
|
statLines: "BINGO",
|
|
@@ -213,8 +256,40 @@ var hz = (semitones) => 523.25 * Math.pow(2, semitones / 12);
|
|
|
213
256
|
function createSound(isMuted) {
|
|
214
257
|
let ac = null;
|
|
215
258
|
let out = null;
|
|
259
|
+
const voices = /* @__PURE__ */ new Map();
|
|
260
|
+
function track(source, gain, start, nodes) {
|
|
261
|
+
voices.set(source, { gain, start });
|
|
262
|
+
source.onended = () => {
|
|
263
|
+
voices.delete(source);
|
|
264
|
+
source.disconnect();
|
|
265
|
+
nodes.forEach((n) => n.disconnect());
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function stop() {
|
|
269
|
+
const now = ac?.currentTime ?? 0;
|
|
270
|
+
voices.forEach(({ gain, start }, source) => {
|
|
271
|
+
try {
|
|
272
|
+
if (start > now) {
|
|
273
|
+
gain.gain.cancelScheduledValues(now);
|
|
274
|
+
gain.gain.setValueAtTime(0, now);
|
|
275
|
+
source.stop(now);
|
|
276
|
+
} else {
|
|
277
|
+
if (typeof gain.gain.cancelAndHoldAtTime === "function") gain.gain.cancelAndHoldAtTime(now);
|
|
278
|
+
else {
|
|
279
|
+
const value = gain.gain.value;
|
|
280
|
+
gain.gain.cancelScheduledValues(now);
|
|
281
|
+
gain.gain.setValueAtTime(value, now);
|
|
282
|
+
}
|
|
283
|
+
gain.gain.linearRampToValueAtTime(0, now + 0.01);
|
|
284
|
+
source.stop(now + 0.01);
|
|
285
|
+
}
|
|
286
|
+
} catch {
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
voices.clear();
|
|
290
|
+
}
|
|
216
291
|
function audio() {
|
|
217
|
-
if (isMuted() || typeof window === "undefined") return null;
|
|
292
|
+
if (isMuted() || typeof window === "undefined" || document.hidden) return null;
|
|
218
293
|
const ua = navigator.userActivation;
|
|
219
294
|
if (!ac && ua && !ua.hasBeenActive) return null;
|
|
220
295
|
if (!ac) {
|
|
@@ -242,6 +317,7 @@ function createSound(isMuted) {
|
|
|
242
317
|
g.gain.exponentialRampToValueAtTime(vol, t + 0.01);
|
|
243
318
|
g.gain.exponentialRampToValueAtTime(1e-4, t + dur);
|
|
244
319
|
o.connect(g).connect(out);
|
|
320
|
+
track(o, g, t, [g]);
|
|
245
321
|
o.start(t);
|
|
246
322
|
o.stop(t + dur + 0.05);
|
|
247
323
|
}
|
|
@@ -262,15 +338,19 @@ function createSound(isMuted) {
|
|
|
262
338
|
f.Q.value = 0.7;
|
|
263
339
|
g.gain.value = vol;
|
|
264
340
|
s.connect(f).connect(g).connect(out);
|
|
341
|
+
track(s, g, t, [f, g]);
|
|
265
342
|
s.start(t);
|
|
266
343
|
}
|
|
267
344
|
const step = (k) => hz(SCALE[Math.min(Math.max(k, 0), SCALE.length - 1)]);
|
|
268
345
|
return {
|
|
269
346
|
pop(k) {
|
|
270
347
|
const f = step(k);
|
|
348
|
+
const energy = Math.min(3, Math.floor((k + 1) / 3));
|
|
271
349
|
tone(f, 0, 0.16, "triangle", 0.22);
|
|
272
350
|
tone(f * 2, 0, 0.08, "sine", 0.06);
|
|
273
|
-
tone(
|
|
351
|
+
tone(170 + energy * 15, 0, 0.11, "sine", 0.28, 48);
|
|
352
|
+
noise(0, 0.035, 0.1 + energy * 0.025, 1800);
|
|
353
|
+
if (energy) tone(f * 1.5, 0.035, 0.11, "triangle", 0.04 + energy * 0.015);
|
|
274
354
|
},
|
|
275
355
|
off: () => tone(392, 0, 0.1, "sine", 0.1, 262),
|
|
276
356
|
reach() {
|
|
@@ -300,7 +380,39 @@ function createSound(isMuted) {
|
|
|
300
380
|
tone(hz(7), 0.34, 0.12, "triangle", 0.14);
|
|
301
381
|
tone(hz(12), 0.42, 0.2, "triangle", 0.14);
|
|
302
382
|
},
|
|
383
|
+
count(go) {
|
|
384
|
+
if (go) {
|
|
385
|
+
tone(hz(12), 0, 0.5, "square", 0.07);
|
|
386
|
+
tone(hz(24), 0, 0.5, "triangle", 0.14);
|
|
387
|
+
noise(0, 0.2, 0.12, 3e3);
|
|
388
|
+
} else {
|
|
389
|
+
tone(hz(5), 0, 0.14, "square", 0.07);
|
|
390
|
+
tone(hz(17), 0, 0.14, "triangle", 0.12);
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
mode(infinite) {
|
|
394
|
+
if (infinite) {
|
|
395
|
+
tone(196, 0, 0.42, "sine", 0.12, 784);
|
|
396
|
+
[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));
|
|
397
|
+
[12, 16, 19, 24].forEach((s) => tone(hz(s), 0.38, 1.1, "sine", 0.07));
|
|
398
|
+
tone(hz(31), 0.38, 0.9, "triangle", 0.03);
|
|
399
|
+
noise(0, 0.5, 0.07, 6500);
|
|
400
|
+
noise(0.36, 0.7, 0.05, 9e3);
|
|
401
|
+
} else {
|
|
402
|
+
tone(hz(7), 0, 0.1, "square", 0.05);
|
|
403
|
+
tone(hz(0), 0.1, 0.28, "triangle", 0.14);
|
|
404
|
+
tone(160, 0.1, 0.2, "sine", 0.26, 55);
|
|
405
|
+
noise(0.1, 0.12, 0.14, 1400);
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
clear(squares) {
|
|
409
|
+
const n = Math.max(1, Math.min(8, squares));
|
|
410
|
+
for (let i = 0; i < n; i++) tone(step(SCALE.length - 1 - i * 2), i * 0.035, 0.18, "sine", 0.07);
|
|
411
|
+
noise(0, 0.3, 0.08, 7e3);
|
|
412
|
+
},
|
|
413
|
+
stop,
|
|
303
414
|
close() {
|
|
415
|
+
stop();
|
|
304
416
|
if (ac) void ac.close().catch(() => {
|
|
305
417
|
});
|
|
306
418
|
ac = null;
|
|
@@ -309,6 +421,64 @@ function createSound(isMuted) {
|
|
|
309
421
|
};
|
|
310
422
|
}
|
|
311
423
|
|
|
424
|
+
// src/haptics.ts
|
|
425
|
+
var owner = null;
|
|
426
|
+
function createHaptics(enabled) {
|
|
427
|
+
const token = {};
|
|
428
|
+
let generation = 0;
|
|
429
|
+
const stop = () => {
|
|
430
|
+
generation++;
|
|
431
|
+
if (owner !== token) return;
|
|
432
|
+
owner = null;
|
|
433
|
+
try {
|
|
434
|
+
navigator.vibrate?.(0);
|
|
435
|
+
} catch {
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
const send = (pattern) => {
|
|
439
|
+
if (!enabled || document.hidden || typeof navigator.vibrate !== "function") return false;
|
|
440
|
+
if (navigator.userActivation && !navigator.userActivation.hasBeenActive) return false;
|
|
441
|
+
try {
|
|
442
|
+
if (navigator.vibrate(pattern)) {
|
|
443
|
+
owner = token;
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
446
|
+
} catch {
|
|
447
|
+
}
|
|
448
|
+
return false;
|
|
449
|
+
};
|
|
450
|
+
return {
|
|
451
|
+
play(moment, combo = 1, lines = 1, impactAt = 360) {
|
|
452
|
+
const current = ++generation;
|
|
453
|
+
const tier = Math.min(3, Math.floor(combo / 3));
|
|
454
|
+
const lead = Math.max(80, impactAt - 40);
|
|
455
|
+
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 : (
|
|
456
|
+
// Switching modes: a quick rising flutter into a hold, or one firm stamp.
|
|
457
|
+
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
|
|
458
|
+
);
|
|
459
|
+
if (!send(pattern)) return;
|
|
460
|
+
return (beat) => {
|
|
461
|
+
if (generation === current && owner === token) send([...beat]);
|
|
462
|
+
};
|
|
463
|
+
},
|
|
464
|
+
stop
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// src/celebration.ts
|
|
469
|
+
var PERFECT_REVEAL_MS = 520;
|
|
470
|
+
var PERFECT_STAMP_DELAY_MS = 720;
|
|
471
|
+
var PERFECT_STAMP_BEAT = [260, 45, 160];
|
|
472
|
+
var PERFECT_FIREWORKS = [
|
|
473
|
+
{ at: 1820, beat: [200] },
|
|
474
|
+
{ at: 2170, beat: [100, 35, 160] },
|
|
475
|
+
{ at: 2520, beat: [260] },
|
|
476
|
+
{ at: 3e3, beat: [110, 40, 110] },
|
|
477
|
+
{ at: 3430, beat: [320] },
|
|
478
|
+
{ at: 3950, beat: [140, 40, 200] },
|
|
479
|
+
{ at: 4540, beat: [460] }
|
|
480
|
+
];
|
|
481
|
+
|
|
312
482
|
// src/storage-key.ts
|
|
313
483
|
function resolveStorageKey(options) {
|
|
314
484
|
if (options.controlled) return null;
|
|
@@ -336,13 +506,17 @@ var CSS = `
|
|
|
336
506
|
.pkb-board.pkb-punch{animation:pkb-punch .4s cubic-bezier(.3,1.6,.5,1)}
|
|
337
507
|
@keyframes pkb-punch{30%{transform:scale(1.018)}}
|
|
338
508
|
.pkb-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px 16px;margin-bottom:18px}
|
|
339
|
-
.pkb-tags{display:flex;gap:8px;align-items:center}
|
|
509
|
+
.pkb-tags{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
|
|
340
510
|
.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}
|
|
341
511
|
.pkb-kicker:empty{display:none}
|
|
342
512
|
.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}
|
|
343
513
|
@keyframes pkb-rb{to{background-position:-200% 0}}
|
|
344
514
|
.pkb button{font-family:var(--pk-body)}
|
|
345
515
|
.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}
|
|
516
|
+
.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}
|
|
517
|
+
.pkb-watch::before{content:"\u23F1 ";font-family:var(--pk-body)}
|
|
518
|
+
.pkb-watch.pkb-run{color:var(--pk-sun)}
|
|
519
|
+
.pkb-watch.pkb-done{background:var(--pk-red);color:#fff}
|
|
346
520
|
.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}
|
|
347
521
|
.pkb-sub{font-weight:800;font-size:13px;margin-top:4px;word-break:auto-phrase}
|
|
348
522
|
.pkb-side{display:flex;flex-direction:column;gap:8px;flex:none}
|
|
@@ -360,12 +534,12 @@ var CSS = `
|
|
|
360
534
|
.pkb-combo b{font-family:var(--pk-display);font-weight:400;font-size:13px;color:var(--pk-red);white-space:nowrap}
|
|
361
535
|
.pkb-bar{flex:1;height:9px;border:2px solid var(--pk-ink);border-radius:999px;background:var(--pk-paper);overflow:hidden}
|
|
362
536
|
.pkb-bar i{display:block;height:100%;background:var(--pk-red);transform-origin:left}
|
|
363
|
-
.pkb-combo.pkb-on .pkb-bar i{animation:pkb-drain 2.5s linear forwards}
|
|
537
|
+
.pkb-combo.pkb-on .pkb-bar i.pkb-draining{animation:pkb-drain 2.5s linear forwards}
|
|
364
538
|
@keyframes pkb-drain{from{transform:scaleX(1)}to{transform:scaleX(0)}}
|
|
365
539
|
.pkb-gw{position:relative}
|
|
366
540
|
.pkb-grid{display:grid;grid-template-columns:repeat(var(--pk-n),minmax(0,1fr));gap:calc(12px*var(--pk-d))}
|
|
367
541
|
.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}
|
|
368
|
-
.pkb-tile:active:not(.pkb-free){transform:translate(3px,3px);box-shadow:1px 1px 0 var(--pk-ink)}
|
|
542
|
+
.pkb-tile:active:not(.pkb-free):not(.pkb-lock){transform:translate(3px,3px);box-shadow:1px 1px 0 var(--pk-ink)}
|
|
369
543
|
.pkb-tile input{position:absolute;opacity:0;pointer-events:none;width:1px;height:1px}
|
|
370
544
|
.pkb-tile:has(input:focus-visible){outline:3px solid var(--pk-ink);outline-offset:3px}
|
|
371
545
|
.pkb-emo{font-size:max(14px,calc(32px*var(--pk-s)));line-height:1;transition:opacity .2s}
|
|
@@ -373,6 +547,7 @@ var CSS = `
|
|
|
373
547
|
.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}
|
|
374
548
|
.pkb-tile.pkb-on .pkb-stamp{opacity:.9;transform:translate(-50%,-50%) rotate(-16deg) scale(1)}
|
|
375
549
|
.pkb-tile.pkb-on .pkb-emo,.pkb-tile.pkb-on .pkb-lbl{opacity:.35}
|
|
550
|
+
.pkb-tile.pkb-lock{cursor:default}
|
|
376
551
|
.pkb-tile.pkb-free{cursor:default;container-type:inline-size}
|
|
377
552
|
.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)}
|
|
378
553
|
.pkb-tile.pkb-line{background-color:var(--pk-mint)}
|
|
@@ -382,12 +557,14 @@ var CSS = `
|
|
|
382
557
|
@keyframes pkb-aura{50%{box-shadow:4px 4px 0 var(--pk-ink),0 0 0 7px rgba(232,71,59,.35)}}
|
|
383
558
|
.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}
|
|
384
559
|
@keyframes pkb-bob{50%{transform:rotate(6deg) translateY(-4px)}}
|
|
385
|
-
.pkb-tile.pkb-squash{animation:pkb-squash .
|
|
386
|
-
@keyframes pkb-squash{0%{transform:scale(.
|
|
560
|
+
.pkb-tile.pkb-squash{z-index:1;animation:pkb-squash .38s cubic-bezier(.16,1,.3,1)}
|
|
561
|
+
@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}}
|
|
562
|
+
.pkb-tile.pkb-on .pkb-stamp.pkb-hit{animation:pkb-stamp-hit .32s cubic-bezier(.16,1,.3,1) both}
|
|
563
|
+
@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)}}
|
|
387
564
|
.pkb-tile.pkb-hop{animation:pkb-hop .55s cubic-bezier(.3,1.6,.5,1) var(--pkb-d,0ms) both}
|
|
388
565
|
@keyframes pkb-hop{35%{transform:translateY(-14px) scale(1.07)}}
|
|
389
566
|
.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}
|
|
390
|
-
@keyframes pkb-ring{from{transform:translate(-50%,-50%) scale(.3);opacity:.9}to{transform:translate(-50%,-50%) scale(1.9);opacity:0}}
|
|
567
|
+
@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}}
|
|
391
568
|
.pkb-lines{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible;z-index:2;transition:opacity .4s}
|
|
392
569
|
.pkb-board.pkb-all .pkb-lines{opacity:0}
|
|
393
570
|
.pkb-lines line{fill:none;stroke-linecap:round;stroke-dasharray:1;stroke-dashoffset:0}
|
|
@@ -398,10 +575,11 @@ var CSS = `
|
|
|
398
575
|
.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}
|
|
399
576
|
.pkb-foot>span{flex:1 1 14em}
|
|
400
577
|
.pkb-foot>span:empty{flex-basis:0}
|
|
401
|
-
.pkb-acts{display:flex;gap:8px;flex:
|
|
578
|
+
.pkb-acts{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;flex:0 1 auto;max-width:100%;margin-left:auto}
|
|
402
579
|
.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}
|
|
403
580
|
.pkb-shuf{background:var(--pk-paper);box-shadow:2px 2px 0 var(--pk-ink)}
|
|
404
|
-
.pkb-shuf:active{transform:translate(2px,2px);box-shadow:none}
|
|
581
|
+
.pkb-shuf:active:not(:disabled){transform:translate(2px,2px);box-shadow:none}
|
|
582
|
+
.pkb-shuf:disabled{cursor:default;opacity:.4}
|
|
405
583
|
.pkb-flash{position:absolute;inset:0;z-index:7;background:#fff;opacity:0;pointer-events:none}
|
|
406
584
|
.pkb-flash.pkb-go{animation:pkb-flash .4s ease-out}
|
|
407
585
|
@keyframes pkb-flash{0%{opacity:.8}100%{opacity:0}}
|
|
@@ -418,15 +596,43 @@ var CSS = `
|
|
|
418
596
|
.pkb-float.pkb-big{font-size:clamp(14px,5.6cqi,34px);color:#FFE066}
|
|
419
597
|
.pkb-float.pkb-rch{font-size:20px;color:var(--pk-red);-webkit-text-stroke:4px var(--pk-paper)}
|
|
420
598
|
@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)}}
|
|
599
|
+
.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}
|
|
600
|
+
.pkb-ready[hidden]{display:none}
|
|
601
|
+
.pkb-ready:focus-visible{outline:3px solid var(--pk-ink);outline-offset:-6px}
|
|
602
|
+
.pkb-ready.pkb-gone{pointer-events:none;background-color:transparent}
|
|
603
|
+
.pkb-board.pkb-waiting .pkb-snd{position:relative;z-index:13}
|
|
604
|
+
.pkb-ready-in,.pkb-count{grid-area:1/1}
|
|
605
|
+
.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)}
|
|
606
|
+
.pkb-ready.pkb-counting .pkb-ready-in{visibility:hidden}
|
|
607
|
+
.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)}
|
|
608
|
+
.pkb-ready-q{margin-top:6px;font-weight:800;font-size:18px;line-height:1.4}
|
|
609
|
+
.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}
|
|
610
|
+
.pkb-ready-l>span::before{content:"\u30FB"}
|
|
611
|
+
.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}
|
|
612
|
+
@keyframes pkb-beat{50%{transform:scale(1.06)}}
|
|
613
|
+
.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}
|
|
614
|
+
.pkb-count.pkb-go{font-size:clamp(48px,17cqi,120px);color:var(--pk-red);white-space:nowrap}
|
|
615
|
+
.pkb-count.pkb-tick{animation:pkb-cnt 1s cubic-bezier(.2,1.4,.4,1) forwards}
|
|
616
|
+
.pkb-count.pkb-go.pkb-tick{animation:pkb-cnt-go .75s cubic-bezier(.2,1.4,.4,1) forwards}
|
|
617
|
+
@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)}}
|
|
618
|
+
@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)}}
|
|
619
|
+
.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)}
|
|
620
|
+
.pkb-ptime[hidden]{display:none}
|
|
621
|
+
.pkb-ptime small{font-size:11px;font-weight:800;letter-spacing:.08em;color:#fff}
|
|
622
|
+
.pkb-ptime b{font-family:var(--pk-display);font-weight:400;font-size:22px;line-height:1.2;font-variant-numeric:tabular-nums}
|
|
421
623
|
.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}
|
|
422
|
-
.
|
|
624
|
+
/* Visible at once when opening (focus can't land on a hidden dialog); hidden only after the fade when closing. */
|
|
625
|
+
.pkb-perfect.pkb-show{opacity:1;visibility:visible;transition:opacity .3s,visibility 0s}
|
|
423
626
|
.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)}
|
|
424
627
|
.pkb-perfect.pkb-show .pkb-card{transform:none}
|
|
425
628
|
.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)}
|
|
426
629
|
.pkb-perfect.pkb-show .pkb-medal{animation:pkb-slam .38s cubic-bezier(.5,0,.75,0) .35s forwards}
|
|
630
|
+
.pkb-perfect.pkb-settled,.pkb-perfect.pkb-settled .pkb-card{transition:none}
|
|
631
|
+
.pkb-perfect.pkb-settled .pkb-medal{animation:none;opacity:1;transform:rotate(-14deg) scale(1)}
|
|
427
632
|
@keyframes pkb-slam{to{opacity:1;transform:rotate(-14deg) scale(1)}}
|
|
428
633
|
.pkb-ptitle{font-family:var(--pk-display);font-weight:400;font-size:30px;line-height:1.3;color:inherit}
|
|
429
634
|
.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}
|
|
635
|
+
.pkb-pbody:empty{margin:0 0 14px}
|
|
430
636
|
.pkb-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:16px}
|
|
431
637
|
.pkb-stats>div{border:2px solid var(--pk-ink);border-radius:12px;padding:6px 4px 5px;background:#fff}
|
|
432
638
|
.pkb-stats small{display:block;font-size:9.5px;font-weight:800;letter-spacing:.1em}
|
|
@@ -436,10 +642,72 @@ var CSS = `
|
|
|
436
642
|
.pkb-fx{position:absolute;inset:0;width:100%;height:100%;z-index:10;pointer-events:none}
|
|
437
643
|
.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}
|
|
438
644
|
.pkb-demo button{appearance:none;border:1px solid #ccc;background:#f3f3f3;color:#222;border-radius:999px;padding:5px 12px;font-size:12px;cursor:pointer}
|
|
439
|
-
|
|
440
|
-
|
|
645
|
+
.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}
|
|
646
|
+
.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}
|
|
647
|
+
.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}
|
|
648
|
+
.pkb-mode button[aria-checked="true"]{opacity:1;color:var(--pk-ink);cursor:default}
|
|
649
|
+
.pkb-mode button:focus-visible{outline:2px solid var(--pk-paper);outline-offset:-4px}
|
|
650
|
+
.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)}
|
|
651
|
+
.pkb.pkb-inf .pkb-mode{box-shadow:0 0 14px 1px rgba(155,93,229,.6)}
|
|
652
|
+
.pkb.pkb-inf .pkb-mode::after{opacity:1;animation:pkb-glint 3.2s ease-in-out infinite}
|
|
653
|
+
.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}
|
|
654
|
+
.pkb.pkb-inf .pkb-mode button[data-m="infinite"]{color:#fff;text-shadow:0 1px 1px rgba(42,31,20,.55)}
|
|
655
|
+
.pkb-board.pkb-waiting .pkb-mode,.pkb-board.pkb-switching .pkb-mode{z-index:15}
|
|
656
|
+
.pkb-board.pkb-switching .pkb-fx{z-index:16}
|
|
657
|
+
.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)}
|
|
658
|
+
.pkb.pkb-inf .pkb-demo [data-r="dP"],.pkb.pkb-inf .pkb-demo [data-r="dR"]{display:none}
|
|
659
|
+
.pkb-ready-b,.pkb-ready-r{display:none}
|
|
660
|
+
.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)}
|
|
661
|
+
.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}
|
|
662
|
+
.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}
|
|
663
|
+
.pkb-tile.pkb-clr{animation:pkb-clr .62s var(--pkb-cd,0ms) both}
|
|
664
|
+
@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)}}
|
|
665
|
+
.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}
|
|
666
|
+
.pkb-mfx.pkb-show{visibility:visible;animation:pkb-mfx 1.7s ease-out forwards}
|
|
667
|
+
@keyframes pkb-mfx{0%{opacity:0}9%{opacity:1}72%{opacity:1}100%{opacity:0}}
|
|
668
|
+
.pkb-mfx-inf{background:radial-gradient(120% 95% at 50% 42%,#33205c 0%,#170f2e 52%,#08060f 100%)}
|
|
669
|
+
.pkb-mfx-n{background:var(--pk-sun) radial-gradient(rgba(42,31,20,.16) 1.3px,transparent 1.6px) 0 0/16px 16px}
|
|
670
|
+
.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}
|
|
671
|
+
.pkb-mfx.pkb-show::after{animation:pkb-beam 1s cubic-bezier(.4,0,.2,1) .22s both}
|
|
672
|
+
@keyframes pkb-beam{0%{opacity:1;transform:skewX(-18deg) translateX(0)}100%{opacity:1;transform:skewX(-18deg) translateX(400%)}}
|
|
673
|
+
.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}
|
|
674
|
+
.pkb-mfx.pkb-show .pkb-mfx-glow{animation:pkb-spin 3s linear infinite,pkb-glow 1.7s ease-out both}
|
|
675
|
+
@keyframes pkb-glow{0%{opacity:0;scale:.35}30%{opacity:.8;scale:1}100%{opacity:.5;scale:1.12}}
|
|
676
|
+
.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}
|
|
677
|
+
.pkb-mfx.pkb-show i{animation:pkb-twk .85s ease-in-out var(--t) both}
|
|
678
|
+
@keyframes pkb-twk{0%{opacity:0;scale:0;rotate:0deg}45%{opacity:1;scale:1;rotate:70deg}100%{opacity:0;scale:.2;rotate:160deg}}
|
|
679
|
+
.pkb-mfx-in{position:relative;display:flex;flex-direction:column;align-items:center;padding:0 16px}
|
|
680
|
+
.pkb-mfx-sym{display:block;font-family:var(--pk-display);font-weight:400;line-height:1}
|
|
681
|
+
.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))}
|
|
682
|
+
.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}
|
|
683
|
+
@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)}}
|
|
684
|
+
.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)}
|
|
685
|
+
.pkb-mfx.pkb-show.pkb-mfx-n .pkb-mfx-sym{animation:pkb-mslam .42s cubic-bezier(.5,0,.75,0) .08s both}
|
|
686
|
+
@keyframes pkb-mslam{from{opacity:0;transform:rotate(-14deg) scale(2.6)}to{opacity:1;transform:rotate(-14deg) scale(1)}}
|
|
687
|
+
.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}
|
|
688
|
+
.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)}
|
|
689
|
+
.pkb-mfx-n .pkb-mfx-t{margin-top:16px;color:var(--pk-ink)}
|
|
690
|
+
.pkb-mfx.pkb-show .pkb-mfx-t{animation:pkb-track 1.7s cubic-bezier(.16,1,.3,1) .12s both}
|
|
691
|
+
@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}}
|
|
692
|
+
.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}
|
|
693
|
+
.pkb-mfx-inf .pkb-mfx-s{color:#fff;background:rgba(255,255,255,.12);border:1.5px solid rgba(255,255,255,.4)}
|
|
694
|
+
.pkb-mfx-n .pkb-mfx-s{background:var(--pk-ink);color:var(--pk-sun)}
|
|
695
|
+
.pkb-mfx.pkb-show .pkb-mfx-s{animation:pkb-up .6s cubic-bezier(.2,1.3,.4,1) .4s both}
|
|
696
|
+
@keyframes pkb-up{from{opacity:0;transform:translateY(12px)}}
|
|
697
|
+
.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}
|
|
698
|
+
.pkb.pkb-inf .pkb-finbtn{display:inline-block}
|
|
699
|
+
.pkb-finbtn:active:not(:disabled){transform:translate(2px,2px);box-shadow:none}
|
|
700
|
+
.pkb-finbtn:disabled{cursor:default;opacity:.4;animation:none;box-shadow:2px 2px 0 var(--pk-ink)}
|
|
701
|
+
.pkb-board.pkb-over .pkb-grid{opacity:.55;filter:saturate(.5);transition:opacity .4s,filter .4s}
|
|
702
|
+
.pkb-pbtns{display:flex;flex-wrap:wrap;justify-content:center;gap:10px}
|
|
703
|
+
.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)}
|
|
704
|
+
.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)}
|
|
705
|
+
.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}
|
|
706
|
+
.pkb-perfect.pkb-fin .pkb-close:not(.pkb-again){background:var(--pk-paper)}
|
|
707
|
+
@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}}
|
|
708
|
+
@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}}
|
|
441
709
|
@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}}
|
|
442
|
-
@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}}
|
|
710
|
+
@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}}
|
|
443
711
|
@keyframes pkb-fade{0%,100%{opacity:0}15%,80%{opacity:1}}
|
|
444
712
|
`;
|
|
445
713
|
|
|
@@ -466,7 +734,22 @@ function writeStore(key, value) {
|
|
|
466
734
|
}
|
|
467
735
|
var glintDelay = (c, n) => `${+((Math.floor(c / n) + c % n) * 0.36 / Math.max(1, n - 1)).toFixed(3)}s`;
|
|
468
736
|
var lineKey = (l) => l.slice().sort((a, b) => a - b).join(",");
|
|
737
|
+
var COUNT_MS = 1e3;
|
|
738
|
+
var GO_MS = 750;
|
|
739
|
+
function formatTime(ms, digits = 1) {
|
|
740
|
+
const unit = 10 ** digits;
|
|
741
|
+
const t = Math.floor(Math.max(0, ms) / (1e3 / unit));
|
|
742
|
+
const frac = digits ? "." + String(t % unit).padStart(digits, "0") : "";
|
|
743
|
+
const sec = Math.floor(t / unit), s = sec % 60, m = Math.floor(sec / 60) % 60, h = Math.floor(sec / 3600);
|
|
744
|
+
const pad = (v) => String(v).padStart(2, "0");
|
|
745
|
+
return (h ? `${h}:${pad(m)}` : String(m)) + `:${pad(s)}${frac}`;
|
|
746
|
+
}
|
|
747
|
+
function speedBonus(items, ms) {
|
|
748
|
+
return Math.round(items * 1e6 / Math.max(ms, 1));
|
|
749
|
+
}
|
|
469
750
|
var FLY_MS = 560;
|
|
751
|
+
var CLEAR_MS = 1e3;
|
|
752
|
+
var SPARK = ["#FFFFFF", "#FFE066", "#FF9F1C", "#E8473B", "#9B5DE5", "#2F6FED", "#1FA971"];
|
|
470
753
|
function ensureStyles() {
|
|
471
754
|
if (document.getElementById("pkb-styles")) return;
|
|
472
755
|
const s = document.createElement("style");
|
|
@@ -474,7 +757,8 @@ function ensureStyles() {
|
|
|
474
757
|
s.textContent = CSS;
|
|
475
758
|
document.head.appendChild(s);
|
|
476
759
|
}
|
|
477
|
-
|
|
760
|
+
var opt = (v, fallback) => v ?? fallback ?? "";
|
|
761
|
+
function template(L, M, { demo, shuffle, vibrate, ready, modes }) {
|
|
478
762
|
const n = L.size;
|
|
479
763
|
const input = vibrate ? `<input type="checkbox" switch role="checkbox">` : `<input type="checkbox">`;
|
|
480
764
|
const tiles = Array.from({ length: n * n }, (_, c) => {
|
|
@@ -486,9 +770,15 @@ function template(L, M, { demo, shuffle, vibrate }) {
|
|
|
486
770
|
}).join("");
|
|
487
771
|
const scale = `--pk-n:${n};--pk-s:${+Math.min(1.35, 4 / n).toFixed(3)};--pk-d:${+Math.min(1, 4 / n).toFixed(3)}`;
|
|
488
772
|
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>` : "";
|
|
773
|
+
const hints = [opt(M.soundHint, ja.soundHint), vibrate ? opt(M.vibrationHint, ja.vibrationHint) : ""].filter((h) => h.trim());
|
|
774
|
+
const prompt = opt(M.readyPrompt, ja.readyPrompt);
|
|
775
|
+
const rule = opt(M.infiniteRule, ja.infiniteRule);
|
|
776
|
+
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>` : "";
|
|
777
|
+
const timeLabel = M.time?.trim() || ja.time;
|
|
778
|
+
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>` : "";
|
|
489
779
|
return `<div class="pkb" style="${scale}"><div class="pkb-board">
|
|
490
780
|
<div class="pkb-head"><div>
|
|
491
|
-
<div class="pkb-tags"><span class="pkb-kicker" data-r="kick">${esc(M.tag)}</span
|
|
781
|
+
<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>
|
|
492
782
|
<h2 class="pkb-title">${esc(M.title)}</h2>${M.subtitle ? `<p class="pkb-sub">${esc(M.subtitle)}</p>` : ""}</div>
|
|
493
783
|
<div class="pkb-side"><div class="pkb-score">
|
|
494
784
|
<div class="pkb-pill"><small>${esc(M.packed)}</small><b data-r="cnt"></b></div>
|
|
@@ -497,13 +787,14 @@ function template(L, M, { demo, shuffle, vibrate }) {
|
|
|
497
787
|
<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>
|
|
498
788
|
<div class="pkb-gw"><div class="pkb-grid">${tiles}</div><svg class="pkb-lines" aria-hidden="true"></svg>
|
|
499
789
|
<div class="pkb-banner" data-r="banner" aria-hidden="true"><b data-r="bT"></b><span data-r="bS"></span></div></div>
|
|
500
|
-
<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>
|
|
790
|
+
<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>
|
|
501
791
|
<div class="pkb-flash" data-r="flash"></div>
|
|
502
|
-
<div class="pkb-perfect" data-r="perfect"><div class="pkb-card" role="dialog" aria-label="${esc(M.perfectTitle)}">
|
|
503
|
-
<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>
|
|
792
|
+
<div class="pkb-perfect" data-r="perfect"><div class="pkb-card" data-r="pcard" role="dialog" aria-label="${esc(M.perfectTitle)}">
|
|
793
|
+
<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>
|
|
794
|
+
<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>
|
|
504
795
|
<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>
|
|
505
|
-
<button type="button" class="pkb-close" data-r="close">${esc(M.close)}</button></div></div>
|
|
506
|
-
<canvas class="pkb-fx"></canvas></div>${demoBar}</div>`;
|
|
796
|
+
<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>
|
|
797
|
+
<canvas class="pkb-fx"></canvas>${readyScreen}<div class="pkb-mfx" data-r="mfx" aria-hidden="true"></div></div>${demoBar}</div>`;
|
|
507
798
|
}
|
|
508
799
|
var perfectText = (M, total) => typeof M.perfectBody === "function" ? M.perfectBody(total) : M.perfectBody;
|
|
509
800
|
function createPackingBingo(container, options = {}) {
|
|
@@ -535,7 +826,15 @@ function createPackingBingo(container, options = {}) {
|
|
|
535
826
|
const orderKey = storageKey && ORDER_PREFIX + storageKey;
|
|
536
827
|
const demo = !!options.demo;
|
|
537
828
|
const vibrate = options.vibrate !== false;
|
|
829
|
+
const uncheck = options.uncheck === true;
|
|
538
830
|
const calm = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
831
|
+
const haptics = createHaptics(vibrate);
|
|
832
|
+
let mode = options.mode === "infinite" ? "infinite" : "normal";
|
|
833
|
+
const inf = () => mode === "infinite";
|
|
834
|
+
let packing = null;
|
|
835
|
+
let cleared = 0;
|
|
836
|
+
const pending = /* @__PURE__ */ new Set();
|
|
837
|
+
let clearTimer = 0, clearAt = 0;
|
|
539
838
|
let dead = false;
|
|
540
839
|
const timers = /* @__PURE__ */ new Set();
|
|
541
840
|
const later = (fn, ms) => {
|
|
@@ -548,12 +847,19 @@ function createPackingBingo(container, options = {}) {
|
|
|
548
847
|
const wait = (ms) => new Promise((r) => window.setTimeout(r, ms));
|
|
549
848
|
let gen = 0;
|
|
550
849
|
ensureStyles();
|
|
551
|
-
container.innerHTML = template(L, M, {
|
|
850
|
+
container.innerHTML = template(L, M, {
|
|
851
|
+
demo,
|
|
852
|
+
shuffle: options.shuffle !== false,
|
|
853
|
+
vibrate,
|
|
854
|
+
ready: options.ready !== false,
|
|
855
|
+
modes: options.modeSwitch !== false
|
|
856
|
+
});
|
|
552
857
|
const $ = (r) => {
|
|
553
858
|
const el = container.querySelector(`[data-r="${r}"]`);
|
|
554
859
|
if (!el) throw new Error(`[packing-bingo] internal: missing element "${r}"`);
|
|
555
860
|
return el;
|
|
556
861
|
};
|
|
862
|
+
const root = container.querySelector(".pkb");
|
|
557
863
|
const board = container.querySelector(".pkb-board");
|
|
558
864
|
const gw = container.querySelector(".pkb-gw");
|
|
559
865
|
const grid = container.querySelector(".pkb-grid");
|
|
@@ -564,12 +870,15 @@ function createPackingBingo(container, options = {}) {
|
|
|
564
870
|
const tiles = L.itemCell.map((c) => cellTiles[c]);
|
|
565
871
|
const freeTile = L.center >= 0 ? cellTiles[L.center] : null;
|
|
566
872
|
const ins = tiles.map((t) => t.querySelector("input"));
|
|
567
|
-
const
|
|
873
|
+
const stamps = tiles.map((t) => t.querySelector(".pkb-stamp"));
|
|
874
|
+
const kick = $("kick"), snd = $("snd"), perfectEl = $("perfect"), mfx = $("mfx");
|
|
568
875
|
let checked = normalize(void 0);
|
|
569
876
|
let cur = [];
|
|
570
877
|
let prevLines = /* @__PURE__ */ new Set();
|
|
571
878
|
let prevReach = /* @__PURE__ */ new Set();
|
|
572
879
|
let prevAll = false;
|
|
880
|
+
let perfectPending = false, restorePerfectFocus = false;
|
|
881
|
+
let perfectBeat;
|
|
573
882
|
let lastN = 0;
|
|
574
883
|
let score = 0, shown = 0, combo = 0, maxCombo = 0, lastT = 0, gotP = false;
|
|
575
884
|
const got = /* @__PURE__ */ new Set(), gotL = /* @__PURE__ */ new Set();
|
|
@@ -583,6 +892,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
583
892
|
snd.addEventListener("click", () => {
|
|
584
893
|
muted = !muted;
|
|
585
894
|
writeStore(MUTE_KEY, muted ? "1" : "0");
|
|
895
|
+
if (muted) sfx.stop();
|
|
586
896
|
setSnd();
|
|
587
897
|
if (!muted) sfx.pop(4);
|
|
588
898
|
});
|
|
@@ -597,7 +907,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
597
907
|
cv.width = Math.round(BW * dpr);
|
|
598
908
|
cv.height = Math.round(BH * dpr);
|
|
599
909
|
};
|
|
600
|
-
const piece = (x, y, vx, vy, g, s, life, shape2) => ({
|
|
910
|
+
const piece = (x, y, vx, vy, g, s, life, shape2, colors = CONFETTI) => ({
|
|
601
911
|
x,
|
|
602
912
|
y,
|
|
603
913
|
vx,
|
|
@@ -609,7 +919,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
609
919
|
h: (3 + Math.random() * 5) * s,
|
|
610
920
|
r: Math.random() * 6,
|
|
611
921
|
vr: (Math.random() - 0.5) * 0.35,
|
|
612
|
-
c:
|
|
922
|
+
c: colors[Math.random() * colors.length | 0]
|
|
613
923
|
});
|
|
614
924
|
const go = () => {
|
|
615
925
|
if (!raf && ctx) raf = requestAnimationFrame(tick);
|
|
@@ -620,7 +930,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
620
930
|
const a = (o.angle ?? -Math.PI / 2) + (Math.random() - 0.5) * (o.spread ?? Math.PI * 2);
|
|
621
931
|
const v = (o.speed ?? 10) * (0.35 + Math.random() * 0.85);
|
|
622
932
|
const sh = Math.random() < (o.star ?? 0.15) ? "star" : Math.random() < 0.3 ? "round" : "rect";
|
|
623
|
-
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));
|
|
933
|
+
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));
|
|
624
934
|
}
|
|
625
935
|
go();
|
|
626
936
|
}
|
|
@@ -715,8 +1025,10 @@ function createPackingBingo(container, options = {}) {
|
|
|
715
1025
|
};
|
|
716
1026
|
grid.addEventListener("animationend", (e) => {
|
|
717
1027
|
const t = e.target;
|
|
1028
|
+
if (e.animationName === "pkb-stamp-hit") t.classList.remove("pkb-hit");
|
|
718
1029
|
if (t.classList.contains("pkb-tile") && (e.animationName === "pkb-hop" || e.animationName === "pkb-squash"))
|
|
719
1030
|
t.classList.remove(e.animationName);
|
|
1031
|
+
if (t.classList.contains("pkb-tile") && e.animationName === "pkb-clr") t.classList.remove("pkb-clr");
|
|
720
1032
|
});
|
|
721
1033
|
const ring = (t) => {
|
|
722
1034
|
const r = document.createElement("span");
|
|
@@ -724,15 +1036,6 @@ function createPackingBingo(container, options = {}) {
|
|
|
724
1036
|
t.appendChild(r);
|
|
725
1037
|
r.addEventListener("animationend", () => r.remove());
|
|
726
1038
|
};
|
|
727
|
-
const buzz = (pattern) => {
|
|
728
|
-
if (!vibrate) return;
|
|
729
|
-
const ua = navigator.userActivation;
|
|
730
|
-
if (ua && !ua.hasBeenActive) return;
|
|
731
|
-
try {
|
|
732
|
-
navigator.vibrate?.(pattern);
|
|
733
|
-
} catch {
|
|
734
|
-
}
|
|
735
|
-
};
|
|
736
1039
|
const floatText = (txt, x, y, cls, delay = 0, color) => {
|
|
737
1040
|
const el = document.createElement("div");
|
|
738
1041
|
el.className = "pkb-float pkb-" + cls;
|
|
@@ -744,20 +1047,20 @@ function createPackingBingo(container, options = {}) {
|
|
|
744
1047
|
board.appendChild(el);
|
|
745
1048
|
el.addEventListener("animationend", () => el.remove());
|
|
746
1049
|
};
|
|
747
|
-
const prog = (t,
|
|
1050
|
+
const prog = (t, t02, d) => Math.max(0, Math.min(1, (t - t02) / d));
|
|
748
1051
|
const ease = (k) => 1 - Math.pow(1 - k, 3);
|
|
749
1052
|
let tw = 0;
|
|
750
1053
|
function countScore() {
|
|
751
1054
|
cancelAnimationFrame(tw);
|
|
752
|
-
const a = shown, b = score,
|
|
1055
|
+
const a = shown, b = score, t02 = performance.now();
|
|
753
1056
|
const st = (t) => {
|
|
754
1057
|
if (dead) return;
|
|
755
|
-
const k = prog(t,
|
|
1058
|
+
const k = prog(t, t02, 450);
|
|
756
1059
|
shown = Math.round(a + (b - a) * ease(k));
|
|
757
1060
|
$("sc").textContent = shown.toLocaleString();
|
|
758
1061
|
if (k < 1) tw = requestAnimationFrame(st);
|
|
759
1062
|
};
|
|
760
|
-
st(
|
|
1063
|
+
st(t02);
|
|
761
1064
|
if (!calm) replay($("sp"), "pkb-bump");
|
|
762
1065
|
}
|
|
763
1066
|
const showGain = (pts, x, y, cls = "pt") => {
|
|
@@ -765,12 +1068,12 @@ function createPackingBingo(container, options = {}) {
|
|
|
765
1068
|
countScore();
|
|
766
1069
|
};
|
|
767
1070
|
function countUp(el, a, b, d, fmt, ticks = false) {
|
|
768
|
-
const
|
|
1071
|
+
const t02 = performance.now();
|
|
769
1072
|
const g = gen;
|
|
770
1073
|
let lt = 0;
|
|
771
1074
|
const st = (t) => {
|
|
772
1075
|
if (dead || g !== gen) return;
|
|
773
|
-
const k = prog(t,
|
|
1076
|
+
const k = prog(t, t02, d);
|
|
774
1077
|
el.textContent = fmt(Math.round(a + (b - a) * ease(k)));
|
|
775
1078
|
if (ticks && k < 1 && t - lt > 55) {
|
|
776
1079
|
sfx.tick();
|
|
@@ -779,7 +1082,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
779
1082
|
if (k < 1) requestAnimationFrame(st);
|
|
780
1083
|
else if (!calm) replay(el, "pkb-bump");
|
|
781
1084
|
};
|
|
782
|
-
st(
|
|
1085
|
+
st(t02);
|
|
783
1086
|
}
|
|
784
1087
|
const ctr = (t) => {
|
|
785
1088
|
return { x: t.offsetLeft + t.offsetWidth / 2, y: t.offsetTop + t.offsetHeight / 2 };
|
|
@@ -798,25 +1101,147 @@ function createPackingBingo(container, options = {}) {
|
|
|
798
1101
|
return `<g${fresh.includes(k) ? ' class="pkb-draw"' : ""}><line class="pkb-u" ${at}/><line class="pkb-o" ${at}/></g>`;
|
|
799
1102
|
}).join("");
|
|
800
1103
|
}
|
|
801
|
-
|
|
1104
|
+
const readyEl = container.querySelector('[data-r="ready"]');
|
|
1105
|
+
const watch = $("watch");
|
|
1106
|
+
let phase = "idle";
|
|
1107
|
+
let t0 = 0, watchTimer = 0;
|
|
1108
|
+
let finalMs = null;
|
|
1109
|
+
const cdTimers = /* @__PURE__ */ new Set();
|
|
1110
|
+
const cd = (fn, ms) => {
|
|
1111
|
+
const id = window.setTimeout(() => {
|
|
1112
|
+
cdTimers.delete(id);
|
|
1113
|
+
if (!dead) fn();
|
|
1114
|
+
}, ms);
|
|
1115
|
+
cdTimers.add(id);
|
|
1116
|
+
};
|
|
1117
|
+
const stopTimers = () => {
|
|
1118
|
+
cdTimers.forEach((id) => clearTimeout(id));
|
|
1119
|
+
cdTimers.clear();
|
|
1120
|
+
clearInterval(watchTimer);
|
|
1121
|
+
};
|
|
1122
|
+
const elapsed = () => phase === "play" ? performance.now() - t0 : finalMs ?? 0;
|
|
1123
|
+
const showWatch = () => {
|
|
1124
|
+
watch.textContent = formatTime(elapsed());
|
|
1125
|
+
};
|
|
1126
|
+
function startWatch() {
|
|
1127
|
+
phase = "play";
|
|
1128
|
+
t0 = performance.now();
|
|
1129
|
+
finalMs = null;
|
|
1130
|
+
clearInterval(watchTimer);
|
|
1131
|
+
watchTimer = window.setInterval(showWatch, 100);
|
|
1132
|
+
watch.classList.remove("pkb-done");
|
|
1133
|
+
watch.classList.add("pkb-run");
|
|
1134
|
+
showWatch();
|
|
1135
|
+
syncFin();
|
|
1136
|
+
}
|
|
1137
|
+
const finBtn = $("fin");
|
|
1138
|
+
const shufBtn = container.querySelector('[data-r="shuf"]');
|
|
1139
|
+
const syncFin = () => {
|
|
1140
|
+
finBtn.disabled = !(inf() && phase === "play");
|
|
1141
|
+
if (shufBtn) shufBtn.disabled = inf() && phase === "done";
|
|
1142
|
+
};
|
|
1143
|
+
function finish() {
|
|
1144
|
+
if (phase === "play") {
|
|
1145
|
+
finalMs = performance.now() - t0;
|
|
1146
|
+
watch.classList.replace("pkb-run", "pkb-done");
|
|
1147
|
+
} else if (phase !== "done") {
|
|
1148
|
+
stopTimers();
|
|
1149
|
+
openGrid(false);
|
|
1150
|
+
}
|
|
1151
|
+
phase = "done";
|
|
1152
|
+
clearInterval(watchTimer);
|
|
1153
|
+
showWatch();
|
|
1154
|
+
}
|
|
1155
|
+
function openGrid(fade) {
|
|
1156
|
+
grid.removeAttribute("inert");
|
|
1157
|
+
if (!readyEl || readyEl.hidden) return;
|
|
1158
|
+
const focused = document.activeElement === readyEl;
|
|
1159
|
+
readyEl.classList.add("pkb-gone");
|
|
1160
|
+
board.classList.remove("pkb-waiting");
|
|
1161
|
+
readyEl.tabIndex = -1;
|
|
1162
|
+
if (focused) grid.querySelector("input")?.focus({ preventScroll: true });
|
|
1163
|
+
if (fade) cd(() => readyEl.hidden = true, GO_MS);
|
|
1164
|
+
else readyEl.hidden = true;
|
|
1165
|
+
}
|
|
1166
|
+
function toReady() {
|
|
1167
|
+
stopTimers();
|
|
1168
|
+
finalMs = null;
|
|
1169
|
+
watch.classList.remove("pkb-run", "pkb-done");
|
|
1170
|
+
phase = readyEl ? "ready" : "idle";
|
|
1171
|
+
showWatch();
|
|
1172
|
+
syncFin();
|
|
1173
|
+
board.classList.remove("pkb-over");
|
|
1174
|
+
grid.removeAttribute("inert");
|
|
1175
|
+
if (!readyEl) return;
|
|
1176
|
+
readyEl.hidden = false;
|
|
1177
|
+
readyEl.removeAttribute("tabindex");
|
|
1178
|
+
readyEl.classList.remove("pkb-gone", "pkb-counting");
|
|
1179
|
+
board.classList.add("pkb-waiting");
|
|
1180
|
+
$("count").textContent = "";
|
|
1181
|
+
grid.setAttribute("inert", "");
|
|
1182
|
+
}
|
|
1183
|
+
let resetAsked = false;
|
|
1184
|
+
const restartGame = () => {
|
|
1185
|
+
if (!controlled || !checked.some(Boolean)) toReady();
|
|
1186
|
+
else resetAsked = true;
|
|
1187
|
+
};
|
|
1188
|
+
function skipReady() {
|
|
1189
|
+
if (phase === "play" || phase === "done") return;
|
|
1190
|
+
stopTimers();
|
|
1191
|
+
openGrid(false);
|
|
1192
|
+
startWatch();
|
|
1193
|
+
}
|
|
1194
|
+
function countdown() {
|
|
1195
|
+
if (phase !== "ready") return;
|
|
1196
|
+
phase = "count";
|
|
1197
|
+
readyEl.classList.add("pkb-counting");
|
|
1198
|
+
const count = $("count");
|
|
1199
|
+
const words = ["3", "2", "1", M.readyGo?.trim() || ja.readyGo];
|
|
1200
|
+
words.forEach((word, k) => {
|
|
1201
|
+
const go2 = k === words.length - 1;
|
|
1202
|
+
const step = () => {
|
|
1203
|
+
count.textContent = word;
|
|
1204
|
+
count.classList.toggle("pkb-go", go2);
|
|
1205
|
+
replay(count, "pkb-tick");
|
|
1206
|
+
sfx.count(go2);
|
|
1207
|
+
haptics.play(go2 ? "go" : "count");
|
|
1208
|
+
if (go2) {
|
|
1209
|
+
startWatch();
|
|
1210
|
+
openGrid(true);
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
if (k) cd(step, k * COUNT_MS);
|
|
1214
|
+
else step();
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
readyEl?.addEventListener("click", countdown);
|
|
1218
|
+
function tapFx(i, on, moment, fresh) {
|
|
802
1219
|
const p = rel(tiles[i]);
|
|
803
1220
|
if (!on) {
|
|
1221
|
+
stamps[i].classList.remove("pkb-hit");
|
|
804
1222
|
combo = 0;
|
|
805
1223
|
$("combo").classList.remove("pkb-on");
|
|
806
1224
|
sfx.off();
|
|
807
|
-
|
|
1225
|
+
haptics.play("off");
|
|
808
1226
|
return;
|
|
809
1227
|
}
|
|
810
1228
|
const now = performance.now();
|
|
811
1229
|
combo = now - lastT < COMBO_MS ? combo + 1 : 1;
|
|
812
1230
|
lastT = now;
|
|
813
1231
|
maxCombo = Math.max(maxCombo, combo);
|
|
1232
|
+
const seqLength = new Set(fresh.flatMap((k) => cellLines[k])).size;
|
|
1233
|
+
const impactAt = Math.min(70 * seqLength, 300) + 60;
|
|
1234
|
+
const beat = haptics.play(moment, combo, fresh.length, impactAt);
|
|
1235
|
+
perfectBeat = moment === "perfect" ? beat : void 0;
|
|
1236
|
+
const energy = Math.min(3, Math.floor(combo / 3));
|
|
814
1237
|
sfx.pop(combo - 1);
|
|
815
1238
|
if (!calm) {
|
|
1239
|
+
tiles[i].style.setProperty("--pkb-impact", String(energy));
|
|
816
1240
|
replay(tiles[i], "pkb-squash");
|
|
1241
|
+
replay(stamps[i], "pkb-hit");
|
|
817
1242
|
ring(tiles[i]);
|
|
818
1243
|
}
|
|
819
|
-
burst(p.x, p.y, 18, { speed: 6, g: 0.2, life: 30, size: 0.6, star: 0.5 });
|
|
1244
|
+
burst(p.x, p.y, 18 + energy * 6, { speed: 6 + energy, g: 0.2, life: 30, size: 0.6, star: 0.5 });
|
|
820
1245
|
if (!got.has(i)) {
|
|
821
1246
|
got.add(i);
|
|
822
1247
|
score += 10 * combo;
|
|
@@ -825,9 +1250,10 @@ function createPackingBingo(container, options = {}) {
|
|
|
825
1250
|
if (combo >= 2) {
|
|
826
1251
|
floatText(M.comboPop(combo), p.x, p.y + 22, "cmb", 90, COMBO_COLORS[(combo - 2) % COMBO_COLORS.length]);
|
|
827
1252
|
$("cmbN").textContent = M.combo(combo);
|
|
828
|
-
|
|
1253
|
+
$("combo").classList.add("pkb-on");
|
|
1254
|
+
replay($("cbar"), "pkb-draining");
|
|
1255
|
+
if (!calm) replay($("cmbN"), "pkb-bump");
|
|
829
1256
|
}
|
|
830
|
-
buzz(18);
|
|
831
1257
|
}
|
|
832
1258
|
$("cbar").addEventListener("animationend", () => {
|
|
833
1259
|
$("combo").classList.remove("pkb-on");
|
|
@@ -840,7 +1266,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
840
1266
|
});
|
|
841
1267
|
later(() => sfx.reach(), 150);
|
|
842
1268
|
}
|
|
843
|
-
function bingo(fresh, pts) {
|
|
1269
|
+
function bingo(fresh, pts, total) {
|
|
844
1270
|
const nl = fresh.length;
|
|
845
1271
|
const seq = [...new Set(fresh.flatMap((k) => cellLines[k]))].map((c) => cellTiles[c]);
|
|
846
1272
|
const step = Math.min(70, 300 / seq.length);
|
|
@@ -854,7 +1280,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
854
1280
|
);
|
|
855
1281
|
later(() => {
|
|
856
1282
|
$("bT").textContent = nl >= 3 ? M.bannerTriple : nl === 2 ? M.bannerDouble : M.bannerSingle;
|
|
857
|
-
$("bS").textContent = nl > 1 ? M.bannerMulti(nl) : M.bannerNth(
|
|
1283
|
+
$("bS").textContent = nl > 1 ? M.bannerMulti(nl) : M.bannerNth(total);
|
|
858
1284
|
replay($("banner"), "pkb-show");
|
|
859
1285
|
if (!calm) {
|
|
860
1286
|
replay($("flash"), "pkb-go");
|
|
@@ -863,54 +1289,126 @@ function createPackingBingo(container, options = {}) {
|
|
|
863
1289
|
const c = rel(gw);
|
|
864
1290
|
burst(c.x, c.y, nl > 1 ? 180 : 110, { speed: nl > 1 ? 16 : 13, star: 0.3 });
|
|
865
1291
|
sfx.bingo(nl);
|
|
866
|
-
buzz(nl > 1 ? [40, 60, 40, 60, 90] : [40, 60, 40]);
|
|
867
1292
|
if (pts) later(() => showGain(pts, c.x, c.y + 105, "big"), 350);
|
|
868
1293
|
}, seq.length * step + 60);
|
|
869
1294
|
}
|
|
870
1295
|
function perfect(events) {
|
|
1296
|
+
const beat = perfectBeat;
|
|
1297
|
+
perfectPending = true;
|
|
1298
|
+
perfectEl.classList.remove("pkb-settled");
|
|
871
1299
|
const bonus = gotP ? 0 : 1e3;
|
|
1300
|
+
const speed = gotP || finalMs == null ? 0 : speedBonus(N, finalMs);
|
|
872
1301
|
gotP = true;
|
|
873
|
-
score += bonus;
|
|
1302
|
+
score += bonus + speed;
|
|
874
1303
|
cellTiles.forEach((t, c) => hop(t, far(c) * 180 / Math.max(1, n - 1)));
|
|
875
1304
|
sfx.perfect();
|
|
876
1305
|
if (!calm) replay($("flash"), "pkb-go");
|
|
1306
|
+
setResult(false);
|
|
877
1307
|
$("pbody").textContent = perfectText(M, N);
|
|
1308
|
+
$("ptime").hidden = finalMs == null;
|
|
1309
|
+
$("pT").textContent = formatTime(finalMs ?? 0, 2);
|
|
878
1310
|
$("sScore").textContent = "0";
|
|
879
1311
|
$("sCombo").textContent = "\xD70";
|
|
880
1312
|
$("sBingo").textContent = "0";
|
|
881
1313
|
later(() => {
|
|
1314
|
+
perfectPending = false;
|
|
882
1315
|
replay(perfectEl, "pkb-show");
|
|
883
1316
|
$("close").focus({ preventScroll: true });
|
|
884
1317
|
later(() => {
|
|
885
1318
|
if (!calm) replay(board, "pkb-shake");
|
|
886
1319
|
sfx.boom();
|
|
1320
|
+
beat?.(PERFECT_STAMP_BEAT);
|
|
887
1321
|
burst(0, BH, 90, { angle: -Math.PI / 3, spread: 0.8, speed: 22, g: 0.32, star: 0.3 });
|
|
888
1322
|
burst(BW, BH, 90, { angle: -Math.PI * 2 / 3, spread: 0.8, speed: 22, g: 0.32, star: 0.3 });
|
|
889
1323
|
rain(3500);
|
|
890
|
-
buzz([60, 80, 60, 80, 200]);
|
|
891
1324
|
if (bonus) floatText(M.perfectBonus(bonus), BW / 2, BH * 0.16, "big");
|
|
1325
|
+
const gap = Math.min(34, Math.max(14, BW * 0.056)) * 1.3 + 6;
|
|
1326
|
+
if (speed) floatText((M.speedBonus ?? ja.speedBonus)(speed), BW / 2, Math.max(gap / 2 + 4, BH * 0.16 - gap), "big");
|
|
892
1327
|
countUp($("sScore"), 0, score, 1300, (v) => v.toLocaleString(), true);
|
|
893
1328
|
countUp($("sCombo"), 0, maxCombo, 700, (v) => "\xD7" + v);
|
|
894
|
-
countUp($("sBingo"), 0,
|
|
1329
|
+
countUp($("sBingo"), 0, resultLines(), 700, String);
|
|
895
1330
|
countScore();
|
|
896
|
-
},
|
|
897
|
-
[1300, 1650, 2e3, 2350, 2700].forEach(
|
|
898
|
-
(t) => later(() => firework(BW * (0.15 + Math.random() * 0.7), BH * (0.12 + Math.random() * 0.3)), t)
|
|
899
|
-
);
|
|
1331
|
+
}, PERFECT_STAMP_DELAY_MS);
|
|
900
1332
|
later(() => emojiRain(3e3), 1500);
|
|
901
|
-
},
|
|
902
|
-
|
|
1333
|
+
}, PERFECT_REVEAL_MS);
|
|
1334
|
+
PERFECT_FIREWORKS.forEach(
|
|
1335
|
+
({ at, beat: pulse }) => later(() => {
|
|
1336
|
+
firework(BW * (0.15 + Math.random() * 0.7), BH * (0.12 + Math.random() * 0.3));
|
|
1337
|
+
beat?.(pulse);
|
|
1338
|
+
}, at)
|
|
1339
|
+
);
|
|
1340
|
+
const e = { score, maxCombo, time: finalMs == null ? null : Math.round(finalMs), speedBonus: speed };
|
|
903
1341
|
events.push(() => options.onPerfect?.(e));
|
|
904
1342
|
}
|
|
1343
|
+
function clearNow(animate) {
|
|
1344
|
+
clearTimeout(clearTimer);
|
|
1345
|
+
clearTimer = 0;
|
|
1346
|
+
if (!pending.size) return;
|
|
1347
|
+
const gone = [...pending].filter((i) => checked[i]);
|
|
1348
|
+
pending.clear();
|
|
1349
|
+
checked = checked.slice();
|
|
1350
|
+
gone.forEach((i) => {
|
|
1351
|
+
checked[i] = false;
|
|
1352
|
+
ins[i].checked = false;
|
|
1353
|
+
got.delete(i);
|
|
1354
|
+
});
|
|
1355
|
+
gotL.clear();
|
|
1356
|
+
const left = completeLines();
|
|
1357
|
+
left.forEach((k) => gotL.add(lineKey(lines[k])));
|
|
1358
|
+
prevLines = new Set(left);
|
|
1359
|
+
render(null, true);
|
|
1360
|
+
if (!animate || !gone.length) return;
|
|
1361
|
+
sfx.clear(gone.length);
|
|
1362
|
+
gone.forEach((i, j) => {
|
|
1363
|
+
const t = tiles[i];
|
|
1364
|
+
if (!calm) {
|
|
1365
|
+
t.style.setProperty("--pkb-cd", Math.min(j * 28, 280) + "ms");
|
|
1366
|
+
t.classList.remove("pkb-squash", "pkb-hop");
|
|
1367
|
+
replay(t, "pkb-clr");
|
|
1368
|
+
}
|
|
1369
|
+
const p = rel(t);
|
|
1370
|
+
burst(p.x, p.y, 9, { speed: 4.5, g: 0.06, life: 45, size: 0.75, star: 1, colors: SPARK.slice(1) });
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
function scheduleClear(ms) {
|
|
1374
|
+
const at = performance.now() + ms;
|
|
1375
|
+
if (clearTimer && at <= clearAt) return;
|
|
1376
|
+
clearTimeout(clearTimer);
|
|
1377
|
+
clearAt = at;
|
|
1378
|
+
clearTimer = window.setTimeout(() => {
|
|
1379
|
+
clearTimer = 0;
|
|
1380
|
+
if (!dead) clearNow(true);
|
|
1381
|
+
}, ms);
|
|
1382
|
+
}
|
|
1383
|
+
const dropClear = () => {
|
|
1384
|
+
clearTimeout(clearTimer);
|
|
1385
|
+
clearTimer = 0;
|
|
1386
|
+
pending.clear();
|
|
1387
|
+
};
|
|
1388
|
+
function setResult(fin) {
|
|
1389
|
+
const title = fin ? M.finishTitle?.trim() || ja.finishTitle : M.perfectTitle;
|
|
1390
|
+
perfectEl.classList.toggle("pkb-fin", fin);
|
|
1391
|
+
$("pcard").setAttribute("aria-label", title);
|
|
1392
|
+
$("medal").textContent = fin ? M.finishStamp?.trim() || ja.finishStamp : M.perfectStamp;
|
|
1393
|
+
$("ptitle").textContent = title;
|
|
1394
|
+
$("pTl").textContent = fin ? M.finishTime?.trim() || ja.finishTime : M.statTime?.trim() || ja.statTime;
|
|
1395
|
+
$("again").hidden = !fin;
|
|
1396
|
+
}
|
|
1397
|
+
const resultLines = () => inf() ? cleared : cur.length;
|
|
1398
|
+
const resultFocus = () => $(perfectEl.classList.contains("pkb-fin") ? "again" : "close");
|
|
905
1399
|
function cancelFx() {
|
|
1400
|
+
if (pending.size) clearNow(false);
|
|
906
1401
|
gen++;
|
|
1402
|
+
perfectPending = false;
|
|
907
1403
|
timers.forEach((id) => clearTimeout(id));
|
|
908
1404
|
timers.clear();
|
|
1405
|
+
haptics.stop();
|
|
1406
|
+
sfx.stop();
|
|
909
1407
|
cancelAnimationFrame(tw);
|
|
910
1408
|
shown = score;
|
|
911
1409
|
$("sc").textContent = score.toLocaleString();
|
|
912
1410
|
}
|
|
913
|
-
function clearVisuals() {
|
|
1411
|
+
function clearVisuals(keepPerfect = false) {
|
|
914
1412
|
P = [];
|
|
915
1413
|
cancelAnimationFrame(raf);
|
|
916
1414
|
raf = 0;
|
|
@@ -919,18 +1417,33 @@ function createPackingBingo(container, options = {}) {
|
|
|
919
1417
|
$("banner").classList.remove("pkb-show");
|
|
920
1418
|
$("flash").classList.remove("pkb-go");
|
|
921
1419
|
$("combo").classList.remove("pkb-on");
|
|
922
|
-
|
|
1420
|
+
$("cbar").classList.remove("pkb-draining");
|
|
1421
|
+
cellTiles.forEach((t) => t.classList.remove("pkb-squash", "pkb-hop", "pkb-clr"));
|
|
1422
|
+
stamps.forEach((s) => s.classList.remove("pkb-hit"));
|
|
1423
|
+
board.classList.remove("pkb-punch", "pkb-shake", "pkb-switching");
|
|
1424
|
+
mfx.classList.remove("pkb-show");
|
|
1425
|
+
if (!keepPerfect) perfectEl.classList.remove("pkb-show", "pkb-settled");
|
|
923
1426
|
}
|
|
924
1427
|
const completeLines = () => lines.flatMap((l, k) => l.every((i) => checked[i]) ? [k] : []);
|
|
925
1428
|
function render(src, quiet) {
|
|
926
1429
|
const events = [];
|
|
927
1430
|
const on = checked, c = on.filter(Boolean).length;
|
|
1431
|
+
const endless = inf();
|
|
1432
|
+
const all = c === N && !endless;
|
|
928
1433
|
cur = completeLines();
|
|
929
1434
|
const fresh = cur.filter((k) => !prevLines.has(k));
|
|
930
1435
|
if ([...prevLines].some((k) => !cur.includes(k))) {
|
|
931
1436
|
cancelFx();
|
|
932
1437
|
if (c < N) perfectEl.classList.remove("pkb-show");
|
|
933
1438
|
}
|
|
1439
|
+
if (endless) {
|
|
1440
|
+
if (!quiet) cleared += fresh.length;
|
|
1441
|
+
if (cur.length) {
|
|
1442
|
+
const known = pending.size;
|
|
1443
|
+
cur.forEach((k) => lines[k].forEach((i) => pending.add(i)));
|
|
1444
|
+
if (pending.size > known || !clearTimer) scheduleClear(!quiet && fresh.length ? CLEAR_MS : FLY_MS + 200);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
934
1447
|
const hit = new Set(cur.flatMap((k) => cellLines[k]));
|
|
935
1448
|
const reach = /* @__PURE__ */ new Set();
|
|
936
1449
|
for (const l of lines) {
|
|
@@ -939,34 +1452,43 @@ function createPackingBingo(container, options = {}) {
|
|
|
939
1452
|
}
|
|
940
1453
|
tiles.forEach((t, i) => {
|
|
941
1454
|
t.classList.toggle("pkb-on", on[i]);
|
|
1455
|
+
const lock = on[i] && (!uncheck || pending.has(i));
|
|
1456
|
+
t.classList.toggle("pkb-lock", lock);
|
|
1457
|
+
if (lock) ins[i].setAttribute("aria-readonly", "true");
|
|
1458
|
+
else ins[i].removeAttribute("aria-readonly");
|
|
942
1459
|
t.classList.toggle("pkb-line", hit.has(L.itemCell[slotOf[i]]));
|
|
943
1460
|
t.classList.toggle("pkb-reach", reach.has(i));
|
|
944
1461
|
});
|
|
945
1462
|
freeTile?.classList.toggle("pkb-line", hit.has(L.center));
|
|
946
|
-
board.classList.toggle("pkb-all",
|
|
947
|
-
kick.classList.toggle("pkb-gold",
|
|
948
|
-
kick.textContent =
|
|
1463
|
+
board.classList.toggle("pkb-all", all);
|
|
1464
|
+
kick.classList.toggle("pkb-gold", all);
|
|
1465
|
+
kick.textContent = all ? M.perfectTag : M.tag;
|
|
949
1466
|
$("cnt").textContent = `${c}/${N}`;
|
|
950
|
-
|
|
1467
|
+
const total = endless ? cleared : cur.length;
|
|
1468
|
+
$("bng").textContent = String(total);
|
|
951
1469
|
const bp = $("bp");
|
|
952
|
-
if (
|
|
1470
|
+
if (total > lastN) {
|
|
953
1471
|
bp.classList.remove("pkb-hot");
|
|
954
1472
|
void bp.offsetWidth;
|
|
955
1473
|
}
|
|
956
|
-
bp.classList.toggle("pkb-hot",
|
|
957
|
-
lastN =
|
|
1474
|
+
bp.classList.toggle("pkb-hot", total > 0);
|
|
1475
|
+
lastN = total;
|
|
958
1476
|
drawLines(fresh);
|
|
959
|
-
if (src !== null)
|
|
1477
|
+
if (src !== null) {
|
|
1478
|
+
const newReach = [...reach].some((i) => !prevReach.has(i));
|
|
1479
|
+
const moment = !quiet && all && !prevAll ? "perfect" : !quiet && fresh.length ? "bingo" : !quiet && newReach ? "reach" : "tap";
|
|
1480
|
+
tapFx(src, on[src], moment, fresh);
|
|
1481
|
+
}
|
|
960
1482
|
const newL = fresh.map((k) => lineKey(lines[k])).filter((key) => !gotL.has(key));
|
|
961
1483
|
newL.forEach((key) => gotL.add(key));
|
|
962
1484
|
const linePts = 100 * newL.length * newL.length;
|
|
963
1485
|
score += linePts;
|
|
964
1486
|
if (quiet) {
|
|
965
1487
|
if (linePts) countScore();
|
|
966
|
-
} else if (
|
|
1488
|
+
} else if (all && !prevAll) perfect(events);
|
|
967
1489
|
else if (fresh.length) {
|
|
968
|
-
bingo(fresh, linePts);
|
|
969
|
-
const e = { newLines: fresh, totalLines:
|
|
1490
|
+
bingo(fresh, linePts, total);
|
|
1491
|
+
const e = { newLines: fresh, totalLines: total, score, mode };
|
|
970
1492
|
events.push(() => options.onBingo?.(e));
|
|
971
1493
|
} else if (src !== null && on[src]) {
|
|
972
1494
|
const nr = [...reach].filter((i) => !prevReach.has(i));
|
|
@@ -974,9 +1496,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
974
1496
|
}
|
|
975
1497
|
prevLines = new Set(cur);
|
|
976
1498
|
prevReach = reach;
|
|
977
|
-
prevAll =
|
|
1499
|
+
prevAll = all;
|
|
978
1500
|
const labels = [...reach].map((i) => items[i].label);
|
|
979
|
-
$("msg").textContent =
|
|
1501
|
+
$("msg").textContent = all ? M.statusDone : labels.length ? M.statusReach(labels) : cur.length ? M.statusLines(cur.length, N - c) : M.statusStart(N - c);
|
|
980
1502
|
return events;
|
|
981
1503
|
}
|
|
982
1504
|
const flush = (events) => {
|
|
@@ -986,9 +1508,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
986
1508
|
}
|
|
987
1509
|
};
|
|
988
1510
|
const save = () => {
|
|
989
|
-
if (storageKey) writeStore(storageKey, JSON.stringify(checked));
|
|
1511
|
+
if (storageKey && !inf()) writeStore(storageKey, JSON.stringify(checked));
|
|
990
1512
|
};
|
|
991
1513
|
function apply(next, src, quiet) {
|
|
1514
|
+
if (phase === "idle" && src !== null && next[src]) startWatch();
|
|
1515
|
+
if (!inf() && next.every(Boolean)) finish();
|
|
992
1516
|
checked = next;
|
|
993
1517
|
ins.forEach((x, i) => x.checked = checked[i]);
|
|
994
1518
|
const events = render(src, quiet);
|
|
@@ -996,8 +1520,12 @@ function createPackingBingo(container, options = {}) {
|
|
|
996
1520
|
return events;
|
|
997
1521
|
}
|
|
998
1522
|
function baseline(next) {
|
|
1523
|
+
dropClear();
|
|
1524
|
+
cleared = 0;
|
|
999
1525
|
cancelFx();
|
|
1000
1526
|
clearVisuals();
|
|
1527
|
+
if (!inf() && next.every(Boolean)) finish();
|
|
1528
|
+
else if ((phase === "play" || phase === "done") && !next.some(Boolean)) toReady();
|
|
1001
1529
|
checked = next;
|
|
1002
1530
|
ins.forEach((x, i) => x.checked = checked[i]);
|
|
1003
1531
|
got.clear();
|
|
@@ -1007,7 +1535,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1007
1535
|
done.forEach((k) => gotL.add(lineKey(lines[k])));
|
|
1008
1536
|
prevLines = new Set(done);
|
|
1009
1537
|
prevReach = /* @__PURE__ */ new Set();
|
|
1010
|
-
prevAll = checked.every(Boolean);
|
|
1538
|
+
prevAll = !inf() && checked.every(Boolean);
|
|
1011
1539
|
gotP = prevAll;
|
|
1012
1540
|
score = shown = got.size * 10 + gotL.size * 100;
|
|
1013
1541
|
combo = 0;
|
|
@@ -1017,7 +1545,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
1017
1545
|
save();
|
|
1018
1546
|
}
|
|
1019
1547
|
function request(next, src, opts = {}) {
|
|
1020
|
-
|
|
1548
|
+
const game = inf();
|
|
1549
|
+
if (controlled && !game) {
|
|
1550
|
+
resetAsked = !!opts.reset && resetAsked;
|
|
1021
1551
|
ins.forEach((x, i) => x.checked = checked[i]);
|
|
1022
1552
|
options.onChange?.(next.slice());
|
|
1023
1553
|
return;
|
|
@@ -1025,7 +1555,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1025
1555
|
let events = [];
|
|
1026
1556
|
if (opts.reset) baseline(next);
|
|
1027
1557
|
else events = apply(next, src, !!opts.quiet);
|
|
1028
|
-
options.onChange?.(checked.slice());
|
|
1558
|
+
if (!game) options.onChange?.(checked.slice());
|
|
1029
1559
|
flush(events);
|
|
1030
1560
|
}
|
|
1031
1561
|
function place(next) {
|
|
@@ -1044,12 +1574,12 @@ function createPackingBingo(container, options = {}) {
|
|
|
1044
1574
|
focused.focus({ preventScroll: true });
|
|
1045
1575
|
}
|
|
1046
1576
|
const saveOrder = () => {
|
|
1047
|
-
if (!orderKey) return;
|
|
1577
|
+
if (!orderKey || inf()) return;
|
|
1048
1578
|
writeStore(orderKey, JSON.stringify(order));
|
|
1049
1579
|
};
|
|
1050
1580
|
let flights = [];
|
|
1051
1581
|
function arrange(next) {
|
|
1052
|
-
if (!checked.every(Boolean)) cancelFx();
|
|
1582
|
+
if (inf() || !checked.every(Boolean)) cancelFx();
|
|
1053
1583
|
const from = calm ? null : tiles.map((t) => t.getBoundingClientRect());
|
|
1054
1584
|
flights.forEach((a) => a.cancel());
|
|
1055
1585
|
flights = [];
|
|
@@ -1085,6 +1615,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1085
1615
|
svg.innerHTML = "";
|
|
1086
1616
|
}
|
|
1087
1617
|
function shuffleCard() {
|
|
1618
|
+
if (inf() && phase === "done") return;
|
|
1088
1619
|
let next = order.slice();
|
|
1089
1620
|
do {
|
|
1090
1621
|
for (let s = N - 1; s > 0; s--) {
|
|
@@ -1094,31 +1625,98 @@ function createPackingBingo(container, options = {}) {
|
|
|
1094
1625
|
} while (next.every((v, s) => v === order[s]));
|
|
1095
1626
|
arrange(next);
|
|
1096
1627
|
sfx.shuffle();
|
|
1097
|
-
|
|
1628
|
+
haptics.play("shuffle");
|
|
1098
1629
|
}
|
|
1099
1630
|
const unshuffle = () => {
|
|
1100
1631
|
if (!isIdentity(order)) arrange(identity());
|
|
1101
1632
|
};
|
|
1633
|
+
const locked = (i) => checked[i] && (!uncheck || pending.has(i));
|
|
1634
|
+
grid.addEventListener("click", (e) => {
|
|
1635
|
+
const i = ins.indexOf(e.target);
|
|
1636
|
+
if (i >= 0 && locked(i)) e.preventDefault();
|
|
1637
|
+
});
|
|
1102
1638
|
grid.addEventListener("change", (e) => {
|
|
1103
1639
|
const i = ins.indexOf(e.target);
|
|
1104
1640
|
if (i < 0) return;
|
|
1641
|
+
if (locked(i)) {
|
|
1642
|
+
ins[i].checked = true;
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1105
1645
|
const next = checked.slice();
|
|
1106
1646
|
next[i] = !next[i];
|
|
1107
1647
|
request(next, i);
|
|
1108
1648
|
});
|
|
1109
1649
|
container.querySelector('[data-r="shuf"]')?.addEventListener("click", shuffleCard);
|
|
1650
|
+
const restartInfinite = () => {
|
|
1651
|
+
baseline(normalize(void 0));
|
|
1652
|
+
toReady();
|
|
1653
|
+
};
|
|
1110
1654
|
$("reset").addEventListener("click", () => {
|
|
1655
|
+
if (inf()) return restartInfinite();
|
|
1111
1656
|
unshuffle();
|
|
1657
|
+
restartGame();
|
|
1112
1658
|
request(normalize(void 0), null, { reset: true });
|
|
1113
1659
|
});
|
|
1114
1660
|
$("close").addEventListener("click", () => perfectEl.classList.remove("pkb-show"));
|
|
1661
|
+
$("again").addEventListener("click", () => {
|
|
1662
|
+
if (!inf()) return;
|
|
1663
|
+
restartInfinite();
|
|
1664
|
+
(readyEl && !readyEl.hidden ? readyEl : grid.querySelector("input"))?.focus({ preventScroll: true });
|
|
1665
|
+
});
|
|
1666
|
+
function finishGame() {
|
|
1667
|
+
if (dead || !inf() || phase !== "play") return;
|
|
1668
|
+
cancelFx();
|
|
1669
|
+
clearVisuals();
|
|
1670
|
+
finalMs = performance.now() - t0;
|
|
1671
|
+
phase = "done";
|
|
1672
|
+
clearInterval(watchTimer);
|
|
1673
|
+
watch.classList.replace("pkb-run", "pkb-done");
|
|
1674
|
+
showWatch();
|
|
1675
|
+
syncFin();
|
|
1676
|
+
combo = 0;
|
|
1677
|
+
grid.setAttribute("inert", "");
|
|
1678
|
+
board.classList.add("pkb-over");
|
|
1679
|
+
setResult(true);
|
|
1680
|
+
$("pbody").textContent = "";
|
|
1681
|
+
$("ptime").hidden = false;
|
|
1682
|
+
$("pT").textContent = formatTime(finalMs, 2);
|
|
1683
|
+
$("sScore").textContent = "0";
|
|
1684
|
+
$("sCombo").textContent = "\xD70";
|
|
1685
|
+
$("sBingo").textContent = "0";
|
|
1686
|
+
perfectEl.classList.remove("pkb-settled");
|
|
1687
|
+
replay(perfectEl, "pkb-show");
|
|
1688
|
+
resultFocus().focus({ preventScroll: true });
|
|
1689
|
+
sfx.perfect();
|
|
1690
|
+
const beat = haptics.play("finish");
|
|
1691
|
+
if (!calm) replay($("flash"), "pkb-go");
|
|
1692
|
+
later(() => {
|
|
1693
|
+
if (!calm) replay(board, "pkb-shake");
|
|
1694
|
+
sfx.boom();
|
|
1695
|
+
beat?.(PERFECT_STAMP_BEAT);
|
|
1696
|
+
burst(0, BH, 80, { angle: -Math.PI / 3, spread: 0.8, speed: 21, g: 0.32, star: 0.5, colors: SPARK });
|
|
1697
|
+
burst(BW, BH, 80, { angle: -Math.PI * 2 / 3, spread: 0.8, speed: 21, g: 0.32, star: 0.5, colors: SPARK });
|
|
1698
|
+
rain(2500);
|
|
1699
|
+
countUp($("sScore"), 0, score, 1300, (v) => v.toLocaleString(), true);
|
|
1700
|
+
countUp($("sCombo"), 0, maxCombo, 700, (v) => "\xD7" + v);
|
|
1701
|
+
countUp($("sBingo"), 0, cleared, 700, String);
|
|
1702
|
+
}, PERFECT_STAMP_DELAY_MS);
|
|
1703
|
+
[1250, 1700, 2200].forEach(
|
|
1704
|
+
(at, k) => later(() => {
|
|
1705
|
+
firework(BW * (0.15 + Math.random() * 0.7), BH * (0.12 + Math.random() * 0.3));
|
|
1706
|
+
beat?.(PERFECT_FIREWORKS[k * 2].beat);
|
|
1707
|
+
}, at)
|
|
1708
|
+
);
|
|
1709
|
+
const e = { score, maxCombo, lines: cleared, time: Math.round(finalMs) };
|
|
1710
|
+
options.onFinish?.(e);
|
|
1711
|
+
}
|
|
1712
|
+
finBtn.addEventListener("click", finishGame);
|
|
1115
1713
|
perfectEl.addEventListener("click", (e) => {
|
|
1116
1714
|
if (e.target === perfectEl) perfectEl.classList.remove("pkb-show");
|
|
1117
1715
|
});
|
|
1118
1716
|
if (demo) {
|
|
1119
1717
|
let busy = false;
|
|
1120
1718
|
const prepare = () => {
|
|
1121
|
-
if (checked.every(Boolean)) request(normalize(void 0), null, { reset: true });
|
|
1719
|
+
if (!inf() && checked.every(Boolean)) request(normalize(void 0), null, { reset: true });
|
|
1122
1720
|
else perfectEl.classList.remove("pkb-show");
|
|
1123
1721
|
};
|
|
1124
1722
|
const run = async (steps) => {
|
|
@@ -1126,6 +1724,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1126
1724
|
busy = true;
|
|
1127
1725
|
try {
|
|
1128
1726
|
prepare();
|
|
1727
|
+
skipReady();
|
|
1129
1728
|
const g = gen;
|
|
1130
1729
|
for (const s of steps) {
|
|
1131
1730
|
await wait(s.delay);
|
|
@@ -1139,21 +1738,100 @@ function createPackingBingo(container, options = {}) {
|
|
|
1139
1738
|
}
|
|
1140
1739
|
};
|
|
1141
1740
|
$("dB").addEventListener("click", () => {
|
|
1741
|
+
if (inf() && phase === "done") return;
|
|
1142
1742
|
const open = () => lines.map((l) => l.filter((i) => !checked[i])).filter((m) => m.length).sort((a, b) => a.length - b.length)[0] ?? [];
|
|
1143
|
-
const pick = checked.every(Boolean) ? [...lines[0]] : open();
|
|
1743
|
+
const pick = !inf() && checked.every(Boolean) ? [...lines[0]] : open();
|
|
1144
1744
|
void run(pick.map((i) => ({ i, delay: 320 })));
|
|
1145
1745
|
});
|
|
1146
1746
|
$("dP").addEventListener("click", () => {
|
|
1747
|
+
if (inf()) return;
|
|
1147
1748
|
const all = items.map((_, i) => i);
|
|
1148
1749
|
const miss = checked.every(Boolean) ? all : all.filter((i) => !checked[i]);
|
|
1149
1750
|
void run(miss.map((i, k) => k < miss.length - 1 ? { i, delay: 170, quiet: true } : { i, delay: 450 }));
|
|
1150
1751
|
});
|
|
1151
1752
|
$("dR").addEventListener("click", () => {
|
|
1152
|
-
if (busy) return;
|
|
1753
|
+
if (busy || inf()) return;
|
|
1153
1754
|
unshuffle();
|
|
1755
|
+
restartGame();
|
|
1154
1756
|
request(demoStart(), null, { reset: true });
|
|
1155
1757
|
});
|
|
1156
1758
|
}
|
|
1759
|
+
const modeEl = container.querySelector('[data-r="mode"]');
|
|
1760
|
+
const radios = modeEl ? Array.from(modeEl.querySelectorAll("button[data-m]")) : [];
|
|
1761
|
+
const syncMode = () => {
|
|
1762
|
+
root.classList.toggle("pkb-inf", inf());
|
|
1763
|
+
radios.forEach((b) => {
|
|
1764
|
+
const on = b.dataset.m === mode;
|
|
1765
|
+
b.setAttribute("aria-checked", String(on));
|
|
1766
|
+
b.tabIndex = on ? 0 : -1;
|
|
1767
|
+
});
|
|
1768
|
+
};
|
|
1769
|
+
function modeFx() {
|
|
1770
|
+
const endless = inf();
|
|
1771
|
+
const title = endless ? M.infiniteTitle?.trim() || ja.infiniteTitle : M.normalTitle?.trim() || ja.normalTitle;
|
|
1772
|
+
const sub = endless ? M.infiniteSub?.trim() || ja.infiniteSub : M.normalSub?.trim() || ja.normalSub;
|
|
1773
|
+
const sparks = endless && !calm ? Array.from({ length: 18 }, (_, k) => {
|
|
1774
|
+
const x = (5 + Math.random() * 90).toFixed(1), y = (6 + Math.random() * 88).toFixed(1);
|
|
1775
|
+
const s = (0.45 + Math.random() * 0.95).toFixed(2), t = Math.round(100 + Math.random() * 950);
|
|
1776
|
+
return `<i style="--x:${x}%;--y:${y}%;--s:${s};--t:${t}ms;--c:${SPARK[k % SPARK.length]}"></i>`;
|
|
1777
|
+
}).join("") : "";
|
|
1778
|
+
mfx.className = "pkb-mfx " + (endless ? "pkb-mfx-inf" : "pkb-mfx-n");
|
|
1779
|
+
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>`;
|
|
1780
|
+
replay(mfx, "pkb-show");
|
|
1781
|
+
board.classList.add("pkb-switching");
|
|
1782
|
+
if (modeEl && !calm) replay(modeEl, "pkb-bump");
|
|
1783
|
+
sfx.mode(endless);
|
|
1784
|
+
haptics.play(endless ? "infinite" : "normal");
|
|
1785
|
+
const picked = radios.find((b) => b.dataset.m === mode);
|
|
1786
|
+
if (picked) {
|
|
1787
|
+
const p = rel(picked);
|
|
1788
|
+
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 });
|
|
1789
|
+
}
|
|
1790
|
+
later(() => {
|
|
1791
|
+
const c = rel(gw);
|
|
1792
|
+
if (endless) burst(c.x, c.y, 90, { speed: 12, g: 0.07, life: 90, star: 0.75, colors: SPARK });
|
|
1793
|
+
else {
|
|
1794
|
+
if (!calm) replay(board, "pkb-punch");
|
|
1795
|
+
burst(c.x, c.y, 70, { speed: 11, star: 0.2 });
|
|
1796
|
+
}
|
|
1797
|
+
}, endless ? 260 : 430);
|
|
1798
|
+
}
|
|
1799
|
+
mfx.addEventListener("animationend", (e) => {
|
|
1800
|
+
if (e.target !== mfx) return;
|
|
1801
|
+
mfx.classList.remove("pkb-show");
|
|
1802
|
+
board.classList.remove("pkb-switching");
|
|
1803
|
+
mfx.innerHTML = "";
|
|
1804
|
+
});
|
|
1805
|
+
function setMode(next) {
|
|
1806
|
+
if (dead || next === mode || next !== "normal" && next !== "infinite") return;
|
|
1807
|
+
const back = packing;
|
|
1808
|
+
if (next === "infinite") packing = { checked: checked.slice(), order: order.slice() };
|
|
1809
|
+
else packing = null;
|
|
1810
|
+
mode = next;
|
|
1811
|
+
resetAsked = false;
|
|
1812
|
+
if (back && back.order.some((v, s) => v !== order[s])) {
|
|
1813
|
+
flights.forEach((a) => a.cancel());
|
|
1814
|
+
flights = [];
|
|
1815
|
+
clearTimeout(landing);
|
|
1816
|
+
landing = 0;
|
|
1817
|
+
place(back.order);
|
|
1818
|
+
}
|
|
1819
|
+
syncMode();
|
|
1820
|
+
toReady();
|
|
1821
|
+
baseline(back ? back.checked : normalize(void 0));
|
|
1822
|
+
modeFx();
|
|
1823
|
+
options.onModeChange?.(mode);
|
|
1824
|
+
}
|
|
1825
|
+
modeEl?.addEventListener("click", (e) => {
|
|
1826
|
+
const b = e.target.closest("button[data-m]");
|
|
1827
|
+
if (b) setMode(b.dataset.m);
|
|
1828
|
+
});
|
|
1829
|
+
modeEl?.addEventListener("keydown", (e) => {
|
|
1830
|
+
if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(e.key)) return;
|
|
1831
|
+
e.preventDefault();
|
|
1832
|
+
setMode(inf() ? "normal" : "infinite");
|
|
1833
|
+
radios.find((b) => b.dataset.m === mode)?.focus();
|
|
1834
|
+
});
|
|
1157
1835
|
let startOrder = null;
|
|
1158
1836
|
if (orderKey) {
|
|
1159
1837
|
try {
|
|
@@ -1175,17 +1853,47 @@ function createPackingBingo(container, options = {}) {
|
|
|
1175
1853
|
if (!initial && options.defaultChecked) initial = normalize(options.defaultChecked);
|
|
1176
1854
|
if (!initial && demo) initial = demoStart();
|
|
1177
1855
|
fit();
|
|
1178
|
-
|
|
1856
|
+
if (inf()) packing = { checked: initial ?? normalize(void 0), order: order.slice() };
|
|
1857
|
+
syncMode();
|
|
1858
|
+
baseline(inf() ? normalize(void 0) : initial ?? normalize(void 0));
|
|
1859
|
+
if (phase !== "done") toReady();
|
|
1179
1860
|
const ro = typeof ResizeObserver === "function" ? new ResizeObserver(() => {
|
|
1180
1861
|
fit();
|
|
1181
1862
|
drawLines();
|
|
1182
1863
|
}) : null;
|
|
1183
1864
|
ro?.observe(board);
|
|
1184
1865
|
ro?.observe(gw);
|
|
1866
|
+
const onVisibility = () => {
|
|
1867
|
+
if (!document.hidden) {
|
|
1868
|
+
if (restorePerfectFocus && perfectEl.classList.contains("pkb-show")) resultFocus().focus({ preventScroll: true });
|
|
1869
|
+
restorePerfectFocus = false;
|
|
1870
|
+
return;
|
|
1871
|
+
}
|
|
1872
|
+
const keepPerfect = perfectPending || perfectEl.classList.contains("pkb-show");
|
|
1873
|
+
restorePerfectFocus = keepPerfect;
|
|
1874
|
+
cancelFx();
|
|
1875
|
+
clearVisuals(keepPerfect);
|
|
1876
|
+
if (keepPerfect) {
|
|
1877
|
+
$("sScore").textContent = score.toLocaleString();
|
|
1878
|
+
$("sCombo").textContent = "\xD7" + maxCombo;
|
|
1879
|
+
$("sBingo").textContent = String(resultLines());
|
|
1880
|
+
perfectEl.classList.add("pkb-show", "pkb-settled");
|
|
1881
|
+
}
|
|
1882
|
+
combo = 0;
|
|
1883
|
+
};
|
|
1884
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
1185
1885
|
return {
|
|
1186
1886
|
setChecked(next, opts = {}) {
|
|
1187
1887
|
if (dead) return;
|
|
1188
1888
|
const n2 = normalize(next);
|
|
1889
|
+
if (packing) {
|
|
1890
|
+
packing.checked = n2;
|
|
1891
|
+
resetAsked = false;
|
|
1892
|
+
if (storageKey) writeStore(storageKey, JSON.stringify(n2));
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
if (resetAsked && !n2.some(Boolean)) toReady();
|
|
1896
|
+
resetAsked = false;
|
|
1189
1897
|
const changed = n2.flatMap((v, i) => v !== checked[i] ? [i] : []);
|
|
1190
1898
|
if (!changed.length) return;
|
|
1191
1899
|
if (!opts.animate || changed.length !== 1) {
|
|
@@ -1196,12 +1904,20 @@ function createPackingBingo(container, options = {}) {
|
|
|
1196
1904
|
},
|
|
1197
1905
|
reset() {
|
|
1198
1906
|
if (dead) return;
|
|
1907
|
+
if (inf()) return restartInfinite();
|
|
1199
1908
|
unshuffle();
|
|
1909
|
+
restartGame();
|
|
1200
1910
|
request(normalize(void 0), null, { reset: true });
|
|
1201
1911
|
},
|
|
1202
1912
|
shuffle() {
|
|
1203
1913
|
if (!dead) shuffleCard();
|
|
1204
1914
|
},
|
|
1915
|
+
setMode(next) {
|
|
1916
|
+
setMode(next);
|
|
1917
|
+
},
|
|
1918
|
+
finish() {
|
|
1919
|
+
finishGame();
|
|
1920
|
+
},
|
|
1205
1921
|
refresh() {
|
|
1206
1922
|
if (!dead) render(null, true);
|
|
1207
1923
|
},
|
|
@@ -1211,22 +1927,31 @@ function createPackingBingo(container, options = {}) {
|
|
|
1211
1927
|
lines: cur.slice(),
|
|
1212
1928
|
score,
|
|
1213
1929
|
maxCombo,
|
|
1214
|
-
complete: checked.every(Boolean),
|
|
1930
|
+
complete: !inf() && checked.every(Boolean),
|
|
1931
|
+
mode,
|
|
1932
|
+
cleared,
|
|
1933
|
+
finished: inf() && phase === "done",
|
|
1934
|
+
packing: packing && { checked: packing.checked.slice(), order: packing.order.slice() },
|
|
1215
1935
|
size: n,
|
|
1216
1936
|
free: L.free,
|
|
1217
|
-
order: order.slice()
|
|
1937
|
+
order: order.slice(),
|
|
1938
|
+
time: Math.round(elapsed())
|
|
1218
1939
|
};
|
|
1219
1940
|
},
|
|
1220
1941
|
destroy() {
|
|
1221
1942
|
if (dead) return;
|
|
1222
1943
|
dead = true;
|
|
1944
|
+
stopTimers();
|
|
1223
1945
|
timers.forEach((id) => clearTimeout(id));
|
|
1224
1946
|
timers.clear();
|
|
1225
1947
|
clearTimeout(landing);
|
|
1948
|
+
clearTimeout(clearTimer);
|
|
1226
1949
|
flights.forEach((a) => a.cancel());
|
|
1227
1950
|
cancelAnimationFrame(raf);
|
|
1228
1951
|
cancelAnimationFrame(tw);
|
|
1229
1952
|
ro?.disconnect();
|
|
1953
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
1954
|
+
haptics.stop();
|
|
1230
1955
|
sfx.close();
|
|
1231
1956
|
container.innerHTML = "";
|
|
1232
1957
|
}
|
|
@@ -1236,11 +1961,12 @@ function createPackingBingo(container, options = {}) {
|
|
|
1236
1961
|
// src/react.tsx
|
|
1237
1962
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1238
1963
|
var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, ref) {
|
|
1239
|
-
const { checked, defaultChecked, defaultOrder, className, style, onChange, onBingo, onPerfect, messages, fonts: _fonts, ...config } = props;
|
|
1964
|
+
const { checked, defaultChecked, defaultOrder, className, style, onChange, onBingo, onPerfect, onModeChange, onFinish, mode, messages, fonts: _fonts, ...config } = props;
|
|
1240
1965
|
const host = (0, import_react.useRef)(null);
|
|
1241
1966
|
const inst = (0, import_react.useRef)(null);
|
|
1242
|
-
const callbacks = (0, import_react.useRef)({ onChange, onBingo, onPerfect });
|
|
1243
|
-
callbacks.current = { onChange, onBingo, onPerfect };
|
|
1967
|
+
const callbacks = (0, import_react.useRef)({ onChange, onBingo, onPerfect, onModeChange, onFinish });
|
|
1968
|
+
callbacks.current = { onChange, onBingo, onPerfect, onModeChange, onFinish };
|
|
1969
|
+
const latestMode = (0, import_react.useRef)(mode);
|
|
1244
1970
|
const messagesRef = (0, import_react.useRef)(messages);
|
|
1245
1971
|
messagesRef.current = messages;
|
|
1246
1972
|
const controlled = checked !== void 0;
|
|
@@ -1270,13 +1996,20 @@ var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, re
|
|
|
1270
1996
|
callbacks.current.onChange?.(next);
|
|
1271
1997
|
},
|
|
1272
1998
|
onBingo: (e) => callbacks.current.onBingo?.(e),
|
|
1273
|
-
onPerfect: (e) => callbacks.current.onPerfect?.(e)
|
|
1999
|
+
onPerfect: (e) => callbacks.current.onPerfect?.(e),
|
|
2000
|
+
onFinish: (e) => callbacks.current.onFinish?.(e),
|
|
2001
|
+
mode: latestMode.current,
|
|
2002
|
+
onModeChange: (m) => {
|
|
2003
|
+
latestMode.current = m;
|
|
2004
|
+
callbacks.current.onModeChange?.(m);
|
|
2005
|
+
}
|
|
1274
2006
|
});
|
|
1275
2007
|
inst.current = instance;
|
|
1276
2008
|
return () => {
|
|
1277
2009
|
const state = instance.getState();
|
|
1278
|
-
|
|
1279
|
-
|
|
2010
|
+
const packed = state.packing ?? state;
|
|
2011
|
+
if (!controlled) latest.current = packed.checked;
|
|
2012
|
+
latestOrder.current = packed.order;
|
|
1280
2013
|
latestKey.current = storageKey;
|
|
1281
2014
|
instance.destroy();
|
|
1282
2015
|
if (inst.current === instance) inst.current = null;
|
|
@@ -1288,11 +2021,16 @@ var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, re
|
|
|
1288
2021
|
(0, import_react.useEffect)(() => {
|
|
1289
2022
|
if (controlled && checked) inst.current?.setChecked(checked, { animate: true });
|
|
1290
2023
|
}, [controlled, checked]);
|
|
2024
|
+
(0, import_react.useEffect)(() => {
|
|
2025
|
+
if (mode) inst.current?.setMode(mode);
|
|
2026
|
+
}, [mode]);
|
|
1291
2027
|
(0, import_react.useImperativeHandle)(
|
|
1292
2028
|
ref,
|
|
1293
2029
|
() => ({
|
|
1294
2030
|
reset: () => inst.current?.reset(),
|
|
1295
2031
|
shuffle: () => inst.current?.shuffle(),
|
|
2032
|
+
setMode: (m) => inst.current?.setMode(m),
|
|
2033
|
+
finish: () => inst.current?.finish(),
|
|
1296
2034
|
setChecked: (next) => inst.current?.setChecked(next, { animate: true }),
|
|
1297
2035
|
getState: () => inst.current?.getState() ?? null
|
|
1298
2036
|
}),
|
|
@@ -1308,5 +2046,6 @@ var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, re
|
|
|
1308
2046
|
bingoLines,
|
|
1309
2047
|
createPackingBingo,
|
|
1310
2048
|
en,
|
|
1311
|
-
ja
|
|
2049
|
+
ja,
|
|
2050
|
+
speedBonus
|
|
1312
2051
|
});
|