packing-bingo 0.2.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 +180 -103
- package/dist/{chunk-WGZKPODU.js → chunk-BAA2CFUA.js} +352 -137
- package/dist/core.cjs +353 -137
- package/dist/core.d.cts +82 -13
- package/dist/core.d.ts +82 -13
- package/dist/core.js +3 -1
- package/dist/index.cjs +360 -139
- package/dist/index.d.cts +7 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +10 -3
- package/dist/packing-bingo.global.js +43 -33
- package/package.json +2 -2
|
@@ -2,38 +2,109 @@
|
|
|
2
2
|
|
|
3
3
|
// src/items.ts
|
|
4
4
|
var DEFAULT_ITEMS = [
|
|
5
|
-
{ emoji: "\u{
|
|
6
|
-
{ emoji: "\u{
|
|
7
|
-
{ emoji: "\u{
|
|
8
|
-
{ emoji: "\u{
|
|
9
|
-
{ emoji: "\u{
|
|
10
|
-
{ emoji: "\u{
|
|
11
|
-
{ emoji: "\u{1F9E5}", label: "\u4E0A\u7740" },
|
|
12
|
-
{ emoji: "\u{1FAA5}", label: "\u6D17\u9762\u7528\u5177" },
|
|
13
|
-
{ emoji: "\u{1F6C1}", label: "\u30BF\u30AA\u30EB" },
|
|
14
|
-
{ emoji: "\u{1F927}", label: "\u30CF\u30F3\u30AB\u30C1\u30FB\u30C6\u30A3\u30C3\u30B7\u30E5" },
|
|
5
|
+
{ emoji: "\u{1F45B}", label: "\u8CA1\u5E03" },
|
|
6
|
+
{ emoji: "\u{1F4F1}", label: "\u30B9\u30DE\u30DB" },
|
|
7
|
+
{ emoji: "\u{1F50B}", label: "\u30E2\u30D0\u30A4\u30EB\u200B\u5145\u96FB\u5668" },
|
|
8
|
+
{ emoji: "\u{1F4CB}", label: "\u5065\u5EB7\u8ABF\u67FB\u7968" },
|
|
9
|
+
{ emoji: "\u{1FAAA}", label: "\u30DE\u30A4\u30CA\u30AB\u30FC\u30C9\u200B\u307E\u305F\u306F\u4FDD\u967A\u8A3C" },
|
|
10
|
+
{ emoji: "\u{1F4D6}", label: "\u3057\u304A\u308A" },
|
|
15
11
|
{ emoji: "\u{1F637}", label: "\u30DE\u30B9\u30AF" },
|
|
16
|
-
{ emoji: "\u{
|
|
17
|
-
{ emoji: "\
|
|
18
|
-
{ emoji: "\u270F\uFE0F", label: "\u7B46\u8A18\u7528\u5177" },
|
|
12
|
+
{ emoji: "\u{1F927}", label: "\u30CF\u30F3\u30AB\u30C1" },
|
|
13
|
+
{ emoji: "\u{1F48A}", label: "\u5E38\u5099\u85AC" },
|
|
19
14
|
{ emoji: "\u{1F6CD}\uFE0F", label: "\u30D3\u30CB\u30FC\u30EB\u888B" },
|
|
20
|
-
{ emoji: "\u{
|
|
15
|
+
{ emoji: "\u{1F9E4}", label: "\u8ECD\u624B\uFF08\u5B66\u6821\u652F\u7D66\uFF09" },
|
|
16
|
+
{ emoji: "\u{1F526}", label: "\u30D8\u30C3\u30C9\u30E9\u30A4\u30C8" },
|
|
17
|
+
{ emoji: "\u{1F455}", label: "\u30B8\u30E3\u30FC\u30B8" },
|
|
18
|
+
{ emoji: "\u2614", label: "\u96E8\u5177" },
|
|
19
|
+
{ emoji: "\u{1F6C1}", label: "\u30BF\u30AA\u30EB" },
|
|
20
|
+
{ emoji: "\u{1F9F4}", label: "\u65E5\u713C\u3051\u6B62\u3081" }
|
|
21
21
|
];
|
|
22
22
|
|
|
23
|
+
// src/layout.ts
|
|
24
|
+
var MIN_SIZE = 2;
|
|
25
|
+
var MAX_SIZE = 10;
|
|
26
|
+
var fail = (msg) => {
|
|
27
|
+
throw new Error(`[packing-bingo] ${msg}`);
|
|
28
|
+
};
|
|
29
|
+
function layoutItems(options) {
|
|
30
|
+
if (options.items) return options.items;
|
|
31
|
+
const n = options.size;
|
|
32
|
+
if (n == null || !Number.isInteger(n) || n < MIN_SIZE) return DEFAULT_ITEMS;
|
|
33
|
+
return DEFAULT_ITEMS.slice(0, n * n - (options.free ? 1 : 0));
|
|
34
|
+
}
|
|
35
|
+
function checkSize(size, free) {
|
|
36
|
+
if (!Number.isInteger(size) || size < MIN_SIZE || size > MAX_SIZE)
|
|
37
|
+
fail(`"size" must be a whole number from ${MIN_SIZE} to ${MAX_SIZE} (got ${size}).`);
|
|
38
|
+
if (free && size % 2 === 0) fail(`"free" needs an odd size such as 3 or 5, so that the card has a center (got ${size}).`);
|
|
39
|
+
}
|
|
40
|
+
function shape(n, free) {
|
|
41
|
+
const seq = (f) => Array.from({ length: n }, (_, k) => f(k));
|
|
42
|
+
const cellLines = [
|
|
43
|
+
...seq((r) => seq((c) => r * n + c)),
|
|
44
|
+
// rows
|
|
45
|
+
...seq((c) => seq((r) => r * n + c)),
|
|
46
|
+
// columns
|
|
47
|
+
seq((k) => k * (n + 1)),
|
|
48
|
+
// top left to bottom right
|
|
49
|
+
seq((k) => (k + 1) * (n - 1))
|
|
50
|
+
// top right to bottom left
|
|
51
|
+
];
|
|
52
|
+
const center = free ? (n * n - 1) / 2 : -1;
|
|
53
|
+
const toItem = (c) => free && c > center ? c - 1 : c;
|
|
54
|
+
return { center, cellLines, lines: cellLines.map((l) => l.filter((c) => c !== center).map(toItem)) };
|
|
55
|
+
}
|
|
56
|
+
function resolveLayout(options) {
|
|
57
|
+
const items = layoutItems(options);
|
|
58
|
+
const free = !!options.free;
|
|
59
|
+
let size = options.size;
|
|
60
|
+
if (size == null) {
|
|
61
|
+
const len = items.length;
|
|
62
|
+
const sideOf = (cells) => {
|
|
63
|
+
const r = Math.round(Math.sqrt(cells));
|
|
64
|
+
return r * r === cells && r >= MIN_SIZE && r <= MAX_SIZE ? r : 0;
|
|
65
|
+
};
|
|
66
|
+
size = sideOf(len + (free ? 1 : 0));
|
|
67
|
+
if (!size) {
|
|
68
|
+
const other = sideOf(len + (free ? 0 : 1));
|
|
69
|
+
const hint = free ? other ? `; ${len} items fill a ${other}\xD7${other} card without "free"` : "" : other % 2 ? `, or set "free" for a ${other}\xD7${other} card with a FREE center` : "";
|
|
70
|
+
fail(
|
|
71
|
+
free ? `With "free", "items" must have one less than an odd square number of entries (8, 24, 48, 80)${hint} (got ${len}).` : `"items" must have a square number of entries (4, 9, 16, 25, \u2026, 100)${hint} (got ${len}).`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
checkSize(size, free);
|
|
76
|
+
const want = size * size - (free ? 1 : 0);
|
|
77
|
+
if (items.length !== want) {
|
|
78
|
+
const card = `a ${size}\xD7${size} card${free ? " with a FREE center" : ""}`;
|
|
79
|
+
fail(
|
|
80
|
+
options.items ? `"items" must have exactly ${want} entries for ${card} (got ${items.length}).` : `A ${card.slice(2)} needs ${want} items, but DEFAULT_ITEMS has only ${DEFAULT_ITEMS.length}. Pass "items".`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const { center, cellLines, lines } = shape(size, free);
|
|
84
|
+
const itemCell = items.map((_, i) => free && i >= center ? i + 1 : i);
|
|
85
|
+
return { size, free, items, itemCell, center, cellLines, lines };
|
|
86
|
+
}
|
|
87
|
+
function bingoLines(size, free = false) {
|
|
88
|
+
checkSize(size, free);
|
|
89
|
+
return shape(size, free).lines;
|
|
90
|
+
}
|
|
91
|
+
|
|
23
92
|
// src/messages.ts
|
|
24
93
|
var ja = {
|
|
25
94
|
title: "\u6301\u3061\u3082\u306E\u30D3\u30F3\u30B4",
|
|
26
|
-
tag: "
|
|
27
|
-
subtitle: "
|
|
95
|
+
tag: "",
|
|
96
|
+
subtitle: "",
|
|
28
97
|
packed: "\u305D\u308D\u3063\u305F",
|
|
29
98
|
bingo: "BINGO",
|
|
30
99
|
score: "SCORE",
|
|
31
100
|
combo: (n) => `${n} COMBO`,
|
|
32
101
|
comboPop: (n) => `${n} COMBO!`,
|
|
33
102
|
reset: "\u30EA\u30BB\u30C3\u30C8",
|
|
103
|
+
shuffle: "\u{1F500} \u30B7\u30E3\u30C3\u30D5\u30EB",
|
|
34
104
|
soundOn: "\u{1F50A} \u97F3\u3042\u308A",
|
|
35
105
|
soundOff: "\u{1F507} \u97F3\u306A\u3057",
|
|
36
106
|
stamp: "OK",
|
|
107
|
+
free: "FREE",
|
|
37
108
|
reachBadge: "\u30EA\u30FC\u30C1",
|
|
38
109
|
reachPop: "\u30EA\u30FC\u30C1!",
|
|
39
110
|
bannerSingle: "BINGO!",
|
|
@@ -41,14 +112,15 @@ var ja = {
|
|
|
41
112
|
bannerTriple: "\u30C8\u30EA\u30D7\u30EB\u30D3\u30F3\u30B4!",
|
|
42
113
|
bannerNth: (n) => `${n} \u30E9\u30A4\u30F3\u76EE\uFF01`,
|
|
43
114
|
bannerMulti: (n) => `\u4E00\u6C17\u306B ${n} \u30E9\u30A4\u30F3\uFF01`,
|
|
44
|
-
statusStart: (
|
|
115
|
+
statusStart: () => "",
|
|
45
116
|
statusLines: (lines, left) => `\u30D3\u30F3\u30B4 ${lines} \u30E9\u30A4\u30F3\uFF01 \u306E\u3053\u308A ${left} \u3053`,
|
|
46
|
-
statusReach: (
|
|
117
|
+
statusReach: () => "",
|
|
47
118
|
statusDone: "\u{1F389} \u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u51FA\u767A\u3067\u304D\u307E\u3059",
|
|
48
119
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
49
120
|
perfectStamp: "\u6E96\u5099\n\u5B8C\u4E86",
|
|
50
121
|
perfectTitle: "\u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01",
|
|
51
|
-
perfectBody:
|
|
122
|
+
perfectBody: (n) => `${n}\u3053 \u305C\u3093\u3076\u305D\u308D\u3063\u305F\uFF01
|
|
123
|
+
\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u3001\u3044\u3063\u3066\u3089\u3063\u3057\u3083\u3044\uFF01`,
|
|
52
124
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
53
125
|
statScore: "SCORE",
|
|
54
126
|
statCombo: "MAX COMBO",
|
|
@@ -61,17 +133,19 @@ var ja = {
|
|
|
61
133
|
};
|
|
62
134
|
var en = {
|
|
63
135
|
title: "Packing Bingo",
|
|
64
|
-
tag: "
|
|
65
|
-
subtitle: "
|
|
136
|
+
tag: "",
|
|
137
|
+
subtitle: "",
|
|
66
138
|
packed: "PACKED",
|
|
67
139
|
bingo: "BINGO",
|
|
68
140
|
score: "SCORE",
|
|
69
141
|
combo: (n) => `${n} COMBO`,
|
|
70
142
|
comboPop: (n) => `${n} COMBO!`,
|
|
71
143
|
reset: "Reset",
|
|
144
|
+
shuffle: "\u{1F500} Shuffle",
|
|
72
145
|
soundOn: "\u{1F50A} Sound on",
|
|
73
146
|
soundOff: "\u{1F507} Muted",
|
|
74
147
|
stamp: "OK",
|
|
148
|
+
free: "FREE",
|
|
75
149
|
reachBadge: "REACH",
|
|
76
150
|
reachPop: "Reach!",
|
|
77
151
|
bannerSingle: "BINGO!",
|
|
@@ -79,14 +153,15 @@ var en = {
|
|
|
79
153
|
bannerTriple: "TRIPLE BINGO!",
|
|
80
154
|
bannerNth: (n) => `Line ${n}!`,
|
|
81
155
|
bannerMulti: (n) => `${n} lines at once!`,
|
|
82
|
-
statusStart: (
|
|
156
|
+
statusStart: () => "",
|
|
83
157
|
statusLines: (lines, left) => `${lines} bingo line${lines === 1 ? "" : "s"}! ${left} to go`,
|
|
84
|
-
statusReach: (
|
|
158
|
+
statusReach: () => "",
|
|
85
159
|
statusDone: "\u{1F389} Perfect! Nothing left behind.",
|
|
86
160
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
87
161
|
perfectStamp: "ALL\nSET",
|
|
88
162
|
perfectTitle: "Perfect!",
|
|
89
|
-
perfectBody:
|
|
163
|
+
perfectBody: (n) => `All ${n} packed!
|
|
164
|
+
Have a great trip!`,
|
|
90
165
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
91
166
|
statScore: "SCORE",
|
|
92
167
|
statCombo: "MAX COMBO",
|
|
@@ -186,6 +261,11 @@ function createSound(isMuted) {
|
|
|
186
261
|
},
|
|
187
262
|
tick: () => tone(1760, 0, 0.03, "square", 0.03),
|
|
188
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
|
+
},
|
|
189
269
|
close() {
|
|
190
270
|
if (ac) void ac.close().catch(() => {
|
|
191
271
|
});
|
|
@@ -200,7 +280,7 @@ function resolveStorageKey(options) {
|
|
|
200
280
|
if (options.controlled) return null;
|
|
201
281
|
if (options.storageKey !== void 0) return options.storageKey || null;
|
|
202
282
|
if (options.demo) return null;
|
|
203
|
-
return itemsKey(options
|
|
283
|
+
return itemsKey(layoutItems(options));
|
|
204
284
|
}
|
|
205
285
|
function itemsKey(items) {
|
|
206
286
|
const text = items.map((it) => `${it.emoji}\0${it.label}`).join("");
|
|
@@ -213,24 +293,24 @@ function itemsKey(items) {
|
|
|
213
293
|
}
|
|
214
294
|
|
|
215
295
|
// src/styles.ts
|
|
216
|
-
var FONT_URL = "https://fonts.googleapis.com/css2?family=Dela+Gothic+One&family=M+PLUS+Rounded+1c:wght@500;800&display=swap";
|
|
217
296
|
var CSS = `
|
|
218
|
-
.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);container-type:inline-size;isolation:isolate;width:100%}
|
|
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%}
|
|
219
298
|
.pkb,.pkb *{box-sizing:border-box;margin:0;padding:0}
|
|
220
299
|
.pkb-board{position:relative;overflow:hidden;max-width:720px;margin:0 auto;padding:22px 26px 20px;border-radius:24px;background-color:var(--pk-sun);background-image:radial-gradient(rgba(42,31,20,.12) 1.3px,transparent 1.6px);background-size:16px 16px;border:2.5px solid var(--pk-ink);color:var(--pk-ink);font-family:var(--pk-body);line-height:1.4;text-align:left}
|
|
221
300
|
.pkb-board.pkb-shake{animation:pkb-shake .45s cubic-bezier(.36,.07,.19,.97)}
|
|
222
301
|
@keyframes pkb-shake{10%,90%{transform:translate(-1px,1px)}20%,80%{transform:translate(3px,-2px)}30%,50%,70%{transform:translate(-6px,2px)}40%,60%{transform:translate(6px,-1px)}}
|
|
223
302
|
.pkb-board.pkb-punch{animation:pkb-punch .4s cubic-bezier(.3,1.6,.5,1)}
|
|
224
303
|
@keyframes pkb-punch{30%{transform:scale(1.018)}}
|
|
225
|
-
.pkb-head{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-bottom:18px}
|
|
304
|
+
.pkb-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px 16px;margin-bottom:18px}
|
|
226
305
|
.pkb-tags{display:flex;gap:8px;align-items:center}
|
|
227
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}
|
|
228
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}
|
|
229
309
|
@keyframes pkb-rb{to{background-position:-200% 0}}
|
|
230
310
|
.pkb button{font-family:var(--pk-body)}
|
|
231
311
|
.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}
|
|
232
312
|
.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}
|
|
233
|
-
.pkb-sub{font-weight:800;font-size:13px;margin-top:4px}
|
|
313
|
+
.pkb-sub{font-weight:800;font-size:13px;margin-top:4px;word-break:auto-phrase}
|
|
234
314
|
.pkb-side{display:flex;flex-direction:column;gap:8px;flex:none}
|
|
235
315
|
.pkb-score{display:flex;gap:10px}
|
|
236
316
|
.pkb-pill{min-width:78px;background:var(--pk-paper);border:2.5px solid var(--pk-ink);border-radius:14px;padding:6px 10px 5px;text-align:center;box-shadow:3px 3px 0 var(--pk-ink)}
|
|
@@ -249,16 +329,18 @@ var CSS = `
|
|
|
249
329
|
.pkb-combo.pkb-on .pkb-bar i{animation:pkb-drain 2.5s linear forwards}
|
|
250
330
|
@keyframes pkb-drain{from{transform:scaleX(1)}to{transform:scaleX(0)}}
|
|
251
331
|
.pkb-gw{position:relative}
|
|
252
|
-
.pkb-grid{display:grid;grid-template-columns:repeat(
|
|
253
|
-
.pkb-tile{position:relative;aspect-ratio:1/.82;background:var(--pk-paper);border:2.5px solid var(--pk-ink);border-radius:16px;box-shadow:4px 4px 0 var(--pk-ink);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;cursor:pointer;user-select:none;-webkit-tap-highlight-color:transparent;transition:transform .1s,box-shadow .1s,background-color .25s}
|
|
254
|
-
.pkb-tile:active{transform:translate(3px,3px);box-shadow:1px 1px 0 var(--pk-ink)}
|
|
332
|
+
.pkb-grid{display:grid;grid-template-columns:repeat(var(--pk-n),minmax(0,1fr));gap:calc(12px*var(--pk-d))}
|
|
333
|
+
.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)}
|
|
255
335
|
.pkb-tile input{position:absolute;opacity:0;pointer-events:none;width:1px;height:1px}
|
|
256
336
|
.pkb-tile:has(input:focus-visible){outline:3px solid var(--pk-ink);outline-offset:3px}
|
|
257
|
-
.pkb-emo{font-size:32px;line-height:1;transition:opacity .2s}
|
|
258
|
-
.pkb-lbl{font-weight:800;font-size:13px;line-height:1.25;text-align:center;padding:0 6px;transition:opacity .2s;word-break:keep-all;overflow-wrap:anywhere}
|
|
259
|
-
.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:20px;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}
|
|
337
|
+
.pkb-emo{font-size:max(14px,calc(32px*var(--pk-s)));line-height:1;transition:opacity .2s}
|
|
338
|
+
.pkb-lbl{font-weight:800;font-size:max(9px,calc(13px*var(--pk-s)));line-height:1.25;text-align:center;padding:0 calc(6px*var(--pk-d));transition:opacity .2s;word-break:keep-all;overflow-wrap:anywhere}
|
|
339
|
+
.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}
|
|
260
340
|
.pkb-tile.pkb-on .pkb-stamp{opacity:.9;transform:translate(-50%,-50%) rotate(-16deg) scale(1)}
|
|
261
341
|
.pkb-tile.pkb-on .pkb-emo,.pkb-tile.pkb-on .pkb-lbl{opacity:.35}
|
|
342
|
+
.pkb-tile.pkb-free{cursor:default;container-type:inline-size}
|
|
343
|
+
.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)}
|
|
262
344
|
.pkb-tile.pkb-line{background-color:var(--pk-mint)}
|
|
263
345
|
.pkb-tile.pkb-line::before{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:linear-gradient(110deg,transparent 30%,rgba(255,255,255,.9) 45%,transparent 60%) no-repeat;background-size:250% 100%;background-position:200% 0;animation:pkb-glint 3.2s ease-in-out var(--pkb-gd,0s) infinite}
|
|
264
346
|
@keyframes pkb-glint{0%{background-position:200% 0}30%,100%{background-position:-100% 0}}
|
|
@@ -275,12 +357,17 @@ var CSS = `
|
|
|
275
357
|
.pkb-lines{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible;z-index:2;transition:opacity .4s}
|
|
276
358
|
.pkb-board.pkb-all .pkb-lines{opacity:0}
|
|
277
359
|
.pkb-lines line{fill:none;stroke-linecap:round;stroke-dasharray:1;stroke-dashoffset:0}
|
|
278
|
-
.pkb-lines .pkb-u{stroke:var(--pk-paper);stroke-width:
|
|
279
|
-
.pkb-lines .pkb-o{stroke:var(--pk-ink);stroke-width:
|
|
360
|
+
.pkb-lines .pkb-u{stroke:var(--pk-paper);stroke-width:calc(13px*var(--pk-d))}
|
|
361
|
+
.pkb-lines .pkb-o{stroke:var(--pk-ink);stroke-width:calc(6px*var(--pk-d))}
|
|
280
362
|
.pkb-lines .pkb-draw line{animation:pkb-draw .45s ease-out .3s both}
|
|
281
363
|
@keyframes pkb-draw{from{stroke-dashoffset:1}}
|
|
282
|
-
.pkb-foot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:18px;font-weight:800;font-size:13px}
|
|
283
|
-
.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}
|
|
284
371
|
.pkb-flash{position:absolute;inset:0;z-index:7;background:#fff;opacity:0;pointer-events:none}
|
|
285
372
|
.pkb-flash.pkb-go{animation:pkb-flash .4s ease-out}
|
|
286
373
|
@keyframes pkb-flash{0%{opacity:.8}100%{opacity:0}}
|
|
@@ -294,7 +381,7 @@ var CSS = `
|
|
|
294
381
|
.pkb-float{position:absolute;z-index:11;pointer-events:none;white-space:nowrap;font-family:var(--pk-display);font-weight:400;line-height:1;-webkit-text-stroke:4px var(--pk-ink);paint-order:stroke fill;transform:translate(-50%,-50%);animation:pkb-rise .95s cubic-bezier(.2,.9,.3,1) both}
|
|
295
382
|
.pkb-float.pkb-pt{font-size:22px;color:var(--pk-paper)}
|
|
296
383
|
.pkb-float.pkb-cmb{font-size:17px}
|
|
297
|
-
.pkb-float.pkb-big{font-size:clamp(
|
|
384
|
+
.pkb-float.pkb-big{font-size:clamp(14px,5.6cqi,34px);color:#FFE066}
|
|
298
385
|
.pkb-float.pkb-rch{font-size:20px;color:var(--pk-red);-webkit-text-stroke:4px var(--pk-paper)}
|
|
299
386
|
@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)}}
|
|
300
387
|
.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}
|
|
@@ -305,7 +392,7 @@ var CSS = `
|
|
|
305
392
|
.pkb-perfect.pkb-show .pkb-medal{animation:pkb-slam .38s cubic-bezier(.5,0,.75,0) .35s forwards}
|
|
306
393
|
@keyframes pkb-slam{to{opacity:1;transform:rotate(-14deg) scale(1)}}
|
|
307
394
|
.pkb-ptitle{font-family:var(--pk-display);font-weight:400;font-size:30px;line-height:1.3;color:inherit}
|
|
308
|
-
.pkb-pbody{font-weight:800;font-size:13.5px;line-height:1.6;margin:6px 0 14px;white-space:pre-line}
|
|
395
|
+
.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}
|
|
309
396
|
.pkb-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:16px}
|
|
310
397
|
.pkb-stats>div{border:2px solid var(--pk-ink);border-radius:12px;padding:6px 4px 5px;background:#fff}
|
|
311
398
|
.pkb-stats small{display:block;font-size:9.5px;font-weight:800;letter-spacing:.1em}
|
|
@@ -315,32 +402,21 @@ var CSS = `
|
|
|
315
402
|
.pkb-fx{position:absolute;inset:0;width:100%;height:100%;z-index:10;pointer-events:none}
|
|
316
403
|
.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}
|
|
317
404
|
.pkb-demo button{appearance:none;border:1px solid #ccc;background:#f3f3f3;color:#222;border-radius:999px;padding:5px 12px;font-size:12px;cursor:pointer}
|
|
318
|
-
@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:8px}.pkb-tile{border-radius:12px;box-shadow:3px 3px 0 var(--pk-ink);gap:3px;aspect-ratio:1/1}.pkb-emo{font-size:24px}.pkb-lbl{font-size:10.5px;padding:0 2px}.pkb-stamp{font-size:14px}.pkb-tile.pkb-reach::after{font-size:9px;padding:2px 5px;top:-9px;right:-5px}.pkb-lines .pkb-u{stroke-width:
|
|
405
|
+
@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}}
|
|
406
|
+
@container (max-width:400px){.pkb-board{padding:16px 10px 14px}.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{padding:12px}.pkb-stats{gap:6px}.pkb-stats>div{padding:6px 2px 5px}.pkb-stats small{letter-spacing:.02em}}
|
|
407
|
+
@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}}
|
|
319
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}}
|
|
320
409
|
@keyframes pkb-fade{0%,100%{opacity:0}15%,80%{opacity:1}}
|
|
321
410
|
`;
|
|
322
411
|
|
|
323
412
|
// src/core.ts
|
|
324
|
-
var LINES =
|
|
325
|
-
[0, 1, 2, 3],
|
|
326
|
-
[4, 5, 6, 7],
|
|
327
|
-
[8, 9, 10, 11],
|
|
328
|
-
[12, 13, 14, 15],
|
|
329
|
-
[0, 4, 8, 12],
|
|
330
|
-
[1, 5, 9, 13],
|
|
331
|
-
[2, 6, 10, 14],
|
|
332
|
-
[3, 7, 11, 15],
|
|
333
|
-
[0, 5, 10, 15],
|
|
334
|
-
[3, 6, 9, 12]
|
|
335
|
-
];
|
|
336
|
-
var DEMO_START = [0, 1, 2, 3, 5, 10];
|
|
413
|
+
var LINES = bingoLines(4);
|
|
337
414
|
var CONFETTI = ["#E8473B", "#2F6FED", "#1FA971", "#FFFDF6", "#2A1F14", "#FF9F1C"];
|
|
338
415
|
var COMBO_COLORS = ["#FF9F1C", "#1FA971", "#2F6FED", "#9B5DE5", "#E8473B"];
|
|
339
416
|
var COMBO_MS = 2500;
|
|
340
417
|
var MUTE_KEY = "packing-bingo-muted";
|
|
418
|
+
var ORDER_PREFIX = "packing-bingo-order:";
|
|
341
419
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
342
|
-
var normalize = (v) => Array.from({ length: 16 }, (_, i) => !!v?.[i]);
|
|
343
|
-
var demoStart = () => Array.from({ length: 16 }, (_, i) => DEMO_START.includes(i));
|
|
344
420
|
function readStore(key) {
|
|
345
421
|
try {
|
|
346
422
|
return localStorage.getItem(key);
|
|
@@ -354,30 +430,32 @@ function writeStore(key, value) {
|
|
|
354
430
|
} catch {
|
|
355
431
|
}
|
|
356
432
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
l.id = "pkb-fonts";
|
|
367
|
-
l.rel = "stylesheet";
|
|
368
|
-
l.href = FONT_URL;
|
|
369
|
-
document.head.appendChild(l);
|
|
370
|
-
}
|
|
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);
|
|
371
442
|
}
|
|
372
|
-
function template(
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
443
|
+
function template(L, M, { demo, shuffle, vibrate }) {
|
|
444
|
+
const n = L.size;
|
|
445
|
+
const input = vibrate ? `<input type="checkbox" switch role="checkbox">` : `<input type="checkbox">`;
|
|
446
|
+
const tiles = Array.from({ length: n * n }, (_, c) => {
|
|
447
|
+
const gd = `--pkb-gd:${glintDelay(c, n)}`;
|
|
448
|
+
if (c === L.center)
|
|
449
|
+
return `<div class="pkb-tile pkb-free pkb-on" style="${gd}"><span class="pkb-stamp">${esc(M.free)}</span></div>`;
|
|
450
|
+
const it = L.items[L.itemCell.indexOf(c)];
|
|
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>`;
|
|
452
|
+
}).join("");
|
|
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)}`;
|
|
376
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>` : "";
|
|
377
|
-
return `<div class="pkb"><div class="pkb-board">
|
|
455
|
+
return `<div class="pkb" style="${scale}"><div class="pkb-board">
|
|
378
456
|
<div class="pkb-head"><div>
|
|
379
457
|
<div class="pkb-tags"><span class="pkb-kicker" data-r="kick">${esc(M.tag)}</span><button type="button" class="pkb-snd" data-r="snd"></button></div>
|
|
380
|
-
<h2 class="pkb-title">${esc(M.title)}</h2
|
|
458
|
+
<h2 class="pkb-title">${esc(M.title)}</h2>${M.subtitle ? `<p class="pkb-sub">${esc(M.subtitle)}</p>` : ""}</div>
|
|
381
459
|
<div class="pkb-side"><div class="pkb-score">
|
|
382
460
|
<div class="pkb-pill"><small>${esc(M.packed)}</small><b data-r="cnt"></b></div>
|
|
383
461
|
<div class="pkb-pill" data-r="bp"><small>${esc(M.bingo)}</small><b data-r="bng"></b></div>
|
|
@@ -385,24 +463,44 @@ function template(items, M, demo) {
|
|
|
385
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>
|
|
386
464
|
<div class="pkb-gw"><div class="pkb-grid">${tiles}</div><svg class="pkb-lines" aria-hidden="true"></svg>
|
|
387
465
|
<div class="pkb-banner" data-r="banner" aria-hidden="true"><b data-r="bT"></b><span data-r="bS"></span></div></div>
|
|
388
|
-
<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>
|
|
389
467
|
<div class="pkb-flash" data-r="flash"></div>
|
|
390
468
|
<div class="pkb-perfect" data-r="perfect"><div class="pkb-card" role="dialog" aria-label="${esc(M.perfectTitle)}">
|
|
391
|
-
<div class="pkb-medal">${esc(M.perfectStamp)}</div><h3 class="pkb-ptitle">${esc(M.perfectTitle)}</h3><p class="pkb-pbody">${esc(M.
|
|
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>
|
|
392
470
|
<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>
|
|
393
471
|
<button type="button" class="pkb-close" data-r="close">${esc(M.close)}</button></div></div>
|
|
394
472
|
<canvas class="pkb-fx"></canvas></div>${demoBar}</div>`;
|
|
395
473
|
}
|
|
474
|
+
var perfectText = (M, total) => typeof M.perfectBody === "function" ? M.perfectBody(total) : M.perfectBody;
|
|
396
475
|
function createPackingBingo(container, options = {}) {
|
|
397
|
-
const
|
|
398
|
-
|
|
476
|
+
const L = resolveLayout(options);
|
|
477
|
+
const { items, cellLines } = L;
|
|
478
|
+
const n = L.size, N = items.length;
|
|
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;
|
|
486
|
+
const demoCells = [...cellLines[0], ...cellLines[2 * n].slice(1, -1)];
|
|
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
|
+
};
|
|
493
|
+
const far = (c) => Math.floor(c / n) + c % n;
|
|
494
|
+
const dScale = Math.min(1, 4 / n);
|
|
399
495
|
const M = { ...ja, ...options.messages };
|
|
400
496
|
if (options.title != null) M.title = options.title;
|
|
401
497
|
if (options.tag != null) M.tag = options.tag;
|
|
402
498
|
if (options.subtitle != null) M.subtitle = options.subtitle;
|
|
403
499
|
const controlled = !!options.controlled;
|
|
404
500
|
const storageKey = resolveStorageKey(options);
|
|
501
|
+
const orderKey = storageKey && ORDER_PREFIX + storageKey;
|
|
405
502
|
const demo = !!options.demo;
|
|
503
|
+
const vibrate = options.vibrate !== false;
|
|
406
504
|
const calm = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
407
505
|
let dead = false;
|
|
408
506
|
const timers = /* @__PURE__ */ new Set();
|
|
@@ -415,8 +513,8 @@ function createPackingBingo(container, options = {}) {
|
|
|
415
513
|
};
|
|
416
514
|
const wait = (ms) => new Promise((r) => window.setTimeout(r, ms));
|
|
417
515
|
let gen = 0;
|
|
418
|
-
|
|
419
|
-
container.innerHTML = template(
|
|
516
|
+
ensureStyles();
|
|
517
|
+
container.innerHTML = template(L, M, { demo, shuffle: options.shuffle !== false, vibrate });
|
|
420
518
|
const $ = (r) => {
|
|
421
519
|
const el = container.querySelector(`[data-r="${r}"]`);
|
|
422
520
|
if (!el) throw new Error(`[packing-bingo] internal: missing element "${r}"`);
|
|
@@ -428,7 +526,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
428
526
|
const svg = container.querySelector(".pkb-lines");
|
|
429
527
|
const cv = container.querySelector(".pkb-fx");
|
|
430
528
|
const ctx = cv.getContext("2d");
|
|
431
|
-
const
|
|
529
|
+
const cellTiles = Array.from(grid.children);
|
|
530
|
+
const tiles = L.itemCell.map((c) => cellTiles[c]);
|
|
531
|
+
const freeTile = L.center >= 0 ? cellTiles[L.center] : null;
|
|
432
532
|
const ins = tiles.map((t) => t.querySelector("input"));
|
|
433
533
|
const kick = $("kick"), snd = $("snd"), perfectEl = $("perfect");
|
|
434
534
|
let checked = normalize(void 0);
|
|
@@ -463,14 +563,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
463
563
|
cv.width = Math.round(BW * dpr);
|
|
464
564
|
cv.height = Math.round(BH * dpr);
|
|
465
565
|
};
|
|
466
|
-
const piece = (x, y, vx, vy, g, s, life,
|
|
566
|
+
const piece = (x, y, vx, vy, g, s, life, shape2) => ({
|
|
467
567
|
x,
|
|
468
568
|
y,
|
|
469
569
|
vx,
|
|
470
570
|
vy,
|
|
471
571
|
g,
|
|
472
572
|
life,
|
|
473
|
-
shape,
|
|
573
|
+
shape: shape2,
|
|
474
574
|
w: (5 + Math.random() * 6) * s,
|
|
475
575
|
h: (3 + Math.random() * 5) * s,
|
|
476
576
|
r: Math.random() * 6,
|
|
@@ -480,9 +580,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
480
580
|
const go = () => {
|
|
481
581
|
if (!raf && ctx) raf = requestAnimationFrame(tick);
|
|
482
582
|
};
|
|
483
|
-
function burst(x, y,
|
|
583
|
+
function burst(x, y, n2, o = {}) {
|
|
484
584
|
if (calm) return;
|
|
485
|
-
for (let i = 0; i <
|
|
585
|
+
for (let i = 0; i < n2; i++) {
|
|
486
586
|
const a = (o.angle ?? -Math.PI / 2) + (Math.random() - 0.5) * (o.spread ?? Math.PI * 2);
|
|
487
587
|
const v = (o.speed ?? 10) * (0.35 + Math.random() * 0.85);
|
|
488
588
|
const sh = Math.random() < (o.star ?? 0.15) ? "star" : Math.random() < 0.3 ? "round" : "rect";
|
|
@@ -573,11 +673,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
573
673
|
void el.offsetWidth;
|
|
574
674
|
el.classList.add(cls);
|
|
575
675
|
};
|
|
576
|
-
const hop = (
|
|
676
|
+
const hop = (t, d) => {
|
|
577
677
|
if (calm) return;
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
replay(
|
|
678
|
+
t.classList.remove("pkb-squash");
|
|
679
|
+
t.style.setProperty("--pkb-d", d + "ms");
|
|
680
|
+
replay(t, "pkb-hop");
|
|
581
681
|
};
|
|
582
682
|
grid.addEventListener("animationend", (e) => {
|
|
583
683
|
const t = e.target;
|
|
@@ -591,6 +691,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
591
691
|
r.addEventListener("animationend", () => r.remove());
|
|
592
692
|
};
|
|
593
693
|
const buzz = (pattern) => {
|
|
694
|
+
if (!vibrate) return;
|
|
594
695
|
const ua = navigator.userActivation;
|
|
595
696
|
if (ua && !ua.hasBeenActive) return;
|
|
596
697
|
try {
|
|
@@ -625,8 +726,8 @@ function createPackingBingo(container, options = {}) {
|
|
|
625
726
|
st(t0);
|
|
626
727
|
if (!calm) replay($("sp"), "pkb-bump");
|
|
627
728
|
}
|
|
628
|
-
const showGain = (
|
|
629
|
-
floatText(`+${
|
|
729
|
+
const showGain = (pts, x, y, cls = "pt") => {
|
|
730
|
+
floatText(`+${pts}`, x, y, cls);
|
|
630
731
|
countScore();
|
|
631
732
|
};
|
|
632
733
|
function countUp(el, a, b, d, fmt, ticks = false) {
|
|
@@ -646,15 +747,19 @@ function createPackingBingo(container, options = {}) {
|
|
|
646
747
|
};
|
|
647
748
|
st(t0);
|
|
648
749
|
}
|
|
649
|
-
const ctr = (
|
|
650
|
-
const t = tiles[i];
|
|
750
|
+
const ctr = (t) => {
|
|
651
751
|
return { x: t.offsetLeft + t.offsetWidth / 2, y: t.offsetTop + t.offsetHeight / 2 };
|
|
652
752
|
};
|
|
753
|
+
let landing = 0;
|
|
653
754
|
function drawLines(fresh = []) {
|
|
755
|
+
if (landing) {
|
|
756
|
+
svg.innerHTML = "";
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
654
759
|
svg.setAttribute("viewBox", `0 0 ${gw.clientWidth} ${gw.clientHeight}`);
|
|
655
760
|
svg.innerHTML = cur.map((k) => {
|
|
656
|
-
const a = ctr(
|
|
657
|
-
const dx = b.x - a.x, dy = b.y - a.y, d = Math.hypot(dx, dy) || 1, ex = dx / d *
|
|
761
|
+
const a = ctr(cellTiles[cellLines[k][0]]), b = ctr(cellTiles[cellLines[k][n - 1]]);
|
|
762
|
+
const dx = b.x - a.x, dy = b.y - a.y, d = Math.hypot(dx, dy) || 1, r = 26 * dScale, ex = dx / d * r, ey = dy / d * r;
|
|
658
763
|
const at = `x1="${a.x - ex}" y1="${a.y - ey}" x2="${b.x + ex}" y2="${b.y + ey}" pathLength="1"`;
|
|
659
764
|
return `<g${fresh.includes(k) ? ' class="pkb-draw"' : ""}><line class="pkb-u" ${at}/><line class="pkb-o" ${at}/></g>`;
|
|
660
765
|
}).join("");
|
|
@@ -665,6 +770,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
665
770
|
combo = 0;
|
|
666
771
|
$("combo").classList.remove("pkb-on");
|
|
667
772
|
sfx.off();
|
|
773
|
+
buzz(8);
|
|
668
774
|
return;
|
|
669
775
|
}
|
|
670
776
|
const now = performance.now();
|
|
@@ -687,7 +793,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
687
793
|
$("cmbN").textContent = M.combo(combo);
|
|
688
794
|
replay($("combo"), "pkb-on");
|
|
689
795
|
}
|
|
690
|
-
buzz(
|
|
796
|
+
buzz(18);
|
|
691
797
|
}
|
|
692
798
|
$("cbar").addEventListener("animationend", () => {
|
|
693
799
|
$("combo").classList.remove("pkb-on");
|
|
@@ -701,29 +807,29 @@ function createPackingBingo(container, options = {}) {
|
|
|
701
807
|
later(() => sfx.reach(), 150);
|
|
702
808
|
}
|
|
703
809
|
function bingo(fresh, pts) {
|
|
704
|
-
const
|
|
705
|
-
const seq = [...new Set(fresh.flatMap((k) =>
|
|
810
|
+
const nl = fresh.length;
|
|
811
|
+
const seq = [...new Set(fresh.flatMap((k) => cellLines[k]))].map((c) => cellTiles[c]);
|
|
706
812
|
const step = Math.min(70, 300 / seq.length);
|
|
707
813
|
seq.forEach(
|
|
708
|
-
(
|
|
709
|
-
hop(
|
|
710
|
-
const p = rel(
|
|
814
|
+
(t, j) => later(() => {
|
|
815
|
+
hop(t, 0);
|
|
816
|
+
const p = rel(t);
|
|
711
817
|
burst(p.x, p.y, 14, { speed: 5, g: 0.15, life: 30, size: 0.7, star: 0.8 });
|
|
712
818
|
sfx.note(j + 3);
|
|
713
819
|
}, j * step)
|
|
714
820
|
);
|
|
715
821
|
later(() => {
|
|
716
|
-
$("bT").textContent =
|
|
717
|
-
$("bS").textContent =
|
|
822
|
+
$("bT").textContent = nl >= 3 ? M.bannerTriple : nl === 2 ? M.bannerDouble : M.bannerSingle;
|
|
823
|
+
$("bS").textContent = nl > 1 ? M.bannerMulti(nl) : M.bannerNth(cur.length);
|
|
718
824
|
replay($("banner"), "pkb-show");
|
|
719
825
|
if (!calm) {
|
|
720
826
|
replay($("flash"), "pkb-go");
|
|
721
827
|
replay(board, "pkb-punch");
|
|
722
828
|
}
|
|
723
829
|
const c = rel(gw);
|
|
724
|
-
burst(c.x, c.y,
|
|
725
|
-
sfx.bingo(
|
|
726
|
-
buzz(
|
|
830
|
+
burst(c.x, c.y, nl > 1 ? 180 : 110, { speed: nl > 1 ? 16 : 13, star: 0.3 });
|
|
831
|
+
sfx.bingo(nl);
|
|
832
|
+
buzz(nl > 1 ? [40, 60, 40, 60, 90] : [40, 60, 40]);
|
|
727
833
|
if (pts) later(() => showGain(pts, c.x, c.y + 105, "big"), 350);
|
|
728
834
|
}, seq.length * step + 60);
|
|
729
835
|
}
|
|
@@ -731,9 +837,10 @@ function createPackingBingo(container, options = {}) {
|
|
|
731
837
|
const bonus = gotP ? 0 : 1e3;
|
|
732
838
|
gotP = true;
|
|
733
839
|
score += bonus;
|
|
734
|
-
|
|
840
|
+
cellTiles.forEach((t, c) => hop(t, far(c) * 180 / Math.max(1, n - 1)));
|
|
735
841
|
sfx.perfect();
|
|
736
842
|
if (!calm) replay($("flash"), "pkb-go");
|
|
843
|
+
$("pbody").textContent = perfectText(M, N);
|
|
737
844
|
$("sScore").textContent = "0";
|
|
738
845
|
$("sCombo").textContent = "\xD70";
|
|
739
846
|
$("sBingo").textContent = "0";
|
|
@@ -780,30 +887,32 @@ function createPackingBingo(container, options = {}) {
|
|
|
780
887
|
$("combo").classList.remove("pkb-on");
|
|
781
888
|
perfectEl.classList.remove("pkb-show");
|
|
782
889
|
}
|
|
890
|
+
const completeLines = () => lines.flatMap((l, k) => l.every((i) => checked[i]) ? [k] : []);
|
|
783
891
|
function render(src, quiet) {
|
|
784
892
|
const events = [];
|
|
785
893
|
const on = checked, c = on.filter(Boolean).length;
|
|
786
|
-
cur =
|
|
894
|
+
cur = completeLines();
|
|
787
895
|
const fresh = cur.filter((k) => !prevLines.has(k));
|
|
788
896
|
if ([...prevLines].some((k) => !cur.includes(k))) {
|
|
789
897
|
cancelFx();
|
|
790
|
-
if (c <
|
|
898
|
+
if (c < N) perfectEl.classList.remove("pkb-show");
|
|
791
899
|
}
|
|
792
|
-
const hit = new Set(cur.flatMap((k) =>
|
|
900
|
+
const hit = new Set(cur.flatMap((k) => cellLines[k]));
|
|
793
901
|
const reach = /* @__PURE__ */ new Set();
|
|
794
|
-
for (const l of
|
|
902
|
+
for (const l of lines) {
|
|
795
903
|
const m = l.filter((i) => !on[i]);
|
|
796
904
|
if (m.length === 1) reach.add(m[0]);
|
|
797
905
|
}
|
|
798
906
|
tiles.forEach((t, i) => {
|
|
799
907
|
t.classList.toggle("pkb-on", on[i]);
|
|
800
|
-
t.classList.toggle("pkb-line", hit.has(i));
|
|
908
|
+
t.classList.toggle("pkb-line", hit.has(L.itemCell[slotOf[i]]));
|
|
801
909
|
t.classList.toggle("pkb-reach", reach.has(i));
|
|
802
910
|
});
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
kick.
|
|
806
|
-
|
|
911
|
+
freeTile?.classList.toggle("pkb-line", hit.has(L.center));
|
|
912
|
+
board.classList.toggle("pkb-all", c === N);
|
|
913
|
+
kick.classList.toggle("pkb-gold", c === N);
|
|
914
|
+
kick.textContent = c === N ? M.perfectTag : M.tag;
|
|
915
|
+
$("cnt").textContent = `${c}/${N}`;
|
|
807
916
|
$("bng").textContent = String(cur.length);
|
|
808
917
|
const bp = $("bp");
|
|
809
918
|
if (cur.length > lastN) {
|
|
@@ -814,13 +923,13 @@ function createPackingBingo(container, options = {}) {
|
|
|
814
923
|
lastN = cur.length;
|
|
815
924
|
drawLines(fresh);
|
|
816
925
|
if (src !== null) tapFx(src, on[src]);
|
|
817
|
-
const newL = fresh.
|
|
818
|
-
newL.forEach((
|
|
926
|
+
const newL = fresh.map((k) => lineKey(lines[k])).filter((key) => !gotL.has(key));
|
|
927
|
+
newL.forEach((key) => gotL.add(key));
|
|
819
928
|
const linePts = 100 * newL.length * newL.length;
|
|
820
929
|
score += linePts;
|
|
821
930
|
if (quiet) {
|
|
822
931
|
if (linePts) countScore();
|
|
823
|
-
} else if (c ===
|
|
932
|
+
} else if (c === N && !prevAll) perfect(events);
|
|
824
933
|
else if (fresh.length) {
|
|
825
934
|
bingo(fresh, linePts);
|
|
826
935
|
const e = { newLines: fresh, totalLines: cur.length, score };
|
|
@@ -831,9 +940,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
831
940
|
}
|
|
832
941
|
prevLines = new Set(cur);
|
|
833
942
|
prevReach = reach;
|
|
834
|
-
prevAll = c ===
|
|
943
|
+
prevAll = c === N;
|
|
835
944
|
const labels = [...reach].map((i) => items[i].label);
|
|
836
|
-
$("msg").textContent = c ===
|
|
945
|
+
$("msg").textContent = c === N ? M.statusDone : labels.length ? M.statusReach(labels) : cur.length ? M.statusLines(cur.length, N - c) : M.statusStart(N - c);
|
|
837
946
|
return events;
|
|
838
947
|
}
|
|
839
948
|
const flush = (events) => {
|
|
@@ -860,8 +969,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
860
969
|
got.clear();
|
|
861
970
|
gotL.clear();
|
|
862
971
|
checked.forEach((v, i) => v && got.add(i));
|
|
863
|
-
|
|
864
|
-
|
|
972
|
+
const done = completeLines();
|
|
973
|
+
done.forEach((k) => gotL.add(lineKey(lines[k])));
|
|
974
|
+
prevLines = new Set(done);
|
|
865
975
|
prevReach = /* @__PURE__ */ new Set();
|
|
866
976
|
prevAll = checked.every(Boolean);
|
|
867
977
|
gotP = prevAll;
|
|
@@ -884,6 +994,77 @@ function createPackingBingo(container, options = {}) {
|
|
|
884
994
|
options.onChange?.(checked.slice());
|
|
885
995
|
flush(events);
|
|
886
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
|
+
};
|
|
887
1068
|
grid.addEventListener("change", (e) => {
|
|
888
1069
|
const i = ins.indexOf(e.target);
|
|
889
1070
|
if (i < 0) return;
|
|
@@ -891,7 +1072,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
891
1072
|
next[i] = !next[i];
|
|
892
1073
|
request(next, i);
|
|
893
1074
|
});
|
|
894
|
-
|
|
1075
|
+
container.querySelector('[data-r="shuf"]')?.addEventListener("click", shuffleCard);
|
|
1076
|
+
$("reset").addEventListener("click", () => {
|
|
1077
|
+
unshuffle();
|
|
1078
|
+
request(normalize(void 0), null, { reset: true });
|
|
1079
|
+
});
|
|
895
1080
|
$("close").addEventListener("click", () => perfectEl.classList.remove("pkb-show"));
|
|
896
1081
|
perfectEl.addEventListener("click", (e) => {
|
|
897
1082
|
if (e.target === perfectEl) perfectEl.classList.remove("pkb-show");
|
|
@@ -920,18 +1105,31 @@ function createPackingBingo(container, options = {}) {
|
|
|
920
1105
|
}
|
|
921
1106
|
};
|
|
922
1107
|
$("dB").addEventListener("click", () => {
|
|
923
|
-
const open = () =>
|
|
924
|
-
const pick = checked.every(Boolean) ? [...
|
|
1108
|
+
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();
|
|
925
1110
|
void run(pick.map((i) => ({ i, delay: 320 })));
|
|
926
1111
|
});
|
|
927
1112
|
$("dP").addEventListener("click", () => {
|
|
928
|
-
const
|
|
1113
|
+
const all = items.map((_, i) => i);
|
|
1114
|
+
const miss = checked.every(Boolean) ? all : all.filter((i) => !checked[i]);
|
|
929
1115
|
void run(miss.map((i, k) => k < miss.length - 1 ? { i, delay: 170, quiet: true } : { i, delay: 450 }));
|
|
930
1116
|
});
|
|
931
1117
|
$("dR").addEventListener("click", () => {
|
|
932
|
-
if (
|
|
1118
|
+
if (busy) return;
|
|
1119
|
+
unshuffle();
|
|
1120
|
+
request(demoStart(), null, { reset: true });
|
|
933
1121
|
});
|
|
934
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);
|
|
935
1133
|
let initial = null;
|
|
936
1134
|
if (storageKey) {
|
|
937
1135
|
try {
|
|
@@ -953,29 +1151,45 @@ function createPackingBingo(container, options = {}) {
|
|
|
953
1151
|
return {
|
|
954
1152
|
setChecked(next, opts = {}) {
|
|
955
1153
|
if (dead) return;
|
|
956
|
-
const
|
|
957
|
-
const changed =
|
|
1154
|
+
const n2 = normalize(next);
|
|
1155
|
+
const changed = n2.flatMap((v, i) => v !== checked[i] ? [i] : []);
|
|
958
1156
|
if (!changed.length) return;
|
|
959
1157
|
if (!opts.animate || changed.length !== 1) {
|
|
960
|
-
baseline(
|
|
1158
|
+
baseline(n2);
|
|
961
1159
|
return;
|
|
962
1160
|
}
|
|
963
|
-
flush(apply(
|
|
1161
|
+
flush(apply(n2, changed[0], false));
|
|
964
1162
|
},
|
|
965
1163
|
reset() {
|
|
966
|
-
if (
|
|
1164
|
+
if (dead) return;
|
|
1165
|
+
unshuffle();
|
|
1166
|
+
request(normalize(void 0), null, { reset: true });
|
|
1167
|
+
},
|
|
1168
|
+
shuffle() {
|
|
1169
|
+
if (!dead) shuffleCard();
|
|
967
1170
|
},
|
|
968
1171
|
refresh() {
|
|
969
1172
|
if (!dead) render(null, true);
|
|
970
1173
|
},
|
|
971
1174
|
getState() {
|
|
972
|
-
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
|
+
};
|
|
973
1185
|
},
|
|
974
1186
|
destroy() {
|
|
975
1187
|
if (dead) return;
|
|
976
1188
|
dead = true;
|
|
977
1189
|
timers.forEach((id) => clearTimeout(id));
|
|
978
1190
|
timers.clear();
|
|
1191
|
+
clearTimeout(landing);
|
|
1192
|
+
flights.forEach((a) => a.cancel());
|
|
979
1193
|
cancelAnimationFrame(raf);
|
|
980
1194
|
cancelAnimationFrame(tw);
|
|
981
1195
|
ro?.disconnect();
|
|
@@ -987,6 +1201,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
987
1201
|
|
|
988
1202
|
export {
|
|
989
1203
|
DEFAULT_ITEMS,
|
|
1204
|
+
bingoLines,
|
|
990
1205
|
ja,
|
|
991
1206
|
en,
|
|
992
1207
|
resolveStorageKey,
|