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
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,7 @@ __export(src_exports, {
|
|
|
24
24
|
DEFAULT_ITEMS: () => DEFAULT_ITEMS,
|
|
25
25
|
LINES: () => LINES,
|
|
26
26
|
PackingBingo: () => PackingBingo,
|
|
27
|
+
bingoLines: () => bingoLines,
|
|
27
28
|
createPackingBingo: () => createPackingBingo,
|
|
28
29
|
en: () => en,
|
|
29
30
|
ja: () => ja
|
|
@@ -35,38 +36,109 @@ var import_react = require("react");
|
|
|
35
36
|
|
|
36
37
|
// src/items.ts
|
|
37
38
|
var DEFAULT_ITEMS = [
|
|
38
|
-
{ emoji: "\u{
|
|
39
|
-
{ emoji: "\u{
|
|
40
|
-
{ emoji: "\u{
|
|
41
|
-
{ emoji: "\u{
|
|
42
|
-
{ emoji: "\u{
|
|
43
|
-
{ emoji: "\u{
|
|
44
|
-
{ emoji: "\u{1F9E5}", label: "\u4E0A\u7740" },
|
|
45
|
-
{ emoji: "\u{1FAA5}", label: "\u6D17\u9762\u7528\u5177" },
|
|
46
|
-
{ emoji: "\u{1F6C1}", label: "\u30BF\u30AA\u30EB" },
|
|
47
|
-
{ emoji: "\u{1F927}", label: "\u30CF\u30F3\u30AB\u30C1\u30FB\u30C6\u30A3\u30C3\u30B7\u30E5" },
|
|
39
|
+
{ emoji: "\u{1F45B}", label: "\u8CA1\u5E03" },
|
|
40
|
+
{ emoji: "\u{1F4F1}", label: "\u30B9\u30DE\u30DB" },
|
|
41
|
+
{ emoji: "\u{1F50B}", label: "\u30E2\u30D0\u30A4\u30EB\u200B\u5145\u96FB\u5668" },
|
|
42
|
+
{ emoji: "\u{1F4CB}", label: "\u5065\u5EB7\u8ABF\u67FB\u7968" },
|
|
43
|
+
{ emoji: "\u{1FAAA}", label: "\u30DE\u30A4\u30CA\u30AB\u30FC\u30C9\u200B\u307E\u305F\u306F\u4FDD\u967A\u8A3C" },
|
|
44
|
+
{ emoji: "\u{1F4D6}", label: "\u3057\u304A\u308A" },
|
|
48
45
|
{ emoji: "\u{1F637}", label: "\u30DE\u30B9\u30AF" },
|
|
49
|
-
{ emoji: "\u{
|
|
50
|
-
{ emoji: "\
|
|
51
|
-
{ emoji: "\u270F\uFE0F", label: "\u7B46\u8A18\u7528\u5177" },
|
|
46
|
+
{ emoji: "\u{1F927}", label: "\u30CF\u30F3\u30AB\u30C1" },
|
|
47
|
+
{ emoji: "\u{1F48A}", label: "\u5E38\u5099\u85AC" },
|
|
52
48
|
{ emoji: "\u{1F6CD}\uFE0F", label: "\u30D3\u30CB\u30FC\u30EB\u888B" },
|
|
53
|
-
{ emoji: "\u{
|
|
49
|
+
{ emoji: "\u{1F9E4}", label: "\u8ECD\u624B\uFF08\u5B66\u6821\u652F\u7D66\uFF09" },
|
|
50
|
+
{ emoji: "\u{1F526}", label: "\u30D8\u30C3\u30C9\u30E9\u30A4\u30C8" },
|
|
51
|
+
{ emoji: "\u{1F455}", label: "\u30B8\u30E3\u30FC\u30B8" },
|
|
52
|
+
{ emoji: "\u2614", label: "\u96E8\u5177" },
|
|
53
|
+
{ emoji: "\u{1F6C1}", label: "\u30BF\u30AA\u30EB" },
|
|
54
|
+
{ emoji: "\u{1F9F4}", label: "\u65E5\u713C\u3051\u6B62\u3081" }
|
|
54
55
|
];
|
|
55
56
|
|
|
57
|
+
// src/layout.ts
|
|
58
|
+
var MIN_SIZE = 2;
|
|
59
|
+
var MAX_SIZE = 10;
|
|
60
|
+
var fail = (msg) => {
|
|
61
|
+
throw new Error(`[packing-bingo] ${msg}`);
|
|
62
|
+
};
|
|
63
|
+
function layoutItems(options) {
|
|
64
|
+
if (options.items) return options.items;
|
|
65
|
+
const n = options.size;
|
|
66
|
+
if (n == null || !Number.isInteger(n) || n < MIN_SIZE) return DEFAULT_ITEMS;
|
|
67
|
+
return DEFAULT_ITEMS.slice(0, n * n - (options.free ? 1 : 0));
|
|
68
|
+
}
|
|
69
|
+
function checkSize(size, free) {
|
|
70
|
+
if (!Number.isInteger(size) || size < MIN_SIZE || size > MAX_SIZE)
|
|
71
|
+
fail(`"size" must be a whole number from ${MIN_SIZE} to ${MAX_SIZE} (got ${size}).`);
|
|
72
|
+
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}).`);
|
|
73
|
+
}
|
|
74
|
+
function shape(n, free) {
|
|
75
|
+
const seq = (f) => Array.from({ length: n }, (_, k) => f(k));
|
|
76
|
+
const cellLines = [
|
|
77
|
+
...seq((r) => seq((c) => r * n + c)),
|
|
78
|
+
// rows
|
|
79
|
+
...seq((c) => seq((r) => r * n + c)),
|
|
80
|
+
// columns
|
|
81
|
+
seq((k) => k * (n + 1)),
|
|
82
|
+
// top left to bottom right
|
|
83
|
+
seq((k) => (k + 1) * (n - 1))
|
|
84
|
+
// top right to bottom left
|
|
85
|
+
];
|
|
86
|
+
const center = free ? (n * n - 1) / 2 : -1;
|
|
87
|
+
const toItem = (c) => free && c > center ? c - 1 : c;
|
|
88
|
+
return { center, cellLines, lines: cellLines.map((l) => l.filter((c) => c !== center).map(toItem)) };
|
|
89
|
+
}
|
|
90
|
+
function resolveLayout(options) {
|
|
91
|
+
const items = layoutItems(options);
|
|
92
|
+
const free = !!options.free;
|
|
93
|
+
let size = options.size;
|
|
94
|
+
if (size == null) {
|
|
95
|
+
const len = items.length;
|
|
96
|
+
const sideOf = (cells) => {
|
|
97
|
+
const r = Math.round(Math.sqrt(cells));
|
|
98
|
+
return r * r === cells && r >= MIN_SIZE && r <= MAX_SIZE ? r : 0;
|
|
99
|
+
};
|
|
100
|
+
size = sideOf(len + (free ? 1 : 0));
|
|
101
|
+
if (!size) {
|
|
102
|
+
const other = sideOf(len + (free ? 0 : 1));
|
|
103
|
+
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` : "";
|
|
104
|
+
fail(
|
|
105
|
+
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}).`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
checkSize(size, free);
|
|
110
|
+
const want = size * size - (free ? 1 : 0);
|
|
111
|
+
if (items.length !== want) {
|
|
112
|
+
const card = `a ${size}\xD7${size} card${free ? " with a FREE center" : ""}`;
|
|
113
|
+
fail(
|
|
114
|
+
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".`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
const { center, cellLines, lines } = shape(size, free);
|
|
118
|
+
const itemCell = items.map((_, i) => free && i >= center ? i + 1 : i);
|
|
119
|
+
return { size, free, items, itemCell, center, cellLines, lines };
|
|
120
|
+
}
|
|
121
|
+
function bingoLines(size, free = false) {
|
|
122
|
+
checkSize(size, free);
|
|
123
|
+
return shape(size, free).lines;
|
|
124
|
+
}
|
|
125
|
+
|
|
56
126
|
// src/messages.ts
|
|
57
127
|
var ja = {
|
|
58
128
|
title: "\u6301\u3061\u3082\u306E\u30D3\u30F3\u30B4",
|
|
59
|
-
tag: "
|
|
60
|
-
subtitle: "
|
|
129
|
+
tag: "",
|
|
130
|
+
subtitle: "",
|
|
61
131
|
packed: "\u305D\u308D\u3063\u305F",
|
|
62
132
|
bingo: "BINGO",
|
|
63
133
|
score: "SCORE",
|
|
64
134
|
combo: (n) => `${n} COMBO`,
|
|
65
135
|
comboPop: (n) => `${n} COMBO!`,
|
|
66
136
|
reset: "\u30EA\u30BB\u30C3\u30C8",
|
|
137
|
+
shuffle: "\u{1F500} \u30B7\u30E3\u30C3\u30D5\u30EB",
|
|
67
138
|
soundOn: "\u{1F50A} \u97F3\u3042\u308A",
|
|
68
139
|
soundOff: "\u{1F507} \u97F3\u306A\u3057",
|
|
69
140
|
stamp: "OK",
|
|
141
|
+
free: "FREE",
|
|
70
142
|
reachBadge: "\u30EA\u30FC\u30C1",
|
|
71
143
|
reachPop: "\u30EA\u30FC\u30C1!",
|
|
72
144
|
bannerSingle: "BINGO!",
|
|
@@ -74,14 +146,15 @@ var ja = {
|
|
|
74
146
|
bannerTriple: "\u30C8\u30EA\u30D7\u30EB\u30D3\u30F3\u30B4!",
|
|
75
147
|
bannerNth: (n) => `${n} \u30E9\u30A4\u30F3\u76EE\uFF01`,
|
|
76
148
|
bannerMulti: (n) => `\u4E00\u6C17\u306B ${n} \u30E9\u30A4\u30F3\uFF01`,
|
|
77
|
-
statusStart: (
|
|
149
|
+
statusStart: () => "",
|
|
78
150
|
statusLines: (lines, left) => `\u30D3\u30F3\u30B4 ${lines} \u30E9\u30A4\u30F3\uFF01 \u306E\u3053\u308A ${left} \u3053`,
|
|
79
|
-
statusReach: (
|
|
151
|
+
statusReach: () => "",
|
|
80
152
|
statusDone: "\u{1F389} \u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u51FA\u767A\u3067\u304D\u307E\u3059",
|
|
81
153
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
82
154
|
perfectStamp: "\u6E96\u5099\n\u5B8C\u4E86",
|
|
83
155
|
perfectTitle: "\u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01",
|
|
84
|
-
perfectBody:
|
|
156
|
+
perfectBody: (n) => `${n}\u3053 \u305C\u3093\u3076\u305D\u308D\u3063\u305F\uFF01
|
|
157
|
+
\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u3001\u3044\u3063\u3066\u3089\u3063\u3057\u3083\u3044\uFF01`,
|
|
85
158
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
86
159
|
statScore: "SCORE",
|
|
87
160
|
statCombo: "MAX COMBO",
|
|
@@ -94,17 +167,19 @@ var ja = {
|
|
|
94
167
|
};
|
|
95
168
|
var en = {
|
|
96
169
|
title: "Packing Bingo",
|
|
97
|
-
tag: "
|
|
98
|
-
subtitle: "
|
|
170
|
+
tag: "",
|
|
171
|
+
subtitle: "",
|
|
99
172
|
packed: "PACKED",
|
|
100
173
|
bingo: "BINGO",
|
|
101
174
|
score: "SCORE",
|
|
102
175
|
combo: (n) => `${n} COMBO`,
|
|
103
176
|
comboPop: (n) => `${n} COMBO!`,
|
|
104
177
|
reset: "Reset",
|
|
178
|
+
shuffle: "\u{1F500} Shuffle",
|
|
105
179
|
soundOn: "\u{1F50A} Sound on",
|
|
106
180
|
soundOff: "\u{1F507} Muted",
|
|
107
181
|
stamp: "OK",
|
|
182
|
+
free: "FREE",
|
|
108
183
|
reachBadge: "REACH",
|
|
109
184
|
reachPop: "Reach!",
|
|
110
185
|
bannerSingle: "BINGO!",
|
|
@@ -112,14 +187,15 @@ var en = {
|
|
|
112
187
|
bannerTriple: "TRIPLE BINGO!",
|
|
113
188
|
bannerNth: (n) => `Line ${n}!`,
|
|
114
189
|
bannerMulti: (n) => `${n} lines at once!`,
|
|
115
|
-
statusStart: (
|
|
190
|
+
statusStart: () => "",
|
|
116
191
|
statusLines: (lines, left) => `${lines} bingo line${lines === 1 ? "" : "s"}! ${left} to go`,
|
|
117
|
-
statusReach: (
|
|
192
|
+
statusReach: () => "",
|
|
118
193
|
statusDone: "\u{1F389} Perfect! Nothing left behind.",
|
|
119
194
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
120
195
|
perfectStamp: "ALL\nSET",
|
|
121
196
|
perfectTitle: "Perfect!",
|
|
122
|
-
perfectBody:
|
|
197
|
+
perfectBody: (n) => `All ${n} packed!
|
|
198
|
+
Have a great trip!`,
|
|
123
199
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
124
200
|
statScore: "SCORE",
|
|
125
201
|
statCombo: "MAX COMBO",
|
|
@@ -219,6 +295,11 @@ function createSound(isMuted) {
|
|
|
219
295
|
},
|
|
220
296
|
tick: () => tone(1760, 0, 0.03, "square", 0.03),
|
|
221
297
|
note: (k, at = 0) => tone(step(k), at, 0.12, "square", 0.05),
|
|
298
|
+
shuffle() {
|
|
299
|
+
for (let i = 0; i < 7; i++) noise(i * 0.042, 0.05, 0.16, 1600 + i * 260);
|
|
300
|
+
tone(hz(7), 0.34, 0.12, "triangle", 0.14);
|
|
301
|
+
tone(hz(12), 0.42, 0.2, "triangle", 0.14);
|
|
302
|
+
},
|
|
222
303
|
close() {
|
|
223
304
|
if (ac) void ac.close().catch(() => {
|
|
224
305
|
});
|
|
@@ -233,7 +314,7 @@ function resolveStorageKey(options) {
|
|
|
233
314
|
if (options.controlled) return null;
|
|
234
315
|
if (options.storageKey !== void 0) return options.storageKey || null;
|
|
235
316
|
if (options.demo) return null;
|
|
236
|
-
return itemsKey(options
|
|
317
|
+
return itemsKey(layoutItems(options));
|
|
237
318
|
}
|
|
238
319
|
function itemsKey(items) {
|
|
239
320
|
const text = items.map((it) => `${it.emoji}\0${it.label}`).join("");
|
|
@@ -246,24 +327,24 @@ function itemsKey(items) {
|
|
|
246
327
|
}
|
|
247
328
|
|
|
248
329
|
// src/styles.ts
|
|
249
|
-
var FONT_URL = "https://fonts.googleapis.com/css2?family=Dela+Gothic+One&family=M+PLUS+Rounded+1c:wght@500;800&display=swap";
|
|
250
330
|
var CSS = `
|
|
251
|
-
.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%}
|
|
331
|
+
.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%}
|
|
252
332
|
.pkb,.pkb *{box-sizing:border-box;margin:0;padding:0}
|
|
253
333
|
.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}
|
|
254
334
|
.pkb-board.pkb-shake{animation:pkb-shake .45s cubic-bezier(.36,.07,.19,.97)}
|
|
255
335
|
@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)}}
|
|
256
336
|
.pkb-board.pkb-punch{animation:pkb-punch .4s cubic-bezier(.3,1.6,.5,1)}
|
|
257
337
|
@keyframes pkb-punch{30%{transform:scale(1.018)}}
|
|
258
|
-
.pkb-head{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-bottom:18px}
|
|
338
|
+
.pkb-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px 16px;margin-bottom:18px}
|
|
259
339
|
.pkb-tags{display:flex;gap:8px;align-items:center}
|
|
260
340
|
.pkb-kicker{display:inline-block;background:var(--pk-ink);color:var(--pk-sun);font-weight:800;font-size:11px;letter-spacing:.14em;padding:4px 10px;border-radius:999px}
|
|
341
|
+
.pkb-kicker:empty{display:none}
|
|
261
342
|
.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}
|
|
262
343
|
@keyframes pkb-rb{to{background-position:-200% 0}}
|
|
263
344
|
.pkb button{font-family:var(--pk-body)}
|
|
264
345
|
.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}
|
|
265
346
|
.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}
|
|
266
|
-
.pkb-sub{font-weight:800;font-size:13px;margin-top:4px}
|
|
347
|
+
.pkb-sub{font-weight:800;font-size:13px;margin-top:4px;word-break:auto-phrase}
|
|
267
348
|
.pkb-side{display:flex;flex-direction:column;gap:8px;flex:none}
|
|
268
349
|
.pkb-score{display:flex;gap:10px}
|
|
269
350
|
.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)}
|
|
@@ -282,16 +363,18 @@ var CSS = `
|
|
|
282
363
|
.pkb-combo.pkb-on .pkb-bar i{animation:pkb-drain 2.5s linear forwards}
|
|
283
364
|
@keyframes pkb-drain{from{transform:scaleX(1)}to{transform:scaleX(0)}}
|
|
284
365
|
.pkb-gw{position:relative}
|
|
285
|
-
.pkb-grid{display:grid;grid-template-columns:repeat(
|
|
286
|
-
.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}
|
|
287
|
-
.pkb-tile:active{transform:translate(3px,3px);box-shadow:1px 1px 0 var(--pk-ink)}
|
|
366
|
+
.pkb-grid{display:grid;grid-template-columns:repeat(var(--pk-n),minmax(0,1fr));gap:calc(12px*var(--pk-d))}
|
|
367
|
+
.pkb-tile{position:relative;min-width:0;place-self:stretch;aspect-ratio:1/.82;background:var(--pk-paper);border:2.5px solid var(--pk-ink);border-radius:calc(16px*var(--pk-d));box-shadow:4px 4px 0 var(--pk-ink);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:calc(6px*var(--pk-d));cursor:pointer;user-select:none;-webkit-tap-highlight-color:transparent;transition:transform .1s,box-shadow .1s,background-color .25s}
|
|
368
|
+
.pkb-tile:active:not(.pkb-free){transform:translate(3px,3px);box-shadow:1px 1px 0 var(--pk-ink)}
|
|
288
369
|
.pkb-tile input{position:absolute;opacity:0;pointer-events:none;width:1px;height:1px}
|
|
289
370
|
.pkb-tile:has(input:focus-visible){outline:3px solid var(--pk-ink);outline-offset:3px}
|
|
290
|
-
.pkb-emo{font-size:32px;line-height:1;transition:opacity .2s}
|
|
291
|
-
.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}
|
|
292
|
-
.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}
|
|
371
|
+
.pkb-emo{font-size:max(14px,calc(32px*var(--pk-s)));line-height:1;transition:opacity .2s}
|
|
372
|
+
.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}
|
|
373
|
+
.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}
|
|
293
374
|
.pkb-tile.pkb-on .pkb-stamp{opacity:.9;transform:translate(-50%,-50%) rotate(-16deg) scale(1)}
|
|
294
375
|
.pkb-tile.pkb-on .pkb-emo,.pkb-tile.pkb-on .pkb-lbl{opacity:.35}
|
|
376
|
+
.pkb-tile.pkb-free{cursor:default;container-type:inline-size}
|
|
377
|
+
.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)}
|
|
295
378
|
.pkb-tile.pkb-line{background-color:var(--pk-mint)}
|
|
296
379
|
.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}
|
|
297
380
|
@keyframes pkb-glint{0%{background-position:200% 0}30%,100%{background-position:-100% 0}}
|
|
@@ -308,12 +391,17 @@ var CSS = `
|
|
|
308
391
|
.pkb-lines{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible;z-index:2;transition:opacity .4s}
|
|
309
392
|
.pkb-board.pkb-all .pkb-lines{opacity:0}
|
|
310
393
|
.pkb-lines line{fill:none;stroke-linecap:round;stroke-dasharray:1;stroke-dashoffset:0}
|
|
311
|
-
.pkb-lines .pkb-u{stroke:var(--pk-paper);stroke-width:
|
|
312
|
-
.pkb-lines .pkb-o{stroke:var(--pk-ink);stroke-width:
|
|
394
|
+
.pkb-lines .pkb-u{stroke:var(--pk-paper);stroke-width:calc(13px*var(--pk-d))}
|
|
395
|
+
.pkb-lines .pkb-o{stroke:var(--pk-ink);stroke-width:calc(6px*var(--pk-d))}
|
|
313
396
|
.pkb-lines .pkb-draw line{animation:pkb-draw .45s ease-out .3s both}
|
|
314
397
|
@keyframes pkb-draw{from{stroke-dashoffset:1}}
|
|
315
|
-
.pkb-foot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:18px;font-weight:800;font-size:13px}
|
|
316
|
-
.pkb-
|
|
398
|
+
.pkb-foot{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px 10px;margin-top:18px;font-weight:800;font-size:13px;word-break:auto-phrase}
|
|
399
|
+
.pkb-foot>span{flex:1 1 14em}
|
|
400
|
+
.pkb-foot>span:empty{flex-basis:0}
|
|
401
|
+
.pkb-acts{display:flex;gap:8px;flex:none;margin-left:auto}
|
|
402
|
+
.pkb-reset,.pkb-shuf{appearance:none;flex:none;border:2px solid var(--pk-ink);background:transparent;border-radius:999px;padding:5px 12px;font-weight:800;font-size:12px;line-height:1.4;color:var(--pk-ink);cursor:pointer}
|
|
403
|
+
.pkb-shuf{background:var(--pk-paper);box-shadow:2px 2px 0 var(--pk-ink)}
|
|
404
|
+
.pkb-shuf:active{transform:translate(2px,2px);box-shadow:none}
|
|
317
405
|
.pkb-flash{position:absolute;inset:0;z-index:7;background:#fff;opacity:0;pointer-events:none}
|
|
318
406
|
.pkb-flash.pkb-go{animation:pkb-flash .4s ease-out}
|
|
319
407
|
@keyframes pkb-flash{0%{opacity:.8}100%{opacity:0}}
|
|
@@ -327,7 +415,7 @@ var CSS = `
|
|
|
327
415
|
.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}
|
|
328
416
|
.pkb-float.pkb-pt{font-size:22px;color:var(--pk-paper)}
|
|
329
417
|
.pkb-float.pkb-cmb{font-size:17px}
|
|
330
|
-
.pkb-float.pkb-big{font-size:clamp(
|
|
418
|
+
.pkb-float.pkb-big{font-size:clamp(14px,5.6cqi,34px);color:#FFE066}
|
|
331
419
|
.pkb-float.pkb-rch{font-size:20px;color:var(--pk-red);-webkit-text-stroke:4px var(--pk-paper)}
|
|
332
420
|
@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)}}
|
|
333
421
|
.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}
|
|
@@ -338,7 +426,7 @@ var CSS = `
|
|
|
338
426
|
.pkb-perfect.pkb-show .pkb-medal{animation:pkb-slam .38s cubic-bezier(.5,0,.75,0) .35s forwards}
|
|
339
427
|
@keyframes pkb-slam{to{opacity:1;transform:rotate(-14deg) scale(1)}}
|
|
340
428
|
.pkb-ptitle{font-family:var(--pk-display);font-weight:400;font-size:30px;line-height:1.3;color:inherit}
|
|
341
|
-
.pkb-pbody{font-weight:800;font-size:13.5px;line-height:1.6;margin:6px 0 14px;white-space:pre-line}
|
|
429
|
+
.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}
|
|
342
430
|
.pkb-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:16px}
|
|
343
431
|
.pkb-stats>div{border:2px solid var(--pk-ink);border-radius:12px;padding:6px 4px 5px;background:#fff}
|
|
344
432
|
.pkb-stats small{display:block;font-size:9.5px;font-weight:800;letter-spacing:.1em}
|
|
@@ -348,32 +436,21 @@ var CSS = `
|
|
|
348
436
|
.pkb-fx{position:absolute;inset:0;width:100%;height:100%;z-index:10;pointer-events:none}
|
|
349
437
|
.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}
|
|
350
438
|
.pkb-demo button{appearance:none;border:1px solid #ccc;background:#f3f3f3;color:#222;border-radius:999px;padding:5px 12px;font-size:12px;cursor:pointer}
|
|
351
|
-
@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:
|
|
439
|
+
@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}}
|
|
440
|
+
@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}}
|
|
441
|
+
@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}}
|
|
352
442
|
@media (prefers-reduced-motion:reduce){.pkb-board.pkb-shake,.pkb-board.pkb-punch,.pkb-flash.pkb-go,.pkb-tile.pkb-hop,.pkb-tile.pkb-squash,.pkb-tile.pkb-reach,.pkb-tile.pkb-line::before,.pkb-pill.pkb-hot,.pkb-bump,.pkb-tile.pkb-reach::after,.pkb-banner::before,.pkb-lines .pkb-draw line,.pkb-kicker.pkb-gold{animation:none}.pkb-banner.pkb-show{animation:pkb-fade 1.8s forwards}.pkb-float{animation:pkb-fade 1s both}.pkb-perfect.pkb-show .pkb-medal{animation:none;opacity:1;transform:rotate(-14deg)}.pkb-card{transition:none}}
|
|
353
443
|
@keyframes pkb-fade{0%,100%{opacity:0}15%,80%{opacity:1}}
|
|
354
444
|
`;
|
|
355
445
|
|
|
356
446
|
// src/core.ts
|
|
357
|
-
var LINES =
|
|
358
|
-
[0, 1, 2, 3],
|
|
359
|
-
[4, 5, 6, 7],
|
|
360
|
-
[8, 9, 10, 11],
|
|
361
|
-
[12, 13, 14, 15],
|
|
362
|
-
[0, 4, 8, 12],
|
|
363
|
-
[1, 5, 9, 13],
|
|
364
|
-
[2, 6, 10, 14],
|
|
365
|
-
[3, 7, 11, 15],
|
|
366
|
-
[0, 5, 10, 15],
|
|
367
|
-
[3, 6, 9, 12]
|
|
368
|
-
];
|
|
369
|
-
var DEMO_START = [0, 1, 2, 3, 5, 10];
|
|
447
|
+
var LINES = bingoLines(4);
|
|
370
448
|
var CONFETTI = ["#E8473B", "#2F6FED", "#1FA971", "#FFFDF6", "#2A1F14", "#FF9F1C"];
|
|
371
449
|
var COMBO_COLORS = ["#FF9F1C", "#1FA971", "#2F6FED", "#9B5DE5", "#E8473B"];
|
|
372
450
|
var COMBO_MS = 2500;
|
|
373
451
|
var MUTE_KEY = "packing-bingo-muted";
|
|
452
|
+
var ORDER_PREFIX = "packing-bingo-order:";
|
|
374
453
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
375
|
-
var normalize = (v) => Array.from({ length: 16 }, (_, i) => !!v?.[i]);
|
|
376
|
-
var demoStart = () => Array.from({ length: 16 }, (_, i) => DEMO_START.includes(i));
|
|
377
454
|
function readStore(key) {
|
|
378
455
|
try {
|
|
379
456
|
return localStorage.getItem(key);
|
|
@@ -387,30 +464,32 @@ function writeStore(key, value) {
|
|
|
387
464
|
} catch {
|
|
388
465
|
}
|
|
389
466
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
l.id = "pkb-fonts";
|
|
400
|
-
l.rel = "stylesheet";
|
|
401
|
-
l.href = FONT_URL;
|
|
402
|
-
document.head.appendChild(l);
|
|
403
|
-
}
|
|
467
|
+
var glintDelay = (c, n) => `${+((Math.floor(c / n) + c % n) * 0.36 / Math.max(1, n - 1)).toFixed(3)}s`;
|
|
468
|
+
var lineKey = (l) => l.slice().sort((a, b) => a - b).join(",");
|
|
469
|
+
var FLY_MS = 560;
|
|
470
|
+
function ensureStyles() {
|
|
471
|
+
if (document.getElementById("pkb-styles")) return;
|
|
472
|
+
const s = document.createElement("style");
|
|
473
|
+
s.id = "pkb-styles";
|
|
474
|
+
s.textContent = CSS;
|
|
475
|
+
document.head.appendChild(s);
|
|
404
476
|
}
|
|
405
|
-
function template(
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
477
|
+
function template(L, M, { demo, shuffle, vibrate }) {
|
|
478
|
+
const n = L.size;
|
|
479
|
+
const input = vibrate ? `<input type="checkbox" switch role="checkbox">` : `<input type="checkbox">`;
|
|
480
|
+
const tiles = Array.from({ length: n * n }, (_, c) => {
|
|
481
|
+
const gd = `--pkb-gd:${glintDelay(c, n)}`;
|
|
482
|
+
if (c === L.center)
|
|
483
|
+
return `<div class="pkb-tile pkb-free pkb-on" style="${gd}"><span class="pkb-stamp">${esc(M.free)}</span></div>`;
|
|
484
|
+
const it = L.items[L.itemCell.indexOf(c)];
|
|
485
|
+
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>`;
|
|
486
|
+
}).join("");
|
|
487
|
+
const scale = `--pk-n:${n};--pk-s:${+Math.min(1.35, 4 / n).toFixed(3)};--pk-d:${+Math.min(1, 4 / n).toFixed(3)}`;
|
|
409
488
|
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>` : "";
|
|
410
|
-
return `<div class="pkb"><div class="pkb-board">
|
|
489
|
+
return `<div class="pkb" style="${scale}"><div class="pkb-board">
|
|
411
490
|
<div class="pkb-head"><div>
|
|
412
491
|
<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>
|
|
413
|
-
<h2 class="pkb-title">${esc(M.title)}</h2
|
|
492
|
+
<h2 class="pkb-title">${esc(M.title)}</h2>${M.subtitle ? `<p class="pkb-sub">${esc(M.subtitle)}</p>` : ""}</div>
|
|
414
493
|
<div class="pkb-side"><div class="pkb-score">
|
|
415
494
|
<div class="pkb-pill"><small>${esc(M.packed)}</small><b data-r="cnt"></b></div>
|
|
416
495
|
<div class="pkb-pill" data-r="bp"><small>${esc(M.bingo)}</small><b data-r="bng"></b></div>
|
|
@@ -418,24 +497,44 @@ function template(items, M, demo) {
|
|
|
418
497
|
<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>
|
|
419
498
|
<div class="pkb-gw"><div class="pkb-grid">${tiles}</div><svg class="pkb-lines" aria-hidden="true"></svg>
|
|
420
499
|
<div class="pkb-banner" data-r="banner" aria-hidden="true"><b data-r="bT"></b><span data-r="bS"></span></div></div>
|
|
421
|
-
<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>
|
|
500
|
+
<div class="pkb-foot"><span data-r="msg" aria-live="polite"></span><div class="pkb-acts">${shuffle ? `<button type="button" class="pkb-shuf" data-r="shuf">${esc(M.shuffle)}</button>` : ""}<button type="button" class="pkb-reset" data-r="reset">${esc(M.reset)}</button></div></div>
|
|
422
501
|
<div class="pkb-flash" data-r="flash"></div>
|
|
423
502
|
<div class="pkb-perfect" data-r="perfect"><div class="pkb-card" role="dialog" aria-label="${esc(M.perfectTitle)}">
|
|
424
|
-
<div class="pkb-medal">${esc(M.perfectStamp)}</div><h3 class="pkb-ptitle">${esc(M.perfectTitle)}</h3><p class="pkb-pbody">${esc(M.
|
|
503
|
+
<div class="pkb-medal">${esc(M.perfectStamp)}</div><h3 class="pkb-ptitle">${esc(M.perfectTitle)}</h3><p class="pkb-pbody" data-r="pbody">${esc(perfectText(M, L.items.length))}</p>
|
|
425
504
|
<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>
|
|
426
505
|
<button type="button" class="pkb-close" data-r="close">${esc(M.close)}</button></div></div>
|
|
427
506
|
<canvas class="pkb-fx"></canvas></div>${demoBar}</div>`;
|
|
428
507
|
}
|
|
508
|
+
var perfectText = (M, total) => typeof M.perfectBody === "function" ? M.perfectBody(total) : M.perfectBody;
|
|
429
509
|
function createPackingBingo(container, options = {}) {
|
|
430
|
-
const
|
|
431
|
-
|
|
510
|
+
const L = resolveLayout(options);
|
|
511
|
+
const { items, cellLines } = L;
|
|
512
|
+
const n = L.size, N = items.length;
|
|
513
|
+
const normalize = (v) => Array.from({ length: N }, (_, i) => !!v?.[i]);
|
|
514
|
+
const identity = () => Array.from({ length: N }, (_, s) => s);
|
|
515
|
+
const isIdentity = (o) => o.every((v, s) => v === s);
|
|
516
|
+
const validOrder = (o) => Array.isArray(o) && o.length === N && o.every((v) => Number.isInteger(v) && v >= 0 && v < N) && new Set(o).size === N;
|
|
517
|
+
let order = identity();
|
|
518
|
+
const slotOf = identity();
|
|
519
|
+
let lines = L.lines;
|
|
520
|
+
const demoCells = [...cellLines[0], ...cellLines[2 * n].slice(1, -1)];
|
|
521
|
+
const demoSlots = L.itemCell.flatMap((c, s) => demoCells.includes(c) ? [s] : []);
|
|
522
|
+
const demoStart = () => {
|
|
523
|
+
const on = normalize(void 0);
|
|
524
|
+
demoSlots.forEach((s) => on[order[s]] = true);
|
|
525
|
+
return on;
|
|
526
|
+
};
|
|
527
|
+
const far = (c) => Math.floor(c / n) + c % n;
|
|
528
|
+
const dScale = Math.min(1, 4 / n);
|
|
432
529
|
const M = { ...ja, ...options.messages };
|
|
433
530
|
if (options.title != null) M.title = options.title;
|
|
434
531
|
if (options.tag != null) M.tag = options.tag;
|
|
435
532
|
if (options.subtitle != null) M.subtitle = options.subtitle;
|
|
436
533
|
const controlled = !!options.controlled;
|
|
437
534
|
const storageKey = resolveStorageKey(options);
|
|
535
|
+
const orderKey = storageKey && ORDER_PREFIX + storageKey;
|
|
438
536
|
const demo = !!options.demo;
|
|
537
|
+
const vibrate = options.vibrate !== false;
|
|
439
538
|
const calm = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
440
539
|
let dead = false;
|
|
441
540
|
const timers = /* @__PURE__ */ new Set();
|
|
@@ -448,8 +547,8 @@ function createPackingBingo(container, options = {}) {
|
|
|
448
547
|
};
|
|
449
548
|
const wait = (ms) => new Promise((r) => window.setTimeout(r, ms));
|
|
450
549
|
let gen = 0;
|
|
451
|
-
|
|
452
|
-
container.innerHTML = template(
|
|
550
|
+
ensureStyles();
|
|
551
|
+
container.innerHTML = template(L, M, { demo, shuffle: options.shuffle !== false, vibrate });
|
|
453
552
|
const $ = (r) => {
|
|
454
553
|
const el = container.querySelector(`[data-r="${r}"]`);
|
|
455
554
|
if (!el) throw new Error(`[packing-bingo] internal: missing element "${r}"`);
|
|
@@ -461,7 +560,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
461
560
|
const svg = container.querySelector(".pkb-lines");
|
|
462
561
|
const cv = container.querySelector(".pkb-fx");
|
|
463
562
|
const ctx = cv.getContext("2d");
|
|
464
|
-
const
|
|
563
|
+
const cellTiles = Array.from(grid.children);
|
|
564
|
+
const tiles = L.itemCell.map((c) => cellTiles[c]);
|
|
565
|
+
const freeTile = L.center >= 0 ? cellTiles[L.center] : null;
|
|
465
566
|
const ins = tiles.map((t) => t.querySelector("input"));
|
|
466
567
|
const kick = $("kick"), snd = $("snd"), perfectEl = $("perfect");
|
|
467
568
|
let checked = normalize(void 0);
|
|
@@ -496,14 +597,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
496
597
|
cv.width = Math.round(BW * dpr);
|
|
497
598
|
cv.height = Math.round(BH * dpr);
|
|
498
599
|
};
|
|
499
|
-
const piece = (x, y, vx, vy, g, s, life,
|
|
600
|
+
const piece = (x, y, vx, vy, g, s, life, shape2) => ({
|
|
500
601
|
x,
|
|
501
602
|
y,
|
|
502
603
|
vx,
|
|
503
604
|
vy,
|
|
504
605
|
g,
|
|
505
606
|
life,
|
|
506
|
-
shape,
|
|
607
|
+
shape: shape2,
|
|
507
608
|
w: (5 + Math.random() * 6) * s,
|
|
508
609
|
h: (3 + Math.random() * 5) * s,
|
|
509
610
|
r: Math.random() * 6,
|
|
@@ -513,9 +614,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
513
614
|
const go = () => {
|
|
514
615
|
if (!raf && ctx) raf = requestAnimationFrame(tick);
|
|
515
616
|
};
|
|
516
|
-
function burst(x, y,
|
|
617
|
+
function burst(x, y, n2, o = {}) {
|
|
517
618
|
if (calm) return;
|
|
518
|
-
for (let i = 0; i <
|
|
619
|
+
for (let i = 0; i < n2; i++) {
|
|
519
620
|
const a = (o.angle ?? -Math.PI / 2) + (Math.random() - 0.5) * (o.spread ?? Math.PI * 2);
|
|
520
621
|
const v = (o.speed ?? 10) * (0.35 + Math.random() * 0.85);
|
|
521
622
|
const sh = Math.random() < (o.star ?? 0.15) ? "star" : Math.random() < 0.3 ? "round" : "rect";
|
|
@@ -606,11 +707,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
606
707
|
void el.offsetWidth;
|
|
607
708
|
el.classList.add(cls);
|
|
608
709
|
};
|
|
609
|
-
const hop = (
|
|
710
|
+
const hop = (t, d) => {
|
|
610
711
|
if (calm) return;
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
replay(
|
|
712
|
+
t.classList.remove("pkb-squash");
|
|
713
|
+
t.style.setProperty("--pkb-d", d + "ms");
|
|
714
|
+
replay(t, "pkb-hop");
|
|
614
715
|
};
|
|
615
716
|
grid.addEventListener("animationend", (e) => {
|
|
616
717
|
const t = e.target;
|
|
@@ -624,6 +725,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
624
725
|
r.addEventListener("animationend", () => r.remove());
|
|
625
726
|
};
|
|
626
727
|
const buzz = (pattern) => {
|
|
728
|
+
if (!vibrate) return;
|
|
627
729
|
const ua = navigator.userActivation;
|
|
628
730
|
if (ua && !ua.hasBeenActive) return;
|
|
629
731
|
try {
|
|
@@ -658,8 +760,8 @@ function createPackingBingo(container, options = {}) {
|
|
|
658
760
|
st(t0);
|
|
659
761
|
if (!calm) replay($("sp"), "pkb-bump");
|
|
660
762
|
}
|
|
661
|
-
const showGain = (
|
|
662
|
-
floatText(`+${
|
|
763
|
+
const showGain = (pts, x, y, cls = "pt") => {
|
|
764
|
+
floatText(`+${pts}`, x, y, cls);
|
|
663
765
|
countScore();
|
|
664
766
|
};
|
|
665
767
|
function countUp(el, a, b, d, fmt, ticks = false) {
|
|
@@ -679,15 +781,19 @@ function createPackingBingo(container, options = {}) {
|
|
|
679
781
|
};
|
|
680
782
|
st(t0);
|
|
681
783
|
}
|
|
682
|
-
const ctr = (
|
|
683
|
-
const t = tiles[i];
|
|
784
|
+
const ctr = (t) => {
|
|
684
785
|
return { x: t.offsetLeft + t.offsetWidth / 2, y: t.offsetTop + t.offsetHeight / 2 };
|
|
685
786
|
};
|
|
787
|
+
let landing = 0;
|
|
686
788
|
function drawLines(fresh = []) {
|
|
789
|
+
if (landing) {
|
|
790
|
+
svg.innerHTML = "";
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
687
793
|
svg.setAttribute("viewBox", `0 0 ${gw.clientWidth} ${gw.clientHeight}`);
|
|
688
794
|
svg.innerHTML = cur.map((k) => {
|
|
689
|
-
const a = ctr(
|
|
690
|
-
const dx = b.x - a.x, dy = b.y - a.y, d = Math.hypot(dx, dy) || 1, ex = dx / d *
|
|
795
|
+
const a = ctr(cellTiles[cellLines[k][0]]), b = ctr(cellTiles[cellLines[k][n - 1]]);
|
|
796
|
+
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;
|
|
691
797
|
const at = `x1="${a.x - ex}" y1="${a.y - ey}" x2="${b.x + ex}" y2="${b.y + ey}" pathLength="1"`;
|
|
692
798
|
return `<g${fresh.includes(k) ? ' class="pkb-draw"' : ""}><line class="pkb-u" ${at}/><line class="pkb-o" ${at}/></g>`;
|
|
693
799
|
}).join("");
|
|
@@ -698,6 +804,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
698
804
|
combo = 0;
|
|
699
805
|
$("combo").classList.remove("pkb-on");
|
|
700
806
|
sfx.off();
|
|
807
|
+
buzz(8);
|
|
701
808
|
return;
|
|
702
809
|
}
|
|
703
810
|
const now = performance.now();
|
|
@@ -720,7 +827,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
720
827
|
$("cmbN").textContent = M.combo(combo);
|
|
721
828
|
replay($("combo"), "pkb-on");
|
|
722
829
|
}
|
|
723
|
-
buzz(
|
|
830
|
+
buzz(18);
|
|
724
831
|
}
|
|
725
832
|
$("cbar").addEventListener("animationend", () => {
|
|
726
833
|
$("combo").classList.remove("pkb-on");
|
|
@@ -734,29 +841,29 @@ function createPackingBingo(container, options = {}) {
|
|
|
734
841
|
later(() => sfx.reach(), 150);
|
|
735
842
|
}
|
|
736
843
|
function bingo(fresh, pts) {
|
|
737
|
-
const
|
|
738
|
-
const seq = [...new Set(fresh.flatMap((k) =>
|
|
844
|
+
const nl = fresh.length;
|
|
845
|
+
const seq = [...new Set(fresh.flatMap((k) => cellLines[k]))].map((c) => cellTiles[c]);
|
|
739
846
|
const step = Math.min(70, 300 / seq.length);
|
|
740
847
|
seq.forEach(
|
|
741
|
-
(
|
|
742
|
-
hop(
|
|
743
|
-
const p = rel(
|
|
848
|
+
(t, j) => later(() => {
|
|
849
|
+
hop(t, 0);
|
|
850
|
+
const p = rel(t);
|
|
744
851
|
burst(p.x, p.y, 14, { speed: 5, g: 0.15, life: 30, size: 0.7, star: 0.8 });
|
|
745
852
|
sfx.note(j + 3);
|
|
746
853
|
}, j * step)
|
|
747
854
|
);
|
|
748
855
|
later(() => {
|
|
749
|
-
$("bT").textContent =
|
|
750
|
-
$("bS").textContent =
|
|
856
|
+
$("bT").textContent = nl >= 3 ? M.bannerTriple : nl === 2 ? M.bannerDouble : M.bannerSingle;
|
|
857
|
+
$("bS").textContent = nl > 1 ? M.bannerMulti(nl) : M.bannerNth(cur.length);
|
|
751
858
|
replay($("banner"), "pkb-show");
|
|
752
859
|
if (!calm) {
|
|
753
860
|
replay($("flash"), "pkb-go");
|
|
754
861
|
replay(board, "pkb-punch");
|
|
755
862
|
}
|
|
756
863
|
const c = rel(gw);
|
|
757
|
-
burst(c.x, c.y,
|
|
758
|
-
sfx.bingo(
|
|
759
|
-
buzz(
|
|
864
|
+
burst(c.x, c.y, nl > 1 ? 180 : 110, { speed: nl > 1 ? 16 : 13, star: 0.3 });
|
|
865
|
+
sfx.bingo(nl);
|
|
866
|
+
buzz(nl > 1 ? [40, 60, 40, 60, 90] : [40, 60, 40]);
|
|
760
867
|
if (pts) later(() => showGain(pts, c.x, c.y + 105, "big"), 350);
|
|
761
868
|
}, seq.length * step + 60);
|
|
762
869
|
}
|
|
@@ -764,9 +871,10 @@ function createPackingBingo(container, options = {}) {
|
|
|
764
871
|
const bonus = gotP ? 0 : 1e3;
|
|
765
872
|
gotP = true;
|
|
766
873
|
score += bonus;
|
|
767
|
-
|
|
874
|
+
cellTiles.forEach((t, c) => hop(t, far(c) * 180 / Math.max(1, n - 1)));
|
|
768
875
|
sfx.perfect();
|
|
769
876
|
if (!calm) replay($("flash"), "pkb-go");
|
|
877
|
+
$("pbody").textContent = perfectText(M, N);
|
|
770
878
|
$("sScore").textContent = "0";
|
|
771
879
|
$("sCombo").textContent = "\xD70";
|
|
772
880
|
$("sBingo").textContent = "0";
|
|
@@ -813,30 +921,32 @@ function createPackingBingo(container, options = {}) {
|
|
|
813
921
|
$("combo").classList.remove("pkb-on");
|
|
814
922
|
perfectEl.classList.remove("pkb-show");
|
|
815
923
|
}
|
|
924
|
+
const completeLines = () => lines.flatMap((l, k) => l.every((i) => checked[i]) ? [k] : []);
|
|
816
925
|
function render(src, quiet) {
|
|
817
926
|
const events = [];
|
|
818
927
|
const on = checked, c = on.filter(Boolean).length;
|
|
819
|
-
cur =
|
|
928
|
+
cur = completeLines();
|
|
820
929
|
const fresh = cur.filter((k) => !prevLines.has(k));
|
|
821
930
|
if ([...prevLines].some((k) => !cur.includes(k))) {
|
|
822
931
|
cancelFx();
|
|
823
|
-
if (c <
|
|
932
|
+
if (c < N) perfectEl.classList.remove("pkb-show");
|
|
824
933
|
}
|
|
825
|
-
const hit = new Set(cur.flatMap((k) =>
|
|
934
|
+
const hit = new Set(cur.flatMap((k) => cellLines[k]));
|
|
826
935
|
const reach = /* @__PURE__ */ new Set();
|
|
827
|
-
for (const l of
|
|
936
|
+
for (const l of lines) {
|
|
828
937
|
const m = l.filter((i) => !on[i]);
|
|
829
938
|
if (m.length === 1) reach.add(m[0]);
|
|
830
939
|
}
|
|
831
940
|
tiles.forEach((t, i) => {
|
|
832
941
|
t.classList.toggle("pkb-on", on[i]);
|
|
833
|
-
t.classList.toggle("pkb-line", hit.has(i));
|
|
942
|
+
t.classList.toggle("pkb-line", hit.has(L.itemCell[slotOf[i]]));
|
|
834
943
|
t.classList.toggle("pkb-reach", reach.has(i));
|
|
835
944
|
});
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
kick.
|
|
839
|
-
|
|
945
|
+
freeTile?.classList.toggle("pkb-line", hit.has(L.center));
|
|
946
|
+
board.classList.toggle("pkb-all", c === N);
|
|
947
|
+
kick.classList.toggle("pkb-gold", c === N);
|
|
948
|
+
kick.textContent = c === N ? M.perfectTag : M.tag;
|
|
949
|
+
$("cnt").textContent = `${c}/${N}`;
|
|
840
950
|
$("bng").textContent = String(cur.length);
|
|
841
951
|
const bp = $("bp");
|
|
842
952
|
if (cur.length > lastN) {
|
|
@@ -847,13 +957,13 @@ function createPackingBingo(container, options = {}) {
|
|
|
847
957
|
lastN = cur.length;
|
|
848
958
|
drawLines(fresh);
|
|
849
959
|
if (src !== null) tapFx(src, on[src]);
|
|
850
|
-
const newL = fresh.
|
|
851
|
-
newL.forEach((
|
|
960
|
+
const newL = fresh.map((k) => lineKey(lines[k])).filter((key) => !gotL.has(key));
|
|
961
|
+
newL.forEach((key) => gotL.add(key));
|
|
852
962
|
const linePts = 100 * newL.length * newL.length;
|
|
853
963
|
score += linePts;
|
|
854
964
|
if (quiet) {
|
|
855
965
|
if (linePts) countScore();
|
|
856
|
-
} else if (c ===
|
|
966
|
+
} else if (c === N && !prevAll) perfect(events);
|
|
857
967
|
else if (fresh.length) {
|
|
858
968
|
bingo(fresh, linePts);
|
|
859
969
|
const e = { newLines: fresh, totalLines: cur.length, score };
|
|
@@ -864,9 +974,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
864
974
|
}
|
|
865
975
|
prevLines = new Set(cur);
|
|
866
976
|
prevReach = reach;
|
|
867
|
-
prevAll = c ===
|
|
977
|
+
prevAll = c === N;
|
|
868
978
|
const labels = [...reach].map((i) => items[i].label);
|
|
869
|
-
$("msg").textContent = c ===
|
|
979
|
+
$("msg").textContent = c === N ? M.statusDone : labels.length ? M.statusReach(labels) : cur.length ? M.statusLines(cur.length, N - c) : M.statusStart(N - c);
|
|
870
980
|
return events;
|
|
871
981
|
}
|
|
872
982
|
const flush = (events) => {
|
|
@@ -893,8 +1003,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
893
1003
|
got.clear();
|
|
894
1004
|
gotL.clear();
|
|
895
1005
|
checked.forEach((v, i) => v && got.add(i));
|
|
896
|
-
|
|
897
|
-
|
|
1006
|
+
const done = completeLines();
|
|
1007
|
+
done.forEach((k) => gotL.add(lineKey(lines[k])));
|
|
1008
|
+
prevLines = new Set(done);
|
|
898
1009
|
prevReach = /* @__PURE__ */ new Set();
|
|
899
1010
|
prevAll = checked.every(Boolean);
|
|
900
1011
|
gotP = prevAll;
|
|
@@ -917,6 +1028,77 @@ function createPackingBingo(container, options = {}) {
|
|
|
917
1028
|
options.onChange?.(checked.slice());
|
|
918
1029
|
flush(events);
|
|
919
1030
|
}
|
|
1031
|
+
function place(next) {
|
|
1032
|
+
order = next;
|
|
1033
|
+
order.forEach((i, s) => {
|
|
1034
|
+
slotOf[i] = s;
|
|
1035
|
+
cellTiles[L.itemCell[s]] = tiles[i];
|
|
1036
|
+
});
|
|
1037
|
+
lines = L.lines.map((l) => l.map((s) => order[s]));
|
|
1038
|
+
const focused = document.activeElement;
|
|
1039
|
+
cellTiles.forEach((t, c) => {
|
|
1040
|
+
t.style.setProperty("--pkb-gd", glintDelay(c, n));
|
|
1041
|
+
grid.appendChild(t);
|
|
1042
|
+
});
|
|
1043
|
+
if (focused instanceof HTMLElement && grid.contains(focused) && document.activeElement !== focused)
|
|
1044
|
+
focused.focus({ preventScroll: true });
|
|
1045
|
+
}
|
|
1046
|
+
const saveOrder = () => {
|
|
1047
|
+
if (!orderKey) return;
|
|
1048
|
+
writeStore(orderKey, JSON.stringify(order));
|
|
1049
|
+
};
|
|
1050
|
+
let flights = [];
|
|
1051
|
+
function arrange(next) {
|
|
1052
|
+
if (!checked.every(Boolean)) cancelFx();
|
|
1053
|
+
const from = calm ? null : tiles.map((t) => t.getBoundingClientRect());
|
|
1054
|
+
flights.forEach((a) => a.cancel());
|
|
1055
|
+
flights = [];
|
|
1056
|
+
place(next);
|
|
1057
|
+
lines.forEach((l) => {
|
|
1058
|
+
if (l.every((i) => got.has(i))) gotL.add(lineKey(l));
|
|
1059
|
+
});
|
|
1060
|
+
const done = completeLines();
|
|
1061
|
+
done.forEach((k) => gotL.add(lineKey(lines[k])));
|
|
1062
|
+
prevLines = new Set(done);
|
|
1063
|
+
render(null, true);
|
|
1064
|
+
saveOrder();
|
|
1065
|
+
if (!from || typeof Element.prototype.animate !== "function") return;
|
|
1066
|
+
tiles.forEach((t, i) => {
|
|
1067
|
+
const a = from[i], b = t.getBoundingClientRect();
|
|
1068
|
+
const dx = a.left - b.left, dy = a.top - b.top;
|
|
1069
|
+
if (Math.abs(dx) + Math.abs(dy) < 1) return;
|
|
1070
|
+
flights.push(
|
|
1071
|
+
t.animate([{ transform: `translate(${dx}px,${dy}px)` }, { transform: "none" }], {
|
|
1072
|
+
duration: FLY_MS - 140,
|
|
1073
|
+
delay: Math.random() * 140,
|
|
1074
|
+
easing: "cubic-bezier(.3,1.25,.5,1)",
|
|
1075
|
+
fill: "backwards"
|
|
1076
|
+
})
|
|
1077
|
+
);
|
|
1078
|
+
});
|
|
1079
|
+
clearTimeout(landing);
|
|
1080
|
+
landing = window.setTimeout(() => {
|
|
1081
|
+
landing = 0;
|
|
1082
|
+
flights = [];
|
|
1083
|
+
if (!dead) drawLines(cur);
|
|
1084
|
+
}, FLY_MS);
|
|
1085
|
+
svg.innerHTML = "";
|
|
1086
|
+
}
|
|
1087
|
+
function shuffleCard() {
|
|
1088
|
+
let next = order.slice();
|
|
1089
|
+
do {
|
|
1090
|
+
for (let s = N - 1; s > 0; s--) {
|
|
1091
|
+
const r = Math.floor(Math.random() * (s + 1));
|
|
1092
|
+
[next[s], next[r]] = [next[r], next[s]];
|
|
1093
|
+
}
|
|
1094
|
+
} while (next.every((v, s) => v === order[s]));
|
|
1095
|
+
arrange(next);
|
|
1096
|
+
sfx.shuffle();
|
|
1097
|
+
buzz([10, 40, 10, 40, 10, 40, 24]);
|
|
1098
|
+
}
|
|
1099
|
+
const unshuffle = () => {
|
|
1100
|
+
if (!isIdentity(order)) arrange(identity());
|
|
1101
|
+
};
|
|
920
1102
|
grid.addEventListener("change", (e) => {
|
|
921
1103
|
const i = ins.indexOf(e.target);
|
|
922
1104
|
if (i < 0) return;
|
|
@@ -924,7 +1106,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
924
1106
|
next[i] = !next[i];
|
|
925
1107
|
request(next, i);
|
|
926
1108
|
});
|
|
927
|
-
|
|
1109
|
+
container.querySelector('[data-r="shuf"]')?.addEventListener("click", shuffleCard);
|
|
1110
|
+
$("reset").addEventListener("click", () => {
|
|
1111
|
+
unshuffle();
|
|
1112
|
+
request(normalize(void 0), null, { reset: true });
|
|
1113
|
+
});
|
|
928
1114
|
$("close").addEventListener("click", () => perfectEl.classList.remove("pkb-show"));
|
|
929
1115
|
perfectEl.addEventListener("click", (e) => {
|
|
930
1116
|
if (e.target === perfectEl) perfectEl.classList.remove("pkb-show");
|
|
@@ -953,18 +1139,31 @@ function createPackingBingo(container, options = {}) {
|
|
|
953
1139
|
}
|
|
954
1140
|
};
|
|
955
1141
|
$("dB").addEventListener("click", () => {
|
|
956
|
-
const open = () =>
|
|
957
|
-
const pick = checked.every(Boolean) ? [...
|
|
1142
|
+
const open = () => lines.map((l) => l.filter((i) => !checked[i])).filter((m) => m.length).sort((a, b) => a.length - b.length)[0] ?? [];
|
|
1143
|
+
const pick = checked.every(Boolean) ? [...lines[0]] : open();
|
|
958
1144
|
void run(pick.map((i) => ({ i, delay: 320 })));
|
|
959
1145
|
});
|
|
960
1146
|
$("dP").addEventListener("click", () => {
|
|
961
|
-
const
|
|
1147
|
+
const all = items.map((_, i) => i);
|
|
1148
|
+
const miss = checked.every(Boolean) ? all : all.filter((i) => !checked[i]);
|
|
962
1149
|
void run(miss.map((i, k) => k < miss.length - 1 ? { i, delay: 170, quiet: true } : { i, delay: 450 }));
|
|
963
1150
|
});
|
|
964
1151
|
$("dR").addEventListener("click", () => {
|
|
965
|
-
if (
|
|
1152
|
+
if (busy) return;
|
|
1153
|
+
unshuffle();
|
|
1154
|
+
request(demoStart(), null, { reset: true });
|
|
966
1155
|
});
|
|
967
1156
|
}
|
|
1157
|
+
let startOrder = null;
|
|
1158
|
+
if (orderKey) {
|
|
1159
|
+
try {
|
|
1160
|
+
const saved = JSON.parse(readStore(orderKey) ?? "null");
|
|
1161
|
+
if (validOrder(saved)) startOrder = saved;
|
|
1162
|
+
} catch {
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
if (!startOrder && validOrder(options.defaultOrder)) startOrder = options.defaultOrder.slice();
|
|
1166
|
+
if (startOrder && !isIdentity(startOrder)) place(startOrder);
|
|
968
1167
|
let initial = null;
|
|
969
1168
|
if (storageKey) {
|
|
970
1169
|
try {
|
|
@@ -986,29 +1185,45 @@ function createPackingBingo(container, options = {}) {
|
|
|
986
1185
|
return {
|
|
987
1186
|
setChecked(next, opts = {}) {
|
|
988
1187
|
if (dead) return;
|
|
989
|
-
const
|
|
990
|
-
const changed =
|
|
1188
|
+
const n2 = normalize(next);
|
|
1189
|
+
const changed = n2.flatMap((v, i) => v !== checked[i] ? [i] : []);
|
|
991
1190
|
if (!changed.length) return;
|
|
992
1191
|
if (!opts.animate || changed.length !== 1) {
|
|
993
|
-
baseline(
|
|
1192
|
+
baseline(n2);
|
|
994
1193
|
return;
|
|
995
1194
|
}
|
|
996
|
-
flush(apply(
|
|
1195
|
+
flush(apply(n2, changed[0], false));
|
|
997
1196
|
},
|
|
998
1197
|
reset() {
|
|
999
|
-
if (
|
|
1198
|
+
if (dead) return;
|
|
1199
|
+
unshuffle();
|
|
1200
|
+
request(normalize(void 0), null, { reset: true });
|
|
1201
|
+
},
|
|
1202
|
+
shuffle() {
|
|
1203
|
+
if (!dead) shuffleCard();
|
|
1000
1204
|
},
|
|
1001
1205
|
refresh() {
|
|
1002
1206
|
if (!dead) render(null, true);
|
|
1003
1207
|
},
|
|
1004
1208
|
getState() {
|
|
1005
|
-
return {
|
|
1209
|
+
return {
|
|
1210
|
+
checked: checked.slice(),
|
|
1211
|
+
lines: cur.slice(),
|
|
1212
|
+
score,
|
|
1213
|
+
maxCombo,
|
|
1214
|
+
complete: checked.every(Boolean),
|
|
1215
|
+
size: n,
|
|
1216
|
+
free: L.free,
|
|
1217
|
+
order: order.slice()
|
|
1218
|
+
};
|
|
1006
1219
|
},
|
|
1007
1220
|
destroy() {
|
|
1008
1221
|
if (dead) return;
|
|
1009
1222
|
dead = true;
|
|
1010
1223
|
timers.forEach((id) => clearTimeout(id));
|
|
1011
1224
|
timers.clear();
|
|
1225
|
+
clearTimeout(landing);
|
|
1226
|
+
flights.forEach((a) => a.cancel());
|
|
1012
1227
|
cancelAnimationFrame(raf);
|
|
1013
1228
|
cancelAnimationFrame(tw);
|
|
1014
1229
|
ro?.disconnect();
|
|
@@ -1021,7 +1236,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1021
1236
|
// src/react.tsx
|
|
1022
1237
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1023
1238
|
var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, ref) {
|
|
1024
|
-
const { checked, defaultChecked, className, style, onChange, onBingo, onPerfect, messages, ...config } = props;
|
|
1239
|
+
const { checked, defaultChecked, defaultOrder, className, style, onChange, onBingo, onPerfect, messages, fonts: _fonts, ...config } = props;
|
|
1025
1240
|
const host = (0, import_react.useRef)(null);
|
|
1026
1241
|
const inst = (0, import_react.useRef)(null);
|
|
1027
1242
|
const callbacks = (0, import_react.useRef)({ onChange, onBingo, onPerfect });
|
|
@@ -1033,6 +1248,7 @@ var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, re
|
|
|
1033
1248
|
const storageKey = resolveStorageKey({ ...config, controlled });
|
|
1034
1249
|
const latestKey = (0, import_react.useRef)(storageKey);
|
|
1035
1250
|
if (controlled) latest.current = checked;
|
|
1251
|
+
const latestOrder = (0, import_react.useRef)(defaultOrder);
|
|
1036
1252
|
const messageShape = messages ? Object.fromEntries(Object.entries(messages).map(([k, v]) => [k, typeof v === "function" ? "\u0192" : v])) : null;
|
|
1037
1253
|
const key = JSON.stringify({ ...config, messageShape, controlled });
|
|
1038
1254
|
(0, import_react.useEffect)(() => {
|
|
@@ -1048,6 +1264,7 @@ var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, re
|
|
|
1048
1264
|
messages: liveMessages,
|
|
1049
1265
|
controlled,
|
|
1050
1266
|
defaultChecked: controlled || latestKey.current === storageKey ? latest.current : defaultChecked,
|
|
1267
|
+
defaultOrder: latestKey.current === storageKey ? latestOrder.current : defaultOrder,
|
|
1051
1268
|
onChange: (next) => {
|
|
1052
1269
|
if (!controlled) latest.current = next;
|
|
1053
1270
|
callbacks.current.onChange?.(next);
|
|
@@ -1057,7 +1274,9 @@ var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, re
|
|
|
1057
1274
|
});
|
|
1058
1275
|
inst.current = instance;
|
|
1059
1276
|
return () => {
|
|
1060
|
-
|
|
1277
|
+
const state = instance.getState();
|
|
1278
|
+
if (!controlled) latest.current = state.checked;
|
|
1279
|
+
latestOrder.current = state.order;
|
|
1061
1280
|
latestKey.current = storageKey;
|
|
1062
1281
|
instance.destroy();
|
|
1063
1282
|
if (inst.current === instance) inst.current = null;
|
|
@@ -1073,6 +1292,7 @@ var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, re
|
|
|
1073
1292
|
ref,
|
|
1074
1293
|
() => ({
|
|
1075
1294
|
reset: () => inst.current?.reset(),
|
|
1295
|
+
shuffle: () => inst.current?.shuffle(),
|
|
1076
1296
|
setChecked: (next) => inst.current?.setChecked(next, { animate: true }),
|
|
1077
1297
|
getState: () => inst.current?.getState() ?? null
|
|
1078
1298
|
}),
|
|
@@ -1085,6 +1305,7 @@ var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, re
|
|
|
1085
1305
|
DEFAULT_ITEMS,
|
|
1086
1306
|
LINES,
|
|
1087
1307
|
PackingBingo,
|
|
1308
|
+
bingoLines,
|
|
1088
1309
|
createPackingBingo,
|
|
1089
1310
|
en,
|
|
1090
1311
|
ja
|