packing-bingo 0.3.0 → 0.4.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 +57 -25
- package/dist/{chunk-Z34GMJW2.js → chunk-BAA2CFUA.js} +174 -41
- package/dist/core.cjs +174 -41
- package/dist/core.d.cts +36 -3
- package/dist/core.d.ts +36 -3
- package/dist/core.js +1 -1
- package/dist/index.cjs +181 -43
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +8 -3
- package/dist/packing-bingo.global.js +26 -20
- package/package.json +1 -1
|
@@ -92,7 +92,7 @@ function bingoLines(size, free = false) {
|
|
|
92
92
|
// src/messages.ts
|
|
93
93
|
var ja = {
|
|
94
94
|
title: "\u6301\u3061\u3082\u306E\u30D3\u30F3\u30B4",
|
|
95
|
-
tag: "
|
|
95
|
+
tag: "",
|
|
96
96
|
subtitle: "",
|
|
97
97
|
packed: "\u305D\u308D\u3063\u305F",
|
|
98
98
|
bingo: "BINGO",
|
|
@@ -100,6 +100,7 @@ var ja = {
|
|
|
100
100
|
combo: (n) => `${n} COMBO`,
|
|
101
101
|
comboPop: (n) => `${n} COMBO!`,
|
|
102
102
|
reset: "\u30EA\u30BB\u30C3\u30C8",
|
|
103
|
+
shuffle: "\u{1F500} \u30B7\u30E3\u30C3\u30D5\u30EB",
|
|
103
104
|
soundOn: "\u{1F50A} \u97F3\u3042\u308A",
|
|
104
105
|
soundOff: "\u{1F507} \u97F3\u306A\u3057",
|
|
105
106
|
stamp: "OK",
|
|
@@ -113,7 +114,7 @@ var ja = {
|
|
|
113
114
|
bannerMulti: (n) => `\u4E00\u6C17\u306B ${n} \u30E9\u30A4\u30F3\uFF01`,
|
|
114
115
|
statusStart: () => "",
|
|
115
116
|
statusLines: (lines, left) => `\u30D3\u30F3\u30B4 ${lines} \u30E9\u30A4\u30F3\uFF01 \u306E\u3053\u308A ${left} \u3053`,
|
|
116
|
-
statusReach: (
|
|
117
|
+
statusReach: () => "",
|
|
117
118
|
statusDone: "\u{1F389} \u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u51FA\u767A\u3067\u304D\u307E\u3059",
|
|
118
119
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
119
120
|
perfectStamp: "\u6E96\u5099\n\u5B8C\u4E86",
|
|
@@ -132,7 +133,7 @@ var ja = {
|
|
|
132
133
|
};
|
|
133
134
|
var en = {
|
|
134
135
|
title: "Packing Bingo",
|
|
135
|
-
tag: "
|
|
136
|
+
tag: "",
|
|
136
137
|
subtitle: "",
|
|
137
138
|
packed: "PACKED",
|
|
138
139
|
bingo: "BINGO",
|
|
@@ -140,6 +141,7 @@ var en = {
|
|
|
140
141
|
combo: (n) => `${n} COMBO`,
|
|
141
142
|
comboPop: (n) => `${n} COMBO!`,
|
|
142
143
|
reset: "Reset",
|
|
144
|
+
shuffle: "\u{1F500} Shuffle",
|
|
143
145
|
soundOn: "\u{1F50A} Sound on",
|
|
144
146
|
soundOff: "\u{1F507} Muted",
|
|
145
147
|
stamp: "OK",
|
|
@@ -153,7 +155,7 @@ var en = {
|
|
|
153
155
|
bannerMulti: (n) => `${n} lines at once!`,
|
|
154
156
|
statusStart: () => "",
|
|
155
157
|
statusLines: (lines, left) => `${lines} bingo line${lines === 1 ? "" : "s"}! ${left} to go`,
|
|
156
|
-
statusReach: (
|
|
158
|
+
statusReach: () => "",
|
|
157
159
|
statusDone: "\u{1F389} Perfect! Nothing left behind.",
|
|
158
160
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
159
161
|
perfectStamp: "ALL\nSET",
|
|
@@ -259,6 +261,11 @@ function createSound(isMuted) {
|
|
|
259
261
|
},
|
|
260
262
|
tick: () => tone(1760, 0, 0.03, "square", 0.03),
|
|
261
263
|
note: (k, at = 0) => tone(step(k), at, 0.12, "square", 0.05),
|
|
264
|
+
shuffle() {
|
|
265
|
+
for (let i = 0; i < 7; i++) noise(i * 0.042, 0.05, 0.16, 1600 + i * 260);
|
|
266
|
+
tone(hz(7), 0.34, 0.12, "triangle", 0.14);
|
|
267
|
+
tone(hz(12), 0.42, 0.2, "triangle", 0.14);
|
|
268
|
+
},
|
|
262
269
|
close() {
|
|
263
270
|
if (ac) void ac.close().catch(() => {
|
|
264
271
|
});
|
|
@@ -286,7 +293,6 @@ function itemsKey(items) {
|
|
|
286
293
|
}
|
|
287
294
|
|
|
288
295
|
// src/styles.ts
|
|
289
|
-
var FONT_URL = "https://fonts.googleapis.com/css2?family=Dela+Gothic+One&family=M+PLUS+Rounded+1c:wght@500;800&display=swap";
|
|
290
296
|
var CSS = `
|
|
291
297
|
.pkb{--pk-sun:var(--pkb-sun,#FFD54F);--pk-ink:var(--pkb-ink,#2A1F14);--pk-paper:var(--pkb-paper,#FFFDF6);--pk-red:var(--pkb-red,#E8473B);--pk-mint:var(--pkb-mint,#BDEBD2);--pk-display:"Dela Gothic One",var(--pkb-font-fallback,sans-serif);--pk-body:"M PLUS Rounded 1c","Hiragino Maru Gothic ProN",var(--pkb-font-fallback,sans-serif);--pk-n:4;--pk-s:1;--pk-d:1;container-type:inline-size;isolation:isolate;width:100%}
|
|
292
298
|
.pkb,.pkb *{box-sizing:border-box;margin:0;padding:0}
|
|
@@ -298,6 +304,7 @@ var CSS = `
|
|
|
298
304
|
.pkb-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px 16px;margin-bottom:18px}
|
|
299
305
|
.pkb-tags{display:flex;gap:8px;align-items:center}
|
|
300
306
|
.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
|
+
.pkb-kicker:empty{display:none}
|
|
301
308
|
.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}
|
|
302
309
|
@keyframes pkb-rb{to{background-position:-200% 0}}
|
|
303
310
|
.pkb button{font-family:var(--pk-body)}
|
|
@@ -354,8 +361,13 @@ var CSS = `
|
|
|
354
361
|
.pkb-lines .pkb-o{stroke:var(--pk-ink);stroke-width:calc(6px*var(--pk-d))}
|
|
355
362
|
.pkb-lines .pkb-draw line{animation:pkb-draw .45s ease-out .3s both}
|
|
356
363
|
@keyframes pkb-draw{from{stroke-dashoffset:1}}
|
|
357
|
-
.pkb-foot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:18px;font-weight:800;font-size:13px;word-break:auto-phrase}
|
|
358
|
-
.pkb-
|
|
364
|
+
.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
|
+
.pkb-foot>span{flex:1 1 14em}
|
|
366
|
+
.pkb-foot>span:empty{flex-basis:0}
|
|
367
|
+
.pkb-acts{display:flex;gap:8px;flex:none;margin-left:auto}
|
|
368
|
+
.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
|
+
.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}
|
|
359
371
|
.pkb-flash{position:absolute;inset:0;z-index:7;background:#fff;opacity:0;pointer-events:none}
|
|
360
372
|
.pkb-flash.pkb-go{animation:pkb-flash .4s ease-out}
|
|
361
373
|
@keyframes pkb-flash{0%{opacity:.8}100%{opacity:0}}
|
|
@@ -403,6 +415,7 @@ var CONFETTI = ["#E8473B", "#2F6FED", "#1FA971", "#FFFDF6", "#2A1F14", "#FF9F1C"
|
|
|
403
415
|
var COMBO_COLORS = ["#FF9F1C", "#1FA971", "#2F6FED", "#9B5DE5", "#E8473B"];
|
|
404
416
|
var COMBO_MS = 2500;
|
|
405
417
|
var MUTE_KEY = "packing-bingo-muted";
|
|
418
|
+
var ORDER_PREFIX = "packing-bingo-order:";
|
|
406
419
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
407
420
|
function readStore(key) {
|
|
408
421
|
try {
|
|
@@ -417,29 +430,25 @@ function writeStore(key, value) {
|
|
|
417
430
|
} catch {
|
|
418
431
|
}
|
|
419
432
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
l.id = "pkb-fonts";
|
|
430
|
-
l.rel = "stylesheet";
|
|
431
|
-
l.href = FONT_URL;
|
|
432
|
-
document.head.appendChild(l);
|
|
433
|
-
}
|
|
433
|
+
var glintDelay = (c, n) => `${+((Math.floor(c / n) + c % n) * 0.36 / Math.max(1, n - 1)).toFixed(3)}s`;
|
|
434
|
+
var lineKey = (l) => l.slice().sort((a, b) => a - b).join(",");
|
|
435
|
+
var FLY_MS = 560;
|
|
436
|
+
function ensureStyles() {
|
|
437
|
+
if (document.getElementById("pkb-styles")) return;
|
|
438
|
+
const s = document.createElement("style");
|
|
439
|
+
s.id = "pkb-styles";
|
|
440
|
+
s.textContent = CSS;
|
|
441
|
+
document.head.appendChild(s);
|
|
434
442
|
}
|
|
435
|
-
function template(L, M, demo) {
|
|
443
|
+
function template(L, M, { demo, shuffle, vibrate }) {
|
|
436
444
|
const n = L.size;
|
|
445
|
+
const input = vibrate ? `<input type="checkbox" switch role="checkbox">` : `<input type="checkbox">`;
|
|
437
446
|
const tiles = Array.from({ length: n * n }, (_, c) => {
|
|
438
|
-
const gd = `--pkb-gd:${
|
|
447
|
+
const gd = `--pkb-gd:${glintDelay(c, n)}`;
|
|
439
448
|
if (c === L.center)
|
|
440
449
|
return `<div class="pkb-tile pkb-free pkb-on" style="${gd}"><span class="pkb-stamp">${esc(M.free)}</span></div>`;
|
|
441
450
|
const it = L.items[L.itemCell.indexOf(c)];
|
|
442
|
-
return `<label class="pkb-tile" data-reach="${esc(M.reachBadge)}" style="${gd}"
|
|
451
|
+
return `<label class="pkb-tile" data-reach="${esc(M.reachBadge)}" style="${gd}">${input}<span class="pkb-emo" aria-hidden="true">${esc(it.emoji)}</span><span class="pkb-lbl">${esc(it.label).replace(/・/g, "\u30FB<wbr>")}</span><span class="pkb-stamp" aria-hidden="true">${esc(M.stamp)}</span></label>`;
|
|
443
452
|
}).join("");
|
|
444
453
|
const scale = `--pk-n:${n};--pk-s:${+Math.min(1.35, 4 / n).toFixed(3)};--pk-d:${+Math.min(1, 4 / n).toFixed(3)}`;
|
|
445
454
|
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>` : "";
|
|
@@ -454,7 +463,7 @@ function template(L, M, demo) {
|
|
|
454
463
|
<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>
|
|
455
464
|
<div class="pkb-gw"><div class="pkb-grid">${tiles}</div><svg class="pkb-lines" aria-hidden="true"></svg>
|
|
456
465
|
<div class="pkb-banner" data-r="banner" aria-hidden="true"><b data-r="bT"></b><span data-r="bS"></span></div></div>
|
|
457
|
-
<div class="pkb-foot"><span data-r="msg" aria-live="polite"></span><button type="button" class="pkb-reset" data-r="reset">${esc(M.reset)}</button></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>
|
|
458
467
|
<div class="pkb-flash" data-r="flash"></div>
|
|
459
468
|
<div class="pkb-perfect" data-r="perfect"><div class="pkb-card" role="dialog" aria-label="${esc(M.perfectTitle)}">
|
|
460
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>
|
|
@@ -465,12 +474,22 @@ function template(L, M, demo) {
|
|
|
465
474
|
var perfectText = (M, total) => typeof M.perfectBody === "function" ? M.perfectBody(total) : M.perfectBody;
|
|
466
475
|
function createPackingBingo(container, options = {}) {
|
|
467
476
|
const L = resolveLayout(options);
|
|
468
|
-
const { items,
|
|
477
|
+
const { items, cellLines } = L;
|
|
469
478
|
const n = L.size, N = items.length;
|
|
470
479
|
const normalize = (v) => Array.from({ length: N }, (_, i) => !!v?.[i]);
|
|
480
|
+
const identity = () => Array.from({ length: N }, (_, s) => s);
|
|
481
|
+
const isIdentity = (o) => o.every((v, s) => v === s);
|
|
482
|
+
const validOrder = (o) => Array.isArray(o) && o.length === N && o.every((v) => Number.isInteger(v) && v >= 0 && v < N) && new Set(o).size === N;
|
|
483
|
+
let order = identity();
|
|
484
|
+
const slotOf = identity();
|
|
485
|
+
let lines = L.lines;
|
|
471
486
|
const demoCells = [...cellLines[0], ...cellLines[2 * n].slice(1, -1)];
|
|
472
|
-
const
|
|
473
|
-
const demoStart = () =>
|
|
487
|
+
const demoSlots = L.itemCell.flatMap((c, s) => demoCells.includes(c) ? [s] : []);
|
|
488
|
+
const demoStart = () => {
|
|
489
|
+
const on = normalize(void 0);
|
|
490
|
+
demoSlots.forEach((s) => on[order[s]] = true);
|
|
491
|
+
return on;
|
|
492
|
+
};
|
|
474
493
|
const far = (c) => Math.floor(c / n) + c % n;
|
|
475
494
|
const dScale = Math.min(1, 4 / n);
|
|
476
495
|
const M = { ...ja, ...options.messages };
|
|
@@ -479,7 +498,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
479
498
|
if (options.subtitle != null) M.subtitle = options.subtitle;
|
|
480
499
|
const controlled = !!options.controlled;
|
|
481
500
|
const storageKey = resolveStorageKey(options);
|
|
501
|
+
const orderKey = storageKey && ORDER_PREFIX + storageKey;
|
|
482
502
|
const demo = !!options.demo;
|
|
503
|
+
const vibrate = options.vibrate !== false;
|
|
483
504
|
const calm = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
484
505
|
let dead = false;
|
|
485
506
|
const timers = /* @__PURE__ */ new Set();
|
|
@@ -492,8 +513,8 @@ function createPackingBingo(container, options = {}) {
|
|
|
492
513
|
};
|
|
493
514
|
const wait = (ms) => new Promise((r) => window.setTimeout(r, ms));
|
|
494
515
|
let gen = 0;
|
|
495
|
-
|
|
496
|
-
container.innerHTML = template(L, M, demo);
|
|
516
|
+
ensureStyles();
|
|
517
|
+
container.innerHTML = template(L, M, { demo, shuffle: options.shuffle !== false, vibrate });
|
|
497
518
|
const $ = (r) => {
|
|
498
519
|
const el = container.querySelector(`[data-r="${r}"]`);
|
|
499
520
|
if (!el) throw new Error(`[packing-bingo] internal: missing element "${r}"`);
|
|
@@ -670,6 +691,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
670
691
|
r.addEventListener("animationend", () => r.remove());
|
|
671
692
|
};
|
|
672
693
|
const buzz = (pattern) => {
|
|
694
|
+
if (!vibrate) return;
|
|
673
695
|
const ua = navigator.userActivation;
|
|
674
696
|
if (ua && !ua.hasBeenActive) return;
|
|
675
697
|
try {
|
|
@@ -728,7 +750,12 @@ function createPackingBingo(container, options = {}) {
|
|
|
728
750
|
const ctr = (t) => {
|
|
729
751
|
return { x: t.offsetLeft + t.offsetWidth / 2, y: t.offsetTop + t.offsetHeight / 2 };
|
|
730
752
|
};
|
|
753
|
+
let landing = 0;
|
|
731
754
|
function drawLines(fresh = []) {
|
|
755
|
+
if (landing) {
|
|
756
|
+
svg.innerHTML = "";
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
732
759
|
svg.setAttribute("viewBox", `0 0 ${gw.clientWidth} ${gw.clientHeight}`);
|
|
733
760
|
svg.innerHTML = cur.map((k) => {
|
|
734
761
|
const a = ctr(cellTiles[cellLines[k][0]]), b = ctr(cellTiles[cellLines[k][n - 1]]);
|
|
@@ -743,6 +770,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
743
770
|
combo = 0;
|
|
744
771
|
$("combo").classList.remove("pkb-on");
|
|
745
772
|
sfx.off();
|
|
773
|
+
buzz(8);
|
|
746
774
|
return;
|
|
747
775
|
}
|
|
748
776
|
const now = performance.now();
|
|
@@ -765,7 +793,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
765
793
|
$("cmbN").textContent = M.combo(combo);
|
|
766
794
|
replay($("combo"), "pkb-on");
|
|
767
795
|
}
|
|
768
|
-
buzz(
|
|
796
|
+
buzz(18);
|
|
769
797
|
}
|
|
770
798
|
$("cbar").addEventListener("animationend", () => {
|
|
771
799
|
$("combo").classList.remove("pkb-on");
|
|
@@ -859,10 +887,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
859
887
|
$("combo").classList.remove("pkb-on");
|
|
860
888
|
perfectEl.classList.remove("pkb-show");
|
|
861
889
|
}
|
|
890
|
+
const completeLines = () => lines.flatMap((l, k) => l.every((i) => checked[i]) ? [k] : []);
|
|
862
891
|
function render(src, quiet) {
|
|
863
892
|
const events = [];
|
|
864
893
|
const on = checked, c = on.filter(Boolean).length;
|
|
865
|
-
cur =
|
|
894
|
+
cur = completeLines();
|
|
866
895
|
const fresh = cur.filter((k) => !prevLines.has(k));
|
|
867
896
|
if ([...prevLines].some((k) => !cur.includes(k))) {
|
|
868
897
|
cancelFx();
|
|
@@ -876,7 +905,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
876
905
|
}
|
|
877
906
|
tiles.forEach((t, i) => {
|
|
878
907
|
t.classList.toggle("pkb-on", on[i]);
|
|
879
|
-
t.classList.toggle("pkb-line", hit.has(L.itemCell[i]));
|
|
908
|
+
t.classList.toggle("pkb-line", hit.has(L.itemCell[slotOf[i]]));
|
|
880
909
|
t.classList.toggle("pkb-reach", reach.has(i));
|
|
881
910
|
});
|
|
882
911
|
freeTile?.classList.toggle("pkb-line", hit.has(L.center));
|
|
@@ -894,8 +923,8 @@ function createPackingBingo(container, options = {}) {
|
|
|
894
923
|
lastN = cur.length;
|
|
895
924
|
drawLines(fresh);
|
|
896
925
|
if (src !== null) tapFx(src, on[src]);
|
|
897
|
-
const newL = fresh.
|
|
898
|
-
newL.forEach((
|
|
926
|
+
const newL = fresh.map((k) => lineKey(lines[k])).filter((key) => !gotL.has(key));
|
|
927
|
+
newL.forEach((key) => gotL.add(key));
|
|
899
928
|
const linePts = 100 * newL.length * newL.length;
|
|
900
929
|
score += linePts;
|
|
901
930
|
if (quiet) {
|
|
@@ -940,8 +969,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
940
969
|
got.clear();
|
|
941
970
|
gotL.clear();
|
|
942
971
|
checked.forEach((v, i) => v && got.add(i));
|
|
943
|
-
|
|
944
|
-
|
|
972
|
+
const done = completeLines();
|
|
973
|
+
done.forEach((k) => gotL.add(lineKey(lines[k])));
|
|
974
|
+
prevLines = new Set(done);
|
|
945
975
|
prevReach = /* @__PURE__ */ new Set();
|
|
946
976
|
prevAll = checked.every(Boolean);
|
|
947
977
|
gotP = prevAll;
|
|
@@ -964,6 +994,77 @@ function createPackingBingo(container, options = {}) {
|
|
|
964
994
|
options.onChange?.(checked.slice());
|
|
965
995
|
flush(events);
|
|
966
996
|
}
|
|
997
|
+
function place(next) {
|
|
998
|
+
order = next;
|
|
999
|
+
order.forEach((i, s) => {
|
|
1000
|
+
slotOf[i] = s;
|
|
1001
|
+
cellTiles[L.itemCell[s]] = tiles[i];
|
|
1002
|
+
});
|
|
1003
|
+
lines = L.lines.map((l) => l.map((s) => order[s]));
|
|
1004
|
+
const focused = document.activeElement;
|
|
1005
|
+
cellTiles.forEach((t, c) => {
|
|
1006
|
+
t.style.setProperty("--pkb-gd", glintDelay(c, n));
|
|
1007
|
+
grid.appendChild(t);
|
|
1008
|
+
});
|
|
1009
|
+
if (focused instanceof HTMLElement && grid.contains(focused) && document.activeElement !== focused)
|
|
1010
|
+
focused.focus({ preventScroll: true });
|
|
1011
|
+
}
|
|
1012
|
+
const saveOrder = () => {
|
|
1013
|
+
if (!orderKey) return;
|
|
1014
|
+
writeStore(orderKey, JSON.stringify(order));
|
|
1015
|
+
};
|
|
1016
|
+
let flights = [];
|
|
1017
|
+
function arrange(next) {
|
|
1018
|
+
if (!checked.every(Boolean)) cancelFx();
|
|
1019
|
+
const from = calm ? null : tiles.map((t) => t.getBoundingClientRect());
|
|
1020
|
+
flights.forEach((a) => a.cancel());
|
|
1021
|
+
flights = [];
|
|
1022
|
+
place(next);
|
|
1023
|
+
lines.forEach((l) => {
|
|
1024
|
+
if (l.every((i) => got.has(i))) gotL.add(lineKey(l));
|
|
1025
|
+
});
|
|
1026
|
+
const done = completeLines();
|
|
1027
|
+
done.forEach((k) => gotL.add(lineKey(lines[k])));
|
|
1028
|
+
prevLines = new Set(done);
|
|
1029
|
+
render(null, true);
|
|
1030
|
+
saveOrder();
|
|
1031
|
+
if (!from || typeof Element.prototype.animate !== "function") return;
|
|
1032
|
+
tiles.forEach((t, i) => {
|
|
1033
|
+
const a = from[i], b = t.getBoundingClientRect();
|
|
1034
|
+
const dx = a.left - b.left, dy = a.top - b.top;
|
|
1035
|
+
if (Math.abs(dx) + Math.abs(dy) < 1) return;
|
|
1036
|
+
flights.push(
|
|
1037
|
+
t.animate([{ transform: `translate(${dx}px,${dy}px)` }, { transform: "none" }], {
|
|
1038
|
+
duration: FLY_MS - 140,
|
|
1039
|
+
delay: Math.random() * 140,
|
|
1040
|
+
easing: "cubic-bezier(.3,1.25,.5,1)",
|
|
1041
|
+
fill: "backwards"
|
|
1042
|
+
})
|
|
1043
|
+
);
|
|
1044
|
+
});
|
|
1045
|
+
clearTimeout(landing);
|
|
1046
|
+
landing = window.setTimeout(() => {
|
|
1047
|
+
landing = 0;
|
|
1048
|
+
flights = [];
|
|
1049
|
+
if (!dead) drawLines(cur);
|
|
1050
|
+
}, FLY_MS);
|
|
1051
|
+
svg.innerHTML = "";
|
|
1052
|
+
}
|
|
1053
|
+
function shuffleCard() {
|
|
1054
|
+
let next = order.slice();
|
|
1055
|
+
do {
|
|
1056
|
+
for (let s = N - 1; s > 0; s--) {
|
|
1057
|
+
const r = Math.floor(Math.random() * (s + 1));
|
|
1058
|
+
[next[s], next[r]] = [next[r], next[s]];
|
|
1059
|
+
}
|
|
1060
|
+
} while (next.every((v, s) => v === order[s]));
|
|
1061
|
+
arrange(next);
|
|
1062
|
+
sfx.shuffle();
|
|
1063
|
+
buzz([10, 40, 10, 40, 10, 40, 24]);
|
|
1064
|
+
}
|
|
1065
|
+
const unshuffle = () => {
|
|
1066
|
+
if (!isIdentity(order)) arrange(identity());
|
|
1067
|
+
};
|
|
967
1068
|
grid.addEventListener("change", (e) => {
|
|
968
1069
|
const i = ins.indexOf(e.target);
|
|
969
1070
|
if (i < 0) return;
|
|
@@ -971,7 +1072,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
971
1072
|
next[i] = !next[i];
|
|
972
1073
|
request(next, i);
|
|
973
1074
|
});
|
|
974
|
-
|
|
1075
|
+
container.querySelector('[data-r="shuf"]')?.addEventListener("click", shuffleCard);
|
|
1076
|
+
$("reset").addEventListener("click", () => {
|
|
1077
|
+
unshuffle();
|
|
1078
|
+
request(normalize(void 0), null, { reset: true });
|
|
1079
|
+
});
|
|
975
1080
|
$("close").addEventListener("click", () => perfectEl.classList.remove("pkb-show"));
|
|
976
1081
|
perfectEl.addEventListener("click", (e) => {
|
|
977
1082
|
if (e.target === perfectEl) perfectEl.classList.remove("pkb-show");
|
|
@@ -1010,9 +1115,21 @@ function createPackingBingo(container, options = {}) {
|
|
|
1010
1115
|
void run(miss.map((i, k) => k < miss.length - 1 ? { i, delay: 170, quiet: true } : { i, delay: 450 }));
|
|
1011
1116
|
});
|
|
1012
1117
|
$("dR").addEventListener("click", () => {
|
|
1013
|
-
if (
|
|
1118
|
+
if (busy) return;
|
|
1119
|
+
unshuffle();
|
|
1120
|
+
request(demoStart(), null, { reset: true });
|
|
1014
1121
|
});
|
|
1015
1122
|
}
|
|
1123
|
+
let startOrder = null;
|
|
1124
|
+
if (orderKey) {
|
|
1125
|
+
try {
|
|
1126
|
+
const saved = JSON.parse(readStore(orderKey) ?? "null");
|
|
1127
|
+
if (validOrder(saved)) startOrder = saved;
|
|
1128
|
+
} catch {
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
if (!startOrder && validOrder(options.defaultOrder)) startOrder = options.defaultOrder.slice();
|
|
1132
|
+
if (startOrder && !isIdentity(startOrder)) place(startOrder);
|
|
1016
1133
|
let initial = null;
|
|
1017
1134
|
if (storageKey) {
|
|
1018
1135
|
try {
|
|
@@ -1044,19 +1161,35 @@ function createPackingBingo(container, options = {}) {
|
|
|
1044
1161
|
flush(apply(n2, changed[0], false));
|
|
1045
1162
|
},
|
|
1046
1163
|
reset() {
|
|
1047
|
-
if (
|
|
1164
|
+
if (dead) return;
|
|
1165
|
+
unshuffle();
|
|
1166
|
+
request(normalize(void 0), null, { reset: true });
|
|
1167
|
+
},
|
|
1168
|
+
shuffle() {
|
|
1169
|
+
if (!dead) shuffleCard();
|
|
1048
1170
|
},
|
|
1049
1171
|
refresh() {
|
|
1050
1172
|
if (!dead) render(null, true);
|
|
1051
1173
|
},
|
|
1052
1174
|
getState() {
|
|
1053
|
-
return {
|
|
1175
|
+
return {
|
|
1176
|
+
checked: checked.slice(),
|
|
1177
|
+
lines: cur.slice(),
|
|
1178
|
+
score,
|
|
1179
|
+
maxCombo,
|
|
1180
|
+
complete: checked.every(Boolean),
|
|
1181
|
+
size: n,
|
|
1182
|
+
free: L.free,
|
|
1183
|
+
order: order.slice()
|
|
1184
|
+
};
|
|
1054
1185
|
},
|
|
1055
1186
|
destroy() {
|
|
1056
1187
|
if (dead) return;
|
|
1057
1188
|
dead = true;
|
|
1058
1189
|
timers.forEach((id) => clearTimeout(id));
|
|
1059
1190
|
timers.clear();
|
|
1191
|
+
clearTimeout(landing);
|
|
1192
|
+
flights.forEach((a) => a.cancel());
|
|
1060
1193
|
cancelAnimationFrame(raf);
|
|
1061
1194
|
cancelAnimationFrame(tw);
|
|
1062
1195
|
ro?.disconnect();
|