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