packing-bingo 0.1.1 → 0.3.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 +293 -185
- package/dist/{chunk-ZDBGKPFF.js → chunk-Z34GMJW2.js} +208 -108
- package/dist/core.cjs +208 -108
- package/dist/core.d.cts +51 -11
- package/dist/core.d.ts +51 -11
- package/dist/core.js +3 -1
- package/dist/index.cjs +212 -111
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +9 -5
- package/dist/packing-bingo.global.js +37 -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,29 +36,98 @@ 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
129
|
tag: "\u4FEE\u5B66\u65C5\u884C",
|
|
60
|
-
subtitle: "
|
|
130
|
+
subtitle: "",
|
|
61
131
|
packed: "\u305D\u308D\u3063\u305F",
|
|
62
132
|
bingo: "BINGO",
|
|
63
133
|
score: "SCORE",
|
|
@@ -67,6 +137,7 @@ var ja = {
|
|
|
67
137
|
soundOn: "\u{1F50A} \u97F3\u3042\u308A",
|
|
68
138
|
soundOff: "\u{1F507} \u97F3\u306A\u3057",
|
|
69
139
|
stamp: "OK",
|
|
140
|
+
free: "FREE",
|
|
70
141
|
reachBadge: "\u30EA\u30FC\u30C1",
|
|
71
142
|
reachPop: "\u30EA\u30FC\u30C1!",
|
|
72
143
|
bannerSingle: "BINGO!",
|
|
@@ -74,14 +145,15 @@ var ja = {
|
|
|
74
145
|
bannerTriple: "\u30C8\u30EA\u30D7\u30EB\u30D3\u30F3\u30B4!",
|
|
75
146
|
bannerNth: (n) => `${n} \u30E9\u30A4\u30F3\u76EE\uFF01`,
|
|
76
147
|
bannerMulti: (n) => `\u4E00\u6C17\u306B ${n} \u30E9\u30A4\u30F3\uFF01`,
|
|
77
|
-
statusStart: (
|
|
148
|
+
statusStart: () => "",
|
|
78
149
|
statusLines: (lines, left) => `\u30D3\u30F3\u30B4 ${lines} \u30E9\u30A4\u30F3\uFF01 \u306E\u3053\u308A ${left} \u3053`,
|
|
79
150
|
statusReach: (labels) => `\u30EA\u30FC\u30C1\uFF01 \u3042\u3068${labels.slice(0, 2).map((l) => `\u300C${l}\u300D`).join("\u304B")}${labels.length > 2 ? "\u306A\u3069" : ""}\u3067\u30D3\u30F3\u30B4`,
|
|
80
151
|
statusDone: "\u{1F389} \u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u51FA\u767A\u3067\u304D\u307E\u3059",
|
|
81
152
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
82
153
|
perfectStamp: "\u6E96\u5099\n\u5B8C\u4E86",
|
|
83
154
|
perfectTitle: "\u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01",
|
|
84
|
-
perfectBody:
|
|
155
|
+
perfectBody: (n) => `${n}\u3053 \u305C\u3093\u3076\u305D\u308D\u3063\u305F\uFF01
|
|
156
|
+
\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u3001\u3044\u3063\u3066\u3089\u3063\u3057\u3083\u3044\uFF01`,
|
|
85
157
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
86
158
|
statScore: "SCORE",
|
|
87
159
|
statCombo: "MAX COMBO",
|
|
@@ -95,7 +167,7 @@ var ja = {
|
|
|
95
167
|
var en = {
|
|
96
168
|
title: "Packing Bingo",
|
|
97
169
|
tag: "School trip",
|
|
98
|
-
subtitle: "
|
|
170
|
+
subtitle: "",
|
|
99
171
|
packed: "PACKED",
|
|
100
172
|
bingo: "BINGO",
|
|
101
173
|
score: "SCORE",
|
|
@@ -105,6 +177,7 @@ var en = {
|
|
|
105
177
|
soundOn: "\u{1F50A} Sound on",
|
|
106
178
|
soundOff: "\u{1F507} Muted",
|
|
107
179
|
stamp: "OK",
|
|
180
|
+
free: "FREE",
|
|
108
181
|
reachBadge: "REACH",
|
|
109
182
|
reachPop: "Reach!",
|
|
110
183
|
bannerSingle: "BINGO!",
|
|
@@ -112,14 +185,15 @@ var en = {
|
|
|
112
185
|
bannerTriple: "TRIPLE BINGO!",
|
|
113
186
|
bannerNth: (n) => `Line ${n}!`,
|
|
114
187
|
bannerMulti: (n) => `${n} lines at once!`,
|
|
115
|
-
statusStart: (
|
|
188
|
+
statusStart: () => "",
|
|
116
189
|
statusLines: (lines, left) => `${lines} bingo line${lines === 1 ? "" : "s"}! ${left} to go`,
|
|
117
190
|
statusReach: (labels) => `Reach! Pack ${labels.slice(0, 2).join(" or ")}${labels.length > 2 ? " (or more)" : ""} for a bingo`,
|
|
118
191
|
statusDone: "\u{1F389} Perfect! Nothing left behind.",
|
|
119
192
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
120
193
|
perfectStamp: "ALL\nSET",
|
|
121
194
|
perfectTitle: "Perfect!",
|
|
122
|
-
perfectBody:
|
|
195
|
+
perfectBody: (n) => `All ${n} packed!
|
|
196
|
+
Have a great trip!`,
|
|
123
197
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
124
198
|
statScore: "SCORE",
|
|
125
199
|
statCombo: "MAX COMBO",
|
|
@@ -228,17 +302,34 @@ function createSound(isMuted) {
|
|
|
228
302
|
};
|
|
229
303
|
}
|
|
230
304
|
|
|
305
|
+
// src/storage-key.ts
|
|
306
|
+
function resolveStorageKey(options) {
|
|
307
|
+
if (options.controlled) return null;
|
|
308
|
+
if (options.storageKey !== void 0) return options.storageKey || null;
|
|
309
|
+
if (options.demo) return null;
|
|
310
|
+
return itemsKey(layoutItems(options));
|
|
311
|
+
}
|
|
312
|
+
function itemsKey(items) {
|
|
313
|
+
const text = items.map((it) => `${it.emoji}\0${it.label}`).join("");
|
|
314
|
+
let h = 2166136261;
|
|
315
|
+
for (let i = 0; i < text.length; i++) {
|
|
316
|
+
h ^= text.charCodeAt(i);
|
|
317
|
+
h = Math.imul(h, 16777619);
|
|
318
|
+
}
|
|
319
|
+
return `packing-bingo:${(h >>> 0).toString(36)}`;
|
|
320
|
+
}
|
|
321
|
+
|
|
231
322
|
// src/styles.ts
|
|
232
323
|
var FONT_URL = "https://fonts.googleapis.com/css2?family=Dela+Gothic+One&family=M+PLUS+Rounded+1c:wght@500;800&display=swap";
|
|
233
324
|
var CSS = `
|
|
234
|
-
.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%}
|
|
325
|
+
.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%}
|
|
235
326
|
.pkb,.pkb *{box-sizing:border-box;margin:0;padding:0}
|
|
236
327
|
.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}
|
|
237
328
|
.pkb-board.pkb-shake{animation:pkb-shake .45s cubic-bezier(.36,.07,.19,.97)}
|
|
238
329
|
@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)}}
|
|
239
330
|
.pkb-board.pkb-punch{animation:pkb-punch .4s cubic-bezier(.3,1.6,.5,1)}
|
|
240
331
|
@keyframes pkb-punch{30%{transform:scale(1.018)}}
|
|
241
|
-
.pkb-head{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-bottom:18px}
|
|
332
|
+
.pkb-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px 16px;margin-bottom:18px}
|
|
242
333
|
.pkb-tags{display:flex;gap:8px;align-items:center}
|
|
243
334
|
.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}
|
|
244
335
|
.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}
|
|
@@ -246,7 +337,7 @@ var CSS = `
|
|
|
246
337
|
.pkb button{font-family:var(--pk-body)}
|
|
247
338
|
.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}
|
|
248
339
|
.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}
|
|
249
|
-
.pkb-sub{font-weight:800;font-size:13px;margin-top:4px}
|
|
340
|
+
.pkb-sub{font-weight:800;font-size:13px;margin-top:4px;word-break:auto-phrase}
|
|
250
341
|
.pkb-side{display:flex;flex-direction:column;gap:8px;flex:none}
|
|
251
342
|
.pkb-score{display:flex;gap:10px}
|
|
252
343
|
.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)}
|
|
@@ -265,16 +356,18 @@ var CSS = `
|
|
|
265
356
|
.pkb-combo.pkb-on .pkb-bar i{animation:pkb-drain 2.5s linear forwards}
|
|
266
357
|
@keyframes pkb-drain{from{transform:scaleX(1)}to{transform:scaleX(0)}}
|
|
267
358
|
.pkb-gw{position:relative}
|
|
268
|
-
.pkb-grid{display:grid;grid-template-columns:repeat(
|
|
269
|
-
.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}
|
|
270
|
-
.pkb-tile:active{transform:translate(3px,3px);box-shadow:1px 1px 0 var(--pk-ink)}
|
|
359
|
+
.pkb-grid{display:grid;grid-template-columns:repeat(var(--pk-n),minmax(0,1fr));gap:calc(12px*var(--pk-d))}
|
|
360
|
+
.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}
|
|
361
|
+
.pkb-tile:active:not(.pkb-free){transform:translate(3px,3px);box-shadow:1px 1px 0 var(--pk-ink)}
|
|
271
362
|
.pkb-tile input{position:absolute;opacity:0;pointer-events:none;width:1px;height:1px}
|
|
272
363
|
.pkb-tile:has(input:focus-visible){outline:3px solid var(--pk-ink);outline-offset:3px}
|
|
273
|
-
.pkb-emo{font-size:32px;line-height:1;transition:opacity .2s}
|
|
274
|
-
.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}
|
|
275
|
-
.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}
|
|
364
|
+
.pkb-emo{font-size:max(14px,calc(32px*var(--pk-s)));line-height:1;transition:opacity .2s}
|
|
365
|
+
.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}
|
|
366
|
+
.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}
|
|
276
367
|
.pkb-tile.pkb-on .pkb-stamp{opacity:.9;transform:translate(-50%,-50%) rotate(-16deg) scale(1)}
|
|
277
368
|
.pkb-tile.pkb-on .pkb-emo,.pkb-tile.pkb-on .pkb-lbl{opacity:.35}
|
|
369
|
+
.pkb-tile.pkb-free{cursor:default;container-type:inline-size}
|
|
370
|
+
.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)}
|
|
278
371
|
.pkb-tile.pkb-line{background-color:var(--pk-mint)}
|
|
279
372
|
.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}
|
|
280
373
|
@keyframes pkb-glint{0%{background-position:200% 0}30%,100%{background-position:-100% 0}}
|
|
@@ -291,11 +384,11 @@ var CSS = `
|
|
|
291
384
|
.pkb-lines{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible;z-index:2;transition:opacity .4s}
|
|
292
385
|
.pkb-board.pkb-all .pkb-lines{opacity:0}
|
|
293
386
|
.pkb-lines line{fill:none;stroke-linecap:round;stroke-dasharray:1;stroke-dashoffset:0}
|
|
294
|
-
.pkb-lines .pkb-u{stroke:var(--pk-paper);stroke-width:
|
|
295
|
-
.pkb-lines .pkb-o{stroke:var(--pk-ink);stroke-width:
|
|
387
|
+
.pkb-lines .pkb-u{stroke:var(--pk-paper);stroke-width:calc(13px*var(--pk-d))}
|
|
388
|
+
.pkb-lines .pkb-o{stroke:var(--pk-ink);stroke-width:calc(6px*var(--pk-d))}
|
|
296
389
|
.pkb-lines .pkb-draw line{animation:pkb-draw .45s ease-out .3s both}
|
|
297
390
|
@keyframes pkb-draw{from{stroke-dashoffset:1}}
|
|
298
|
-
.pkb-foot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:18px;font-weight:800;font-size:13px}
|
|
391
|
+
.pkb-foot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:18px;font-weight:800;font-size:13px;word-break:auto-phrase}
|
|
299
392
|
.pkb-reset{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}
|
|
300
393
|
.pkb-flash{position:absolute;inset:0;z-index:7;background:#fff;opacity:0;pointer-events:none}
|
|
301
394
|
.pkb-flash.pkb-go{animation:pkb-flash .4s ease-out}
|
|
@@ -310,7 +403,7 @@ var CSS = `
|
|
|
310
403
|
.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}
|
|
311
404
|
.pkb-float.pkb-pt{font-size:22px;color:var(--pk-paper)}
|
|
312
405
|
.pkb-float.pkb-cmb{font-size:17px}
|
|
313
|
-
.pkb-float.pkb-big{font-size:clamp(
|
|
406
|
+
.pkb-float.pkb-big{font-size:clamp(14px,5.6cqi,34px);color:#FFE066}
|
|
314
407
|
.pkb-float.pkb-rch{font-size:20px;color:var(--pk-red);-webkit-text-stroke:4px var(--pk-paper)}
|
|
315
408
|
@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)}}
|
|
316
409
|
.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}
|
|
@@ -321,7 +414,7 @@ var CSS = `
|
|
|
321
414
|
.pkb-perfect.pkb-show .pkb-medal{animation:pkb-slam .38s cubic-bezier(.5,0,.75,0) .35s forwards}
|
|
322
415
|
@keyframes pkb-slam{to{opacity:1;transform:rotate(-14deg) scale(1)}}
|
|
323
416
|
.pkb-ptitle{font-family:var(--pk-display);font-weight:400;font-size:30px;line-height:1.3;color:inherit}
|
|
324
|
-
.pkb-pbody{font-weight:800;font-size:13.5px;line-height:1.6;margin:6px 0 14px;white-space:pre-line}
|
|
417
|
+
.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}
|
|
325
418
|
.pkb-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:16px}
|
|
326
419
|
.pkb-stats>div{border:2px solid var(--pk-ink);border-radius:12px;padding:6px 4px 5px;background:#fff}
|
|
327
420
|
.pkb-stats small{display:block;font-size:9.5px;font-weight:800;letter-spacing:.1em}
|
|
@@ -331,32 +424,20 @@ var CSS = `
|
|
|
331
424
|
.pkb-fx{position:absolute;inset:0;width:100%;height:100%;z-index:10;pointer-events:none}
|
|
332
425
|
.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}
|
|
333
426
|
.pkb-demo button{appearance:none;border:1px solid #ccc;background:#f3f3f3;color:#222;border-radius:999px;padding:5px 12px;font-size:12px;cursor:pointer}
|
|
334
|
-
@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:
|
|
427
|
+
@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}}
|
|
428
|
+
@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}}
|
|
429
|
+
@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}}
|
|
335
430
|
@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}}
|
|
336
431
|
@keyframes pkb-fade{0%,100%{opacity:0}15%,80%{opacity:1}}
|
|
337
432
|
`;
|
|
338
433
|
|
|
339
434
|
// src/core.ts
|
|
340
|
-
var LINES =
|
|
341
|
-
[0, 1, 2, 3],
|
|
342
|
-
[4, 5, 6, 7],
|
|
343
|
-
[8, 9, 10, 11],
|
|
344
|
-
[12, 13, 14, 15],
|
|
345
|
-
[0, 4, 8, 12],
|
|
346
|
-
[1, 5, 9, 13],
|
|
347
|
-
[2, 6, 10, 14],
|
|
348
|
-
[3, 7, 11, 15],
|
|
349
|
-
[0, 5, 10, 15],
|
|
350
|
-
[3, 6, 9, 12]
|
|
351
|
-
];
|
|
352
|
-
var DEMO_START = [0, 1, 2, 3, 5, 10];
|
|
435
|
+
var LINES = bingoLines(4);
|
|
353
436
|
var CONFETTI = ["#E8473B", "#2F6FED", "#1FA971", "#FFFDF6", "#2A1F14", "#FF9F1C"];
|
|
354
437
|
var COMBO_COLORS = ["#FF9F1C", "#1FA971", "#2F6FED", "#9B5DE5", "#E8473B"];
|
|
355
438
|
var COMBO_MS = 2500;
|
|
356
439
|
var MUTE_KEY = "packing-bingo-muted";
|
|
357
440
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
358
|
-
var normalize = (v) => Array.from({ length: 16 }, (_, i) => !!v?.[i]);
|
|
359
|
-
var demoStart = () => Array.from({ length: 16 }, (_, i) => DEMO_START.includes(i));
|
|
360
441
|
function readStore(key) {
|
|
361
442
|
try {
|
|
362
443
|
return localStorage.getItem(key);
|
|
@@ -385,15 +466,21 @@ function ensureAssets(fonts) {
|
|
|
385
466
|
document.head.appendChild(l);
|
|
386
467
|
}
|
|
387
468
|
}
|
|
388
|
-
function template(
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
469
|
+
function template(L, M, demo) {
|
|
470
|
+
const n = L.size;
|
|
471
|
+
const tiles = Array.from({ length: n * n }, (_, c) => {
|
|
472
|
+
const gd = `--pkb-gd:${+((Math.floor(c / n) + c % n) * 0.36 / Math.max(1, n - 1)).toFixed(3)}s`;
|
|
473
|
+
if (c === L.center)
|
|
474
|
+
return `<div class="pkb-tile pkb-free pkb-on" style="${gd}"><span class="pkb-stamp">${esc(M.free)}</span></div>`;
|
|
475
|
+
const it = L.items[L.itemCell.indexOf(c)];
|
|
476
|
+
return `<label class="pkb-tile" data-reach="${esc(M.reachBadge)}" style="${gd}"><input type="checkbox"><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>`;
|
|
477
|
+
}).join("");
|
|
478
|
+
const scale = `--pk-n:${n};--pk-s:${+Math.min(1.35, 4 / n).toFixed(3)};--pk-d:${+Math.min(1, 4 / n).toFixed(3)}`;
|
|
392
479
|
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>` : "";
|
|
393
|
-
return `<div class="pkb"><div class="pkb-board">
|
|
480
|
+
return `<div class="pkb" style="${scale}"><div class="pkb-board">
|
|
394
481
|
<div class="pkb-head"><div>
|
|
395
482
|
<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>
|
|
396
|
-
<h2 class="pkb-title">${esc(M.title)}</h2
|
|
483
|
+
<h2 class="pkb-title">${esc(M.title)}</h2>${M.subtitle ? `<p class="pkb-sub">${esc(M.subtitle)}</p>` : ""}</div>
|
|
397
484
|
<div class="pkb-side"><div class="pkb-score">
|
|
398
485
|
<div class="pkb-pill"><small>${esc(M.packed)}</small><b data-r="cnt"></b></div>
|
|
399
486
|
<div class="pkb-pill" data-r="bp"><small>${esc(M.bingo)}</small><b data-r="bng"></b></div>
|
|
@@ -404,20 +491,28 @@ function template(items, M, demo) {
|
|
|
404
491
|
<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>
|
|
405
492
|
<div class="pkb-flash" data-r="flash"></div>
|
|
406
493
|
<div class="pkb-perfect" data-r="perfect"><div class="pkb-card" role="dialog" aria-label="${esc(M.perfectTitle)}">
|
|
407
|
-
<div class="pkb-medal">${esc(M.perfectStamp)}</div><h3 class="pkb-ptitle">${esc(M.perfectTitle)}</h3><p class="pkb-pbody">${esc(M.
|
|
494
|
+
<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>
|
|
408
495
|
<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>
|
|
409
496
|
<button type="button" class="pkb-close" data-r="close">${esc(M.close)}</button></div></div>
|
|
410
497
|
<canvas class="pkb-fx"></canvas></div>${demoBar}</div>`;
|
|
411
498
|
}
|
|
499
|
+
var perfectText = (M, total) => typeof M.perfectBody === "function" ? M.perfectBody(total) : M.perfectBody;
|
|
412
500
|
function createPackingBingo(container, options = {}) {
|
|
413
|
-
const
|
|
414
|
-
|
|
501
|
+
const L = resolveLayout(options);
|
|
502
|
+
const { items, lines, cellLines } = L;
|
|
503
|
+
const n = L.size, N = items.length;
|
|
504
|
+
const normalize = (v) => Array.from({ length: N }, (_, i) => !!v?.[i]);
|
|
505
|
+
const demoCells = [...cellLines[0], ...cellLines[2 * n].slice(1, -1)];
|
|
506
|
+
const demoItems = new Set(L.itemCell.flatMap((c, i) => demoCells.includes(c) ? [i] : []));
|
|
507
|
+
const demoStart = () => Array.from({ length: N }, (_, i) => demoItems.has(i));
|
|
508
|
+
const far = (c) => Math.floor(c / n) + c % n;
|
|
509
|
+
const dScale = Math.min(1, 4 / n);
|
|
415
510
|
const M = { ...ja, ...options.messages };
|
|
416
511
|
if (options.title != null) M.title = options.title;
|
|
417
512
|
if (options.tag != null) M.tag = options.tag;
|
|
418
513
|
if (options.subtitle != null) M.subtitle = options.subtitle;
|
|
419
514
|
const controlled = !!options.controlled;
|
|
420
|
-
const storageKey =
|
|
515
|
+
const storageKey = resolveStorageKey(options);
|
|
421
516
|
const demo = !!options.demo;
|
|
422
517
|
const calm = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
423
518
|
let dead = false;
|
|
@@ -432,7 +527,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
432
527
|
const wait = (ms) => new Promise((r) => window.setTimeout(r, ms));
|
|
433
528
|
let gen = 0;
|
|
434
529
|
ensureAssets(options.fonts !== false);
|
|
435
|
-
container.innerHTML = template(
|
|
530
|
+
container.innerHTML = template(L, M, demo);
|
|
436
531
|
const $ = (r) => {
|
|
437
532
|
const el = container.querySelector(`[data-r="${r}"]`);
|
|
438
533
|
if (!el) throw new Error(`[packing-bingo] internal: missing element "${r}"`);
|
|
@@ -444,7 +539,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
444
539
|
const svg = container.querySelector(".pkb-lines");
|
|
445
540
|
const cv = container.querySelector(".pkb-fx");
|
|
446
541
|
const ctx = cv.getContext("2d");
|
|
447
|
-
const
|
|
542
|
+
const cellTiles = Array.from(grid.children);
|
|
543
|
+
const tiles = L.itemCell.map((c) => cellTiles[c]);
|
|
544
|
+
const freeTile = L.center >= 0 ? cellTiles[L.center] : null;
|
|
448
545
|
const ins = tiles.map((t) => t.querySelector("input"));
|
|
449
546
|
const kick = $("kick"), snd = $("snd"), perfectEl = $("perfect");
|
|
450
547
|
let checked = normalize(void 0);
|
|
@@ -479,14 +576,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
479
576
|
cv.width = Math.round(BW * dpr);
|
|
480
577
|
cv.height = Math.round(BH * dpr);
|
|
481
578
|
};
|
|
482
|
-
const piece = (x, y, vx, vy, g, s, life,
|
|
579
|
+
const piece = (x, y, vx, vy, g, s, life, shape2) => ({
|
|
483
580
|
x,
|
|
484
581
|
y,
|
|
485
582
|
vx,
|
|
486
583
|
vy,
|
|
487
584
|
g,
|
|
488
585
|
life,
|
|
489
|
-
shape,
|
|
586
|
+
shape: shape2,
|
|
490
587
|
w: (5 + Math.random() * 6) * s,
|
|
491
588
|
h: (3 + Math.random() * 5) * s,
|
|
492
589
|
r: Math.random() * 6,
|
|
@@ -496,9 +593,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
496
593
|
const go = () => {
|
|
497
594
|
if (!raf && ctx) raf = requestAnimationFrame(tick);
|
|
498
595
|
};
|
|
499
|
-
function burst(x, y,
|
|
596
|
+
function burst(x, y, n2, o = {}) {
|
|
500
597
|
if (calm) return;
|
|
501
|
-
for (let i = 0; i <
|
|
598
|
+
for (let i = 0; i < n2; i++) {
|
|
502
599
|
const a = (o.angle ?? -Math.PI / 2) + (Math.random() - 0.5) * (o.spread ?? Math.PI * 2);
|
|
503
600
|
const v = (o.speed ?? 10) * (0.35 + Math.random() * 0.85);
|
|
504
601
|
const sh = Math.random() < (o.star ?? 0.15) ? "star" : Math.random() < 0.3 ? "round" : "rect";
|
|
@@ -589,11 +686,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
589
686
|
void el.offsetWidth;
|
|
590
687
|
el.classList.add(cls);
|
|
591
688
|
};
|
|
592
|
-
const hop = (
|
|
689
|
+
const hop = (t, d) => {
|
|
593
690
|
if (calm) return;
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
replay(
|
|
691
|
+
t.classList.remove("pkb-squash");
|
|
692
|
+
t.style.setProperty("--pkb-d", d + "ms");
|
|
693
|
+
replay(t, "pkb-hop");
|
|
597
694
|
};
|
|
598
695
|
grid.addEventListener("animationend", (e) => {
|
|
599
696
|
const t = e.target;
|
|
@@ -641,8 +738,8 @@ function createPackingBingo(container, options = {}) {
|
|
|
641
738
|
st(t0);
|
|
642
739
|
if (!calm) replay($("sp"), "pkb-bump");
|
|
643
740
|
}
|
|
644
|
-
const showGain = (
|
|
645
|
-
floatText(`+${
|
|
741
|
+
const showGain = (pts, x, y, cls = "pt") => {
|
|
742
|
+
floatText(`+${pts}`, x, y, cls);
|
|
646
743
|
countScore();
|
|
647
744
|
};
|
|
648
745
|
function countUp(el, a, b, d, fmt, ticks = false) {
|
|
@@ -662,15 +759,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
662
759
|
};
|
|
663
760
|
st(t0);
|
|
664
761
|
}
|
|
665
|
-
const ctr = (
|
|
666
|
-
const t = tiles[i];
|
|
762
|
+
const ctr = (t) => {
|
|
667
763
|
return { x: t.offsetLeft + t.offsetWidth / 2, y: t.offsetTop + t.offsetHeight / 2 };
|
|
668
764
|
};
|
|
669
765
|
function drawLines(fresh = []) {
|
|
670
766
|
svg.setAttribute("viewBox", `0 0 ${gw.clientWidth} ${gw.clientHeight}`);
|
|
671
767
|
svg.innerHTML = cur.map((k) => {
|
|
672
|
-
const a = ctr(
|
|
673
|
-
const dx = b.x - a.x, dy = b.y - a.y, d = Math.hypot(dx, dy) || 1, ex = dx / d *
|
|
768
|
+
const a = ctr(cellTiles[cellLines[k][0]]), b = ctr(cellTiles[cellLines[k][n - 1]]);
|
|
769
|
+
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;
|
|
674
770
|
const at = `x1="${a.x - ex}" y1="${a.y - ey}" x2="${b.x + ex}" y2="${b.y + ey}" pathLength="1"`;
|
|
675
771
|
return `<g${fresh.includes(k) ? ' class="pkb-draw"' : ""}><line class="pkb-u" ${at}/><line class="pkb-o" ${at}/></g>`;
|
|
676
772
|
}).join("");
|
|
@@ -717,29 +813,29 @@ function createPackingBingo(container, options = {}) {
|
|
|
717
813
|
later(() => sfx.reach(), 150);
|
|
718
814
|
}
|
|
719
815
|
function bingo(fresh, pts) {
|
|
720
|
-
const
|
|
721
|
-
const seq = [...new Set(fresh.flatMap((k) =>
|
|
816
|
+
const nl = fresh.length;
|
|
817
|
+
const seq = [...new Set(fresh.flatMap((k) => cellLines[k]))].map((c) => cellTiles[c]);
|
|
722
818
|
const step = Math.min(70, 300 / seq.length);
|
|
723
819
|
seq.forEach(
|
|
724
|
-
(
|
|
725
|
-
hop(
|
|
726
|
-
const p = rel(
|
|
820
|
+
(t, j) => later(() => {
|
|
821
|
+
hop(t, 0);
|
|
822
|
+
const p = rel(t);
|
|
727
823
|
burst(p.x, p.y, 14, { speed: 5, g: 0.15, life: 30, size: 0.7, star: 0.8 });
|
|
728
824
|
sfx.note(j + 3);
|
|
729
825
|
}, j * step)
|
|
730
826
|
);
|
|
731
827
|
later(() => {
|
|
732
|
-
$("bT").textContent =
|
|
733
|
-
$("bS").textContent =
|
|
828
|
+
$("bT").textContent = nl >= 3 ? M.bannerTriple : nl === 2 ? M.bannerDouble : M.bannerSingle;
|
|
829
|
+
$("bS").textContent = nl > 1 ? M.bannerMulti(nl) : M.bannerNth(cur.length);
|
|
734
830
|
replay($("banner"), "pkb-show");
|
|
735
831
|
if (!calm) {
|
|
736
832
|
replay($("flash"), "pkb-go");
|
|
737
833
|
replay(board, "pkb-punch");
|
|
738
834
|
}
|
|
739
835
|
const c = rel(gw);
|
|
740
|
-
burst(c.x, c.y,
|
|
741
|
-
sfx.bingo(
|
|
742
|
-
buzz(
|
|
836
|
+
burst(c.x, c.y, nl > 1 ? 180 : 110, { speed: nl > 1 ? 16 : 13, star: 0.3 });
|
|
837
|
+
sfx.bingo(nl);
|
|
838
|
+
buzz(nl > 1 ? [40, 60, 40, 60, 90] : [40, 60, 40]);
|
|
743
839
|
if (pts) later(() => showGain(pts, c.x, c.y + 105, "big"), 350);
|
|
744
840
|
}, seq.length * step + 60);
|
|
745
841
|
}
|
|
@@ -747,9 +843,10 @@ function createPackingBingo(container, options = {}) {
|
|
|
747
843
|
const bonus = gotP ? 0 : 1e3;
|
|
748
844
|
gotP = true;
|
|
749
845
|
score += bonus;
|
|
750
|
-
|
|
846
|
+
cellTiles.forEach((t, c) => hop(t, far(c) * 180 / Math.max(1, n - 1)));
|
|
751
847
|
sfx.perfect();
|
|
752
848
|
if (!calm) replay($("flash"), "pkb-go");
|
|
849
|
+
$("pbody").textContent = perfectText(M, N);
|
|
753
850
|
$("sScore").textContent = "0";
|
|
754
851
|
$("sCombo").textContent = "\xD70";
|
|
755
852
|
$("sBingo").textContent = "0";
|
|
@@ -799,27 +896,28 @@ function createPackingBingo(container, options = {}) {
|
|
|
799
896
|
function render(src, quiet) {
|
|
800
897
|
const events = [];
|
|
801
898
|
const on = checked, c = on.filter(Boolean).length;
|
|
802
|
-
cur =
|
|
899
|
+
cur = lines.flatMap((l, k) => l.every((i) => on[i]) ? [k] : []);
|
|
803
900
|
const fresh = cur.filter((k) => !prevLines.has(k));
|
|
804
901
|
if ([...prevLines].some((k) => !cur.includes(k))) {
|
|
805
902
|
cancelFx();
|
|
806
|
-
if (c <
|
|
903
|
+
if (c < N) perfectEl.classList.remove("pkb-show");
|
|
807
904
|
}
|
|
808
|
-
const hit = new Set(cur.flatMap((k) =>
|
|
905
|
+
const hit = new Set(cur.flatMap((k) => cellLines[k]));
|
|
809
906
|
const reach = /* @__PURE__ */ new Set();
|
|
810
|
-
for (const l of
|
|
907
|
+
for (const l of lines) {
|
|
811
908
|
const m = l.filter((i) => !on[i]);
|
|
812
909
|
if (m.length === 1) reach.add(m[0]);
|
|
813
910
|
}
|
|
814
911
|
tiles.forEach((t, i) => {
|
|
815
912
|
t.classList.toggle("pkb-on", on[i]);
|
|
816
|
-
t.classList.toggle("pkb-line", hit.has(i));
|
|
913
|
+
t.classList.toggle("pkb-line", hit.has(L.itemCell[i]));
|
|
817
914
|
t.classList.toggle("pkb-reach", reach.has(i));
|
|
818
915
|
});
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
kick.
|
|
822
|
-
|
|
916
|
+
freeTile?.classList.toggle("pkb-line", hit.has(L.center));
|
|
917
|
+
board.classList.toggle("pkb-all", c === N);
|
|
918
|
+
kick.classList.toggle("pkb-gold", c === N);
|
|
919
|
+
kick.textContent = c === N ? M.perfectTag : M.tag;
|
|
920
|
+
$("cnt").textContent = `${c}/${N}`;
|
|
823
921
|
$("bng").textContent = String(cur.length);
|
|
824
922
|
const bp = $("bp");
|
|
825
923
|
if (cur.length > lastN) {
|
|
@@ -836,7 +934,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
836
934
|
score += linePts;
|
|
837
935
|
if (quiet) {
|
|
838
936
|
if (linePts) countScore();
|
|
839
|
-
} else if (c ===
|
|
937
|
+
} else if (c === N && !prevAll) perfect(events);
|
|
840
938
|
else if (fresh.length) {
|
|
841
939
|
bingo(fresh, linePts);
|
|
842
940
|
const e = { newLines: fresh, totalLines: cur.length, score };
|
|
@@ -847,9 +945,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
847
945
|
}
|
|
848
946
|
prevLines = new Set(cur);
|
|
849
947
|
prevReach = reach;
|
|
850
|
-
prevAll = c ===
|
|
948
|
+
prevAll = c === N;
|
|
851
949
|
const labels = [...reach].map((i) => items[i].label);
|
|
852
|
-
$("msg").textContent = c ===
|
|
950
|
+
$("msg").textContent = c === N ? M.statusDone : labels.length ? M.statusReach(labels) : cur.length ? M.statusLines(cur.length, N - c) : M.statusStart(N - c);
|
|
853
951
|
return events;
|
|
854
952
|
}
|
|
855
953
|
const flush = (events) => {
|
|
@@ -876,7 +974,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
876
974
|
got.clear();
|
|
877
975
|
gotL.clear();
|
|
878
976
|
checked.forEach((v, i) => v && got.add(i));
|
|
879
|
-
|
|
977
|
+
lines.forEach((l, k) => l.every((i) => checked[i]) && gotL.add(k));
|
|
880
978
|
prevLines = new Set(gotL);
|
|
881
979
|
prevReach = /* @__PURE__ */ new Set();
|
|
882
980
|
prevAll = checked.every(Boolean);
|
|
@@ -936,12 +1034,13 @@ function createPackingBingo(container, options = {}) {
|
|
|
936
1034
|
}
|
|
937
1035
|
};
|
|
938
1036
|
$("dB").addEventListener("click", () => {
|
|
939
|
-
const open = () =>
|
|
940
|
-
const pick = checked.every(Boolean) ? [...
|
|
1037
|
+
const open = () => lines.map((l) => l.filter((i) => !checked[i])).filter((m) => m.length).sort((a, b) => a.length - b.length)[0] ?? [];
|
|
1038
|
+
const pick = checked.every(Boolean) ? [...lines[0]] : open();
|
|
941
1039
|
void run(pick.map((i) => ({ i, delay: 320 })));
|
|
942
1040
|
});
|
|
943
1041
|
$("dP").addEventListener("click", () => {
|
|
944
|
-
const
|
|
1042
|
+
const all = items.map((_, i) => i);
|
|
1043
|
+
const miss = checked.every(Boolean) ? all : all.filter((i) => !checked[i]);
|
|
945
1044
|
void run(miss.map((i, k) => k < miss.length - 1 ? { i, delay: 170, quiet: true } : { i, delay: 450 }));
|
|
946
1045
|
});
|
|
947
1046
|
$("dR").addEventListener("click", () => {
|
|
@@ -969,14 +1068,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
969
1068
|
return {
|
|
970
1069
|
setChecked(next, opts = {}) {
|
|
971
1070
|
if (dead) return;
|
|
972
|
-
const
|
|
973
|
-
const changed =
|
|
1071
|
+
const n2 = normalize(next);
|
|
1072
|
+
const changed = n2.flatMap((v, i) => v !== checked[i] ? [i] : []);
|
|
974
1073
|
if (!changed.length) return;
|
|
975
1074
|
if (!opts.animate || changed.length !== 1) {
|
|
976
|
-
baseline(
|
|
1075
|
+
baseline(n2);
|
|
977
1076
|
return;
|
|
978
1077
|
}
|
|
979
|
-
flush(apply(
|
|
1078
|
+
flush(apply(n2, changed[0], false));
|
|
980
1079
|
},
|
|
981
1080
|
reset() {
|
|
982
1081
|
if (!dead) request(normalize(void 0), null, { reset: true });
|
|
@@ -985,7 +1084,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
985
1084
|
if (!dead) render(null, true);
|
|
986
1085
|
},
|
|
987
1086
|
getState() {
|
|
988
|
-
return { checked: checked.slice(), lines: cur.slice(), score, maxCombo, complete: checked.every(Boolean) };
|
|
1087
|
+
return { checked: checked.slice(), lines: cur.slice(), score, maxCombo, complete: checked.every(Boolean), size: n, free: L.free };
|
|
989
1088
|
},
|
|
990
1089
|
destroy() {
|
|
991
1090
|
if (dead) return;
|
|
@@ -1013,7 +1112,8 @@ var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, re
|
|
|
1013
1112
|
messagesRef.current = messages;
|
|
1014
1113
|
const controlled = checked !== void 0;
|
|
1015
1114
|
const latest = (0, import_react.useRef)(checked ?? defaultChecked);
|
|
1016
|
-
const
|
|
1115
|
+
const storageKey = resolveStorageKey({ ...config, controlled });
|
|
1116
|
+
const latestKey = (0, import_react.useRef)(storageKey);
|
|
1017
1117
|
if (controlled) latest.current = checked;
|
|
1018
1118
|
const messageShape = messages ? Object.fromEntries(Object.entries(messages).map(([k, v]) => [k, typeof v === "function" ? "\u0192" : v])) : null;
|
|
1019
1119
|
const key = JSON.stringify({ ...config, messageShape, controlled });
|
|
@@ -1029,7 +1129,7 @@ var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, re
|
|
|
1029
1129
|
...config,
|
|
1030
1130
|
messages: liveMessages,
|
|
1031
1131
|
controlled,
|
|
1032
|
-
defaultChecked: controlled || latestKey.current ===
|
|
1132
|
+
defaultChecked: controlled || latestKey.current === storageKey ? latest.current : defaultChecked,
|
|
1033
1133
|
onChange: (next) => {
|
|
1034
1134
|
if (!controlled) latest.current = next;
|
|
1035
1135
|
callbacks.current.onChange?.(next);
|
|
@@ -1040,7 +1140,7 @@ var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, re
|
|
|
1040
1140
|
inst.current = instance;
|
|
1041
1141
|
return () => {
|
|
1042
1142
|
if (!controlled) latest.current = instance.getState().checked;
|
|
1043
|
-
latestKey.current =
|
|
1143
|
+
latestKey.current = storageKey;
|
|
1044
1144
|
instance.destroy();
|
|
1045
1145
|
if (inst.current === instance) inst.current = null;
|
|
1046
1146
|
};
|
|
@@ -1067,6 +1167,7 @@ var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, re
|
|
|
1067
1167
|
DEFAULT_ITEMS,
|
|
1068
1168
|
LINES,
|
|
1069
1169
|
PackingBingo,
|
|
1170
|
+
bingoLines,
|
|
1070
1171
|
createPackingBingo,
|
|
1071
1172
|
en,
|
|
1072
1173
|
ja
|