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/core.cjs
CHANGED
|
@@ -23,6 +23,7 @@ var core_exports = {};
|
|
|
23
23
|
__export(core_exports, {
|
|
24
24
|
DEFAULT_ITEMS: () => DEFAULT_ITEMS,
|
|
25
25
|
LINES: () => LINES,
|
|
26
|
+
bingoLines: () => bingoLines,
|
|
26
27
|
createPackingBingo: () => createPackingBingo,
|
|
27
28
|
en: () => en,
|
|
28
29
|
ja: () => ja
|
|
@@ -31,29 +32,98 @@ module.exports = __toCommonJS(core_exports);
|
|
|
31
32
|
|
|
32
33
|
// src/items.ts
|
|
33
34
|
var DEFAULT_ITEMS = [
|
|
34
|
-
{ emoji: "\u{
|
|
35
|
-
{ emoji: "\u{
|
|
36
|
-
{ emoji: "\u{
|
|
37
|
-
{ emoji: "\u{
|
|
38
|
-
{ emoji: "\u{
|
|
39
|
-
{ emoji: "\u{
|
|
40
|
-
{ emoji: "\u{1F9E5}", label: "\u4E0A\u7740" },
|
|
41
|
-
{ emoji: "\u{1FAA5}", label: "\u6D17\u9762\u7528\u5177" },
|
|
42
|
-
{ emoji: "\u{1F6C1}", label: "\u30BF\u30AA\u30EB" },
|
|
43
|
-
{ emoji: "\u{1F927}", label: "\u30CF\u30F3\u30AB\u30C1\u30FB\u30C6\u30A3\u30C3\u30B7\u30E5" },
|
|
35
|
+
{ emoji: "\u{1F45B}", label: "\u8CA1\u5E03" },
|
|
36
|
+
{ emoji: "\u{1F4F1}", label: "\u30B9\u30DE\u30DB" },
|
|
37
|
+
{ emoji: "\u{1F50B}", label: "\u30E2\u30D0\u30A4\u30EB\u200B\u5145\u96FB\u5668" },
|
|
38
|
+
{ emoji: "\u{1F4CB}", label: "\u5065\u5EB7\u8ABF\u67FB\u7968" },
|
|
39
|
+
{ emoji: "\u{1FAAA}", label: "\u30DE\u30A4\u30CA\u30AB\u30FC\u30C9\u200B\u307E\u305F\u306F\u4FDD\u967A\u8A3C" },
|
|
40
|
+
{ emoji: "\u{1F4D6}", label: "\u3057\u304A\u308A" },
|
|
44
41
|
{ emoji: "\u{1F637}", label: "\u30DE\u30B9\u30AF" },
|
|
45
|
-
{ emoji: "\u{
|
|
46
|
-
{ emoji: "\
|
|
47
|
-
{ emoji: "\u270F\uFE0F", label: "\u7B46\u8A18\u7528\u5177" },
|
|
42
|
+
{ emoji: "\u{1F927}", label: "\u30CF\u30F3\u30AB\u30C1" },
|
|
43
|
+
{ emoji: "\u{1F48A}", label: "\u5E38\u5099\u85AC" },
|
|
48
44
|
{ emoji: "\u{1F6CD}\uFE0F", label: "\u30D3\u30CB\u30FC\u30EB\u888B" },
|
|
49
|
-
{ emoji: "\u{
|
|
45
|
+
{ emoji: "\u{1F9E4}", label: "\u8ECD\u624B\uFF08\u5B66\u6821\u652F\u7D66\uFF09" },
|
|
46
|
+
{ emoji: "\u{1F526}", label: "\u30D8\u30C3\u30C9\u30E9\u30A4\u30C8" },
|
|
47
|
+
{ emoji: "\u{1F455}", label: "\u30B8\u30E3\u30FC\u30B8" },
|
|
48
|
+
{ emoji: "\u2614", label: "\u96E8\u5177" },
|
|
49
|
+
{ emoji: "\u{1F6C1}", label: "\u30BF\u30AA\u30EB" },
|
|
50
|
+
{ emoji: "\u{1F9F4}", label: "\u65E5\u713C\u3051\u6B62\u3081" }
|
|
50
51
|
];
|
|
51
52
|
|
|
53
|
+
// src/layout.ts
|
|
54
|
+
var MIN_SIZE = 2;
|
|
55
|
+
var MAX_SIZE = 10;
|
|
56
|
+
var fail = (msg) => {
|
|
57
|
+
throw new Error(`[packing-bingo] ${msg}`);
|
|
58
|
+
};
|
|
59
|
+
function layoutItems(options) {
|
|
60
|
+
if (options.items) return options.items;
|
|
61
|
+
const n = options.size;
|
|
62
|
+
if (n == null || !Number.isInteger(n) || n < MIN_SIZE) return DEFAULT_ITEMS;
|
|
63
|
+
return DEFAULT_ITEMS.slice(0, n * n - (options.free ? 1 : 0));
|
|
64
|
+
}
|
|
65
|
+
function checkSize(size, free) {
|
|
66
|
+
if (!Number.isInteger(size) || size < MIN_SIZE || size > MAX_SIZE)
|
|
67
|
+
fail(`"size" must be a whole number from ${MIN_SIZE} to ${MAX_SIZE} (got ${size}).`);
|
|
68
|
+
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}).`);
|
|
69
|
+
}
|
|
70
|
+
function shape(n, free) {
|
|
71
|
+
const seq = (f) => Array.from({ length: n }, (_, k) => f(k));
|
|
72
|
+
const cellLines = [
|
|
73
|
+
...seq((r) => seq((c) => r * n + c)),
|
|
74
|
+
// rows
|
|
75
|
+
...seq((c) => seq((r) => r * n + c)),
|
|
76
|
+
// columns
|
|
77
|
+
seq((k) => k * (n + 1)),
|
|
78
|
+
// top left to bottom right
|
|
79
|
+
seq((k) => (k + 1) * (n - 1))
|
|
80
|
+
// top right to bottom left
|
|
81
|
+
];
|
|
82
|
+
const center = free ? (n * n - 1) / 2 : -1;
|
|
83
|
+
const toItem = (c) => free && c > center ? c - 1 : c;
|
|
84
|
+
return { center, cellLines, lines: cellLines.map((l) => l.filter((c) => c !== center).map(toItem)) };
|
|
85
|
+
}
|
|
86
|
+
function resolveLayout(options) {
|
|
87
|
+
const items = layoutItems(options);
|
|
88
|
+
const free = !!options.free;
|
|
89
|
+
let size = options.size;
|
|
90
|
+
if (size == null) {
|
|
91
|
+
const len = items.length;
|
|
92
|
+
const sideOf = (cells) => {
|
|
93
|
+
const r = Math.round(Math.sqrt(cells));
|
|
94
|
+
return r * r === cells && r >= MIN_SIZE && r <= MAX_SIZE ? r : 0;
|
|
95
|
+
};
|
|
96
|
+
size = sideOf(len + (free ? 1 : 0));
|
|
97
|
+
if (!size) {
|
|
98
|
+
const other = sideOf(len + (free ? 0 : 1));
|
|
99
|
+
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` : "";
|
|
100
|
+
fail(
|
|
101
|
+
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}).`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
checkSize(size, free);
|
|
106
|
+
const want = size * size - (free ? 1 : 0);
|
|
107
|
+
if (items.length !== want) {
|
|
108
|
+
const card = `a ${size}\xD7${size} card${free ? " with a FREE center" : ""}`;
|
|
109
|
+
fail(
|
|
110
|
+
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".`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
const { center, cellLines, lines } = shape(size, free);
|
|
114
|
+
const itemCell = items.map((_, i) => free && i >= center ? i + 1 : i);
|
|
115
|
+
return { size, free, items, itemCell, center, cellLines, lines };
|
|
116
|
+
}
|
|
117
|
+
function bingoLines(size, free = false) {
|
|
118
|
+
checkSize(size, free);
|
|
119
|
+
return shape(size, free).lines;
|
|
120
|
+
}
|
|
121
|
+
|
|
52
122
|
// src/messages.ts
|
|
53
123
|
var ja = {
|
|
54
124
|
title: "\u6301\u3061\u3082\u306E\u30D3\u30F3\u30B4",
|
|
55
125
|
tag: "\u4FEE\u5B66\u65C5\u884C",
|
|
56
|
-
subtitle: "
|
|
126
|
+
subtitle: "",
|
|
57
127
|
packed: "\u305D\u308D\u3063\u305F",
|
|
58
128
|
bingo: "BINGO",
|
|
59
129
|
score: "SCORE",
|
|
@@ -63,6 +133,7 @@ var ja = {
|
|
|
63
133
|
soundOn: "\u{1F50A} \u97F3\u3042\u308A",
|
|
64
134
|
soundOff: "\u{1F507} \u97F3\u306A\u3057",
|
|
65
135
|
stamp: "OK",
|
|
136
|
+
free: "FREE",
|
|
66
137
|
reachBadge: "\u30EA\u30FC\u30C1",
|
|
67
138
|
reachPop: "\u30EA\u30FC\u30C1!",
|
|
68
139
|
bannerSingle: "BINGO!",
|
|
@@ -70,14 +141,15 @@ var ja = {
|
|
|
70
141
|
bannerTriple: "\u30C8\u30EA\u30D7\u30EB\u30D3\u30F3\u30B4!",
|
|
71
142
|
bannerNth: (n) => `${n} \u30E9\u30A4\u30F3\u76EE\uFF01`,
|
|
72
143
|
bannerMulti: (n) => `\u4E00\u6C17\u306B ${n} \u30E9\u30A4\u30F3\uFF01`,
|
|
73
|
-
statusStart: (
|
|
144
|
+
statusStart: () => "",
|
|
74
145
|
statusLines: (lines, left) => `\u30D3\u30F3\u30B4 ${lines} \u30E9\u30A4\u30F3\uFF01 \u306E\u3053\u308A ${left} \u3053`,
|
|
75
146
|
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`,
|
|
76
147
|
statusDone: "\u{1F389} \u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u51FA\u767A\u3067\u304D\u307E\u3059",
|
|
77
148
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
78
149
|
perfectStamp: "\u6E96\u5099\n\u5B8C\u4E86",
|
|
79
150
|
perfectTitle: "\u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01",
|
|
80
|
-
perfectBody:
|
|
151
|
+
perfectBody: (n) => `${n}\u3053 \u305C\u3093\u3076\u305D\u308D\u3063\u305F\uFF01
|
|
152
|
+
\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u3001\u3044\u3063\u3066\u3089\u3063\u3057\u3083\u3044\uFF01`,
|
|
81
153
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
82
154
|
statScore: "SCORE",
|
|
83
155
|
statCombo: "MAX COMBO",
|
|
@@ -91,7 +163,7 @@ var ja = {
|
|
|
91
163
|
var en = {
|
|
92
164
|
title: "Packing Bingo",
|
|
93
165
|
tag: "School trip",
|
|
94
|
-
subtitle: "
|
|
166
|
+
subtitle: "",
|
|
95
167
|
packed: "PACKED",
|
|
96
168
|
bingo: "BINGO",
|
|
97
169
|
score: "SCORE",
|
|
@@ -101,6 +173,7 @@ var en = {
|
|
|
101
173
|
soundOn: "\u{1F50A} Sound on",
|
|
102
174
|
soundOff: "\u{1F507} Muted",
|
|
103
175
|
stamp: "OK",
|
|
176
|
+
free: "FREE",
|
|
104
177
|
reachBadge: "REACH",
|
|
105
178
|
reachPop: "Reach!",
|
|
106
179
|
bannerSingle: "BINGO!",
|
|
@@ -108,14 +181,15 @@ var en = {
|
|
|
108
181
|
bannerTriple: "TRIPLE BINGO!",
|
|
109
182
|
bannerNth: (n) => `Line ${n}!`,
|
|
110
183
|
bannerMulti: (n) => `${n} lines at once!`,
|
|
111
|
-
statusStart: (
|
|
184
|
+
statusStart: () => "",
|
|
112
185
|
statusLines: (lines, left) => `${lines} bingo line${lines === 1 ? "" : "s"}! ${left} to go`,
|
|
113
186
|
statusReach: (labels) => `Reach! Pack ${labels.slice(0, 2).join(" or ")}${labels.length > 2 ? " (or more)" : ""} for a bingo`,
|
|
114
187
|
statusDone: "\u{1F389} Perfect! Nothing left behind.",
|
|
115
188
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
116
189
|
perfectStamp: "ALL\nSET",
|
|
117
190
|
perfectTitle: "Perfect!",
|
|
118
|
-
perfectBody:
|
|
191
|
+
perfectBody: (n) => `All ${n} packed!
|
|
192
|
+
Have a great trip!`,
|
|
119
193
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
120
194
|
statScore: "SCORE",
|
|
121
195
|
statCombo: "MAX COMBO",
|
|
@@ -224,17 +298,34 @@ function createSound(isMuted) {
|
|
|
224
298
|
};
|
|
225
299
|
}
|
|
226
300
|
|
|
301
|
+
// src/storage-key.ts
|
|
302
|
+
function resolveStorageKey(options) {
|
|
303
|
+
if (options.controlled) return null;
|
|
304
|
+
if (options.storageKey !== void 0) return options.storageKey || null;
|
|
305
|
+
if (options.demo) return null;
|
|
306
|
+
return itemsKey(layoutItems(options));
|
|
307
|
+
}
|
|
308
|
+
function itemsKey(items) {
|
|
309
|
+
const text = items.map((it) => `${it.emoji}\0${it.label}`).join("");
|
|
310
|
+
let h = 2166136261;
|
|
311
|
+
for (let i = 0; i < text.length; i++) {
|
|
312
|
+
h ^= text.charCodeAt(i);
|
|
313
|
+
h = Math.imul(h, 16777619);
|
|
314
|
+
}
|
|
315
|
+
return `packing-bingo:${(h >>> 0).toString(36)}`;
|
|
316
|
+
}
|
|
317
|
+
|
|
227
318
|
// src/styles.ts
|
|
228
319
|
var FONT_URL = "https://fonts.googleapis.com/css2?family=Dela+Gothic+One&family=M+PLUS+Rounded+1c:wght@500;800&display=swap";
|
|
229
320
|
var CSS = `
|
|
230
|
-
.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%}
|
|
321
|
+
.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%}
|
|
231
322
|
.pkb,.pkb *{box-sizing:border-box;margin:0;padding:0}
|
|
232
323
|
.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}
|
|
233
324
|
.pkb-board.pkb-shake{animation:pkb-shake .45s cubic-bezier(.36,.07,.19,.97)}
|
|
234
325
|
@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)}}
|
|
235
326
|
.pkb-board.pkb-punch{animation:pkb-punch .4s cubic-bezier(.3,1.6,.5,1)}
|
|
236
327
|
@keyframes pkb-punch{30%{transform:scale(1.018)}}
|
|
237
|
-
.pkb-head{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-bottom:18px}
|
|
328
|
+
.pkb-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px 16px;margin-bottom:18px}
|
|
238
329
|
.pkb-tags{display:flex;gap:8px;align-items:center}
|
|
239
330
|
.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}
|
|
240
331
|
.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}
|
|
@@ -242,7 +333,7 @@ var CSS = `
|
|
|
242
333
|
.pkb button{font-family:var(--pk-body)}
|
|
243
334
|
.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}
|
|
244
335
|
.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}
|
|
245
|
-
.pkb-sub{font-weight:800;font-size:13px;margin-top:4px}
|
|
336
|
+
.pkb-sub{font-weight:800;font-size:13px;margin-top:4px;word-break:auto-phrase}
|
|
246
337
|
.pkb-side{display:flex;flex-direction:column;gap:8px;flex:none}
|
|
247
338
|
.pkb-score{display:flex;gap:10px}
|
|
248
339
|
.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)}
|
|
@@ -261,16 +352,18 @@ var CSS = `
|
|
|
261
352
|
.pkb-combo.pkb-on .pkb-bar i{animation:pkb-drain 2.5s linear forwards}
|
|
262
353
|
@keyframes pkb-drain{from{transform:scaleX(1)}to{transform:scaleX(0)}}
|
|
263
354
|
.pkb-gw{position:relative}
|
|
264
|
-
.pkb-grid{display:grid;grid-template-columns:repeat(
|
|
265
|
-
.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}
|
|
266
|
-
.pkb-tile:active{transform:translate(3px,3px);box-shadow:1px 1px 0 var(--pk-ink)}
|
|
355
|
+
.pkb-grid{display:grid;grid-template-columns:repeat(var(--pk-n),minmax(0,1fr));gap:calc(12px*var(--pk-d))}
|
|
356
|
+
.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}
|
|
357
|
+
.pkb-tile:active:not(.pkb-free){transform:translate(3px,3px);box-shadow:1px 1px 0 var(--pk-ink)}
|
|
267
358
|
.pkb-tile input{position:absolute;opacity:0;pointer-events:none;width:1px;height:1px}
|
|
268
359
|
.pkb-tile:has(input:focus-visible){outline:3px solid var(--pk-ink);outline-offset:3px}
|
|
269
|
-
.pkb-emo{font-size:32px;line-height:1;transition:opacity .2s}
|
|
270
|
-
.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}
|
|
271
|
-
.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}
|
|
360
|
+
.pkb-emo{font-size:max(14px,calc(32px*var(--pk-s)));line-height:1;transition:opacity .2s}
|
|
361
|
+
.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}
|
|
362
|
+
.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}
|
|
272
363
|
.pkb-tile.pkb-on .pkb-stamp{opacity:.9;transform:translate(-50%,-50%) rotate(-16deg) scale(1)}
|
|
273
364
|
.pkb-tile.pkb-on .pkb-emo,.pkb-tile.pkb-on .pkb-lbl{opacity:.35}
|
|
365
|
+
.pkb-tile.pkb-free{cursor:default;container-type:inline-size}
|
|
366
|
+
.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)}
|
|
274
367
|
.pkb-tile.pkb-line{background-color:var(--pk-mint)}
|
|
275
368
|
.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}
|
|
276
369
|
@keyframes pkb-glint{0%{background-position:200% 0}30%,100%{background-position:-100% 0}}
|
|
@@ -287,11 +380,11 @@ var CSS = `
|
|
|
287
380
|
.pkb-lines{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible;z-index:2;transition:opacity .4s}
|
|
288
381
|
.pkb-board.pkb-all .pkb-lines{opacity:0}
|
|
289
382
|
.pkb-lines line{fill:none;stroke-linecap:round;stroke-dasharray:1;stroke-dashoffset:0}
|
|
290
|
-
.pkb-lines .pkb-u{stroke:var(--pk-paper);stroke-width:
|
|
291
|
-
.pkb-lines .pkb-o{stroke:var(--pk-ink);stroke-width:
|
|
383
|
+
.pkb-lines .pkb-u{stroke:var(--pk-paper);stroke-width:calc(13px*var(--pk-d))}
|
|
384
|
+
.pkb-lines .pkb-o{stroke:var(--pk-ink);stroke-width:calc(6px*var(--pk-d))}
|
|
292
385
|
.pkb-lines .pkb-draw line{animation:pkb-draw .45s ease-out .3s both}
|
|
293
386
|
@keyframes pkb-draw{from{stroke-dashoffset:1}}
|
|
294
|
-
.pkb-foot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:18px;font-weight:800;font-size:13px}
|
|
387
|
+
.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}
|
|
295
388
|
.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}
|
|
296
389
|
.pkb-flash{position:absolute;inset:0;z-index:7;background:#fff;opacity:0;pointer-events:none}
|
|
297
390
|
.pkb-flash.pkb-go{animation:pkb-flash .4s ease-out}
|
|
@@ -306,7 +399,7 @@ var CSS = `
|
|
|
306
399
|
.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}
|
|
307
400
|
.pkb-float.pkb-pt{font-size:22px;color:var(--pk-paper)}
|
|
308
401
|
.pkb-float.pkb-cmb{font-size:17px}
|
|
309
|
-
.pkb-float.pkb-big{font-size:clamp(
|
|
402
|
+
.pkb-float.pkb-big{font-size:clamp(14px,5.6cqi,34px);color:#FFE066}
|
|
310
403
|
.pkb-float.pkb-rch{font-size:20px;color:var(--pk-red);-webkit-text-stroke:4px var(--pk-paper)}
|
|
311
404
|
@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)}}
|
|
312
405
|
.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}
|
|
@@ -317,7 +410,7 @@ var CSS = `
|
|
|
317
410
|
.pkb-perfect.pkb-show .pkb-medal{animation:pkb-slam .38s cubic-bezier(.5,0,.75,0) .35s forwards}
|
|
318
411
|
@keyframes pkb-slam{to{opacity:1;transform:rotate(-14deg) scale(1)}}
|
|
319
412
|
.pkb-ptitle{font-family:var(--pk-display);font-weight:400;font-size:30px;line-height:1.3;color:inherit}
|
|
320
|
-
.pkb-pbody{font-weight:800;font-size:13.5px;line-height:1.6;margin:6px 0 14px;white-space:pre-line}
|
|
413
|
+
.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}
|
|
321
414
|
.pkb-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:16px}
|
|
322
415
|
.pkb-stats>div{border:2px solid var(--pk-ink);border-radius:12px;padding:6px 4px 5px;background:#fff}
|
|
323
416
|
.pkb-stats small{display:block;font-size:9.5px;font-weight:800;letter-spacing:.1em}
|
|
@@ -327,32 +420,20 @@ var CSS = `
|
|
|
327
420
|
.pkb-fx{position:absolute;inset:0;width:100%;height:100%;z-index:10;pointer-events:none}
|
|
328
421
|
.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}
|
|
329
422
|
.pkb-demo button{appearance:none;border:1px solid #ccc;background:#f3f3f3;color:#222;border-radius:999px;padding:5px 12px;font-size:12px;cursor:pointer}
|
|
330
|
-
@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:
|
|
423
|
+
@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}}
|
|
424
|
+
@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}}
|
|
425
|
+
@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}}
|
|
331
426
|
@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}}
|
|
332
427
|
@keyframes pkb-fade{0%,100%{opacity:0}15%,80%{opacity:1}}
|
|
333
428
|
`;
|
|
334
429
|
|
|
335
430
|
// src/core.ts
|
|
336
|
-
var LINES =
|
|
337
|
-
[0, 1, 2, 3],
|
|
338
|
-
[4, 5, 6, 7],
|
|
339
|
-
[8, 9, 10, 11],
|
|
340
|
-
[12, 13, 14, 15],
|
|
341
|
-
[0, 4, 8, 12],
|
|
342
|
-
[1, 5, 9, 13],
|
|
343
|
-
[2, 6, 10, 14],
|
|
344
|
-
[3, 7, 11, 15],
|
|
345
|
-
[0, 5, 10, 15],
|
|
346
|
-
[3, 6, 9, 12]
|
|
347
|
-
];
|
|
348
|
-
var DEMO_START = [0, 1, 2, 3, 5, 10];
|
|
431
|
+
var LINES = bingoLines(4);
|
|
349
432
|
var CONFETTI = ["#E8473B", "#2F6FED", "#1FA971", "#FFFDF6", "#2A1F14", "#FF9F1C"];
|
|
350
433
|
var COMBO_COLORS = ["#FF9F1C", "#1FA971", "#2F6FED", "#9B5DE5", "#E8473B"];
|
|
351
434
|
var COMBO_MS = 2500;
|
|
352
435
|
var MUTE_KEY = "packing-bingo-muted";
|
|
353
436
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
354
|
-
var normalize = (v) => Array.from({ length: 16 }, (_, i) => !!v?.[i]);
|
|
355
|
-
var demoStart = () => Array.from({ length: 16 }, (_, i) => DEMO_START.includes(i));
|
|
356
437
|
function readStore(key) {
|
|
357
438
|
try {
|
|
358
439
|
return localStorage.getItem(key);
|
|
@@ -381,15 +462,21 @@ function ensureAssets(fonts) {
|
|
|
381
462
|
document.head.appendChild(l);
|
|
382
463
|
}
|
|
383
464
|
}
|
|
384
|
-
function template(
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
465
|
+
function template(L, M, demo) {
|
|
466
|
+
const n = L.size;
|
|
467
|
+
const tiles = Array.from({ length: n * n }, (_, c) => {
|
|
468
|
+
const gd = `--pkb-gd:${+((Math.floor(c / n) + c % n) * 0.36 / Math.max(1, n - 1)).toFixed(3)}s`;
|
|
469
|
+
if (c === L.center)
|
|
470
|
+
return `<div class="pkb-tile pkb-free pkb-on" style="${gd}"><span class="pkb-stamp">${esc(M.free)}</span></div>`;
|
|
471
|
+
const it = L.items[L.itemCell.indexOf(c)];
|
|
472
|
+
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>`;
|
|
473
|
+
}).join("");
|
|
474
|
+
const scale = `--pk-n:${n};--pk-s:${+Math.min(1.35, 4 / n).toFixed(3)};--pk-d:${+Math.min(1, 4 / n).toFixed(3)}`;
|
|
388
475
|
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>` : "";
|
|
389
|
-
return `<div class="pkb"><div class="pkb-board">
|
|
476
|
+
return `<div class="pkb" style="${scale}"><div class="pkb-board">
|
|
390
477
|
<div class="pkb-head"><div>
|
|
391
478
|
<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>
|
|
392
|
-
<h2 class="pkb-title">${esc(M.title)}</h2
|
|
479
|
+
<h2 class="pkb-title">${esc(M.title)}</h2>${M.subtitle ? `<p class="pkb-sub">${esc(M.subtitle)}</p>` : ""}</div>
|
|
393
480
|
<div class="pkb-side"><div class="pkb-score">
|
|
394
481
|
<div class="pkb-pill"><small>${esc(M.packed)}</small><b data-r="cnt"></b></div>
|
|
395
482
|
<div class="pkb-pill" data-r="bp"><small>${esc(M.bingo)}</small><b data-r="bng"></b></div>
|
|
@@ -400,20 +487,28 @@ function template(items, M, demo) {
|
|
|
400
487
|
<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>
|
|
401
488
|
<div class="pkb-flash" data-r="flash"></div>
|
|
402
489
|
<div class="pkb-perfect" data-r="perfect"><div class="pkb-card" role="dialog" aria-label="${esc(M.perfectTitle)}">
|
|
403
|
-
<div class="pkb-medal">${esc(M.perfectStamp)}</div><h3 class="pkb-ptitle">${esc(M.perfectTitle)}</h3><p class="pkb-pbody">${esc(M.
|
|
490
|
+
<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>
|
|
404
491
|
<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>
|
|
405
492
|
<button type="button" class="pkb-close" data-r="close">${esc(M.close)}</button></div></div>
|
|
406
493
|
<canvas class="pkb-fx"></canvas></div>${demoBar}</div>`;
|
|
407
494
|
}
|
|
495
|
+
var perfectText = (M, total) => typeof M.perfectBody === "function" ? M.perfectBody(total) : M.perfectBody;
|
|
408
496
|
function createPackingBingo(container, options = {}) {
|
|
409
|
-
const
|
|
410
|
-
|
|
497
|
+
const L = resolveLayout(options);
|
|
498
|
+
const { items, lines, cellLines } = L;
|
|
499
|
+
const n = L.size, N = items.length;
|
|
500
|
+
const normalize = (v) => Array.from({ length: N }, (_, i) => !!v?.[i]);
|
|
501
|
+
const demoCells = [...cellLines[0], ...cellLines[2 * n].slice(1, -1)];
|
|
502
|
+
const demoItems = new Set(L.itemCell.flatMap((c, i) => demoCells.includes(c) ? [i] : []));
|
|
503
|
+
const demoStart = () => Array.from({ length: N }, (_, i) => demoItems.has(i));
|
|
504
|
+
const far = (c) => Math.floor(c / n) + c % n;
|
|
505
|
+
const dScale = Math.min(1, 4 / n);
|
|
411
506
|
const M = { ...ja, ...options.messages };
|
|
412
507
|
if (options.title != null) M.title = options.title;
|
|
413
508
|
if (options.tag != null) M.tag = options.tag;
|
|
414
509
|
if (options.subtitle != null) M.subtitle = options.subtitle;
|
|
415
510
|
const controlled = !!options.controlled;
|
|
416
|
-
const storageKey =
|
|
511
|
+
const storageKey = resolveStorageKey(options);
|
|
417
512
|
const demo = !!options.demo;
|
|
418
513
|
const calm = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
419
514
|
let dead = false;
|
|
@@ -428,7 +523,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
428
523
|
const wait = (ms) => new Promise((r) => window.setTimeout(r, ms));
|
|
429
524
|
let gen = 0;
|
|
430
525
|
ensureAssets(options.fonts !== false);
|
|
431
|
-
container.innerHTML = template(
|
|
526
|
+
container.innerHTML = template(L, M, demo);
|
|
432
527
|
const $ = (r) => {
|
|
433
528
|
const el = container.querySelector(`[data-r="${r}"]`);
|
|
434
529
|
if (!el) throw new Error(`[packing-bingo] internal: missing element "${r}"`);
|
|
@@ -440,7 +535,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
440
535
|
const svg = container.querySelector(".pkb-lines");
|
|
441
536
|
const cv = container.querySelector(".pkb-fx");
|
|
442
537
|
const ctx = cv.getContext("2d");
|
|
443
|
-
const
|
|
538
|
+
const cellTiles = Array.from(grid.children);
|
|
539
|
+
const tiles = L.itemCell.map((c) => cellTiles[c]);
|
|
540
|
+
const freeTile = L.center >= 0 ? cellTiles[L.center] : null;
|
|
444
541
|
const ins = tiles.map((t) => t.querySelector("input"));
|
|
445
542
|
const kick = $("kick"), snd = $("snd"), perfectEl = $("perfect");
|
|
446
543
|
let checked = normalize(void 0);
|
|
@@ -475,14 +572,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
475
572
|
cv.width = Math.round(BW * dpr);
|
|
476
573
|
cv.height = Math.round(BH * dpr);
|
|
477
574
|
};
|
|
478
|
-
const piece = (x, y, vx, vy, g, s, life,
|
|
575
|
+
const piece = (x, y, vx, vy, g, s, life, shape2) => ({
|
|
479
576
|
x,
|
|
480
577
|
y,
|
|
481
578
|
vx,
|
|
482
579
|
vy,
|
|
483
580
|
g,
|
|
484
581
|
life,
|
|
485
|
-
shape,
|
|
582
|
+
shape: shape2,
|
|
486
583
|
w: (5 + Math.random() * 6) * s,
|
|
487
584
|
h: (3 + Math.random() * 5) * s,
|
|
488
585
|
r: Math.random() * 6,
|
|
@@ -492,9 +589,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
492
589
|
const go = () => {
|
|
493
590
|
if (!raf && ctx) raf = requestAnimationFrame(tick);
|
|
494
591
|
};
|
|
495
|
-
function burst(x, y,
|
|
592
|
+
function burst(x, y, n2, o = {}) {
|
|
496
593
|
if (calm) return;
|
|
497
|
-
for (let i = 0; i <
|
|
594
|
+
for (let i = 0; i < n2; i++) {
|
|
498
595
|
const a = (o.angle ?? -Math.PI / 2) + (Math.random() - 0.5) * (o.spread ?? Math.PI * 2);
|
|
499
596
|
const v = (o.speed ?? 10) * (0.35 + Math.random() * 0.85);
|
|
500
597
|
const sh = Math.random() < (o.star ?? 0.15) ? "star" : Math.random() < 0.3 ? "round" : "rect";
|
|
@@ -585,11 +682,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
585
682
|
void el.offsetWidth;
|
|
586
683
|
el.classList.add(cls);
|
|
587
684
|
};
|
|
588
|
-
const hop = (
|
|
685
|
+
const hop = (t, d) => {
|
|
589
686
|
if (calm) return;
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
replay(
|
|
687
|
+
t.classList.remove("pkb-squash");
|
|
688
|
+
t.style.setProperty("--pkb-d", d + "ms");
|
|
689
|
+
replay(t, "pkb-hop");
|
|
593
690
|
};
|
|
594
691
|
grid.addEventListener("animationend", (e) => {
|
|
595
692
|
const t = e.target;
|
|
@@ -637,8 +734,8 @@ function createPackingBingo(container, options = {}) {
|
|
|
637
734
|
st(t0);
|
|
638
735
|
if (!calm) replay($("sp"), "pkb-bump");
|
|
639
736
|
}
|
|
640
|
-
const showGain = (
|
|
641
|
-
floatText(`+${
|
|
737
|
+
const showGain = (pts, x, y, cls = "pt") => {
|
|
738
|
+
floatText(`+${pts}`, x, y, cls);
|
|
642
739
|
countScore();
|
|
643
740
|
};
|
|
644
741
|
function countUp(el, a, b, d, fmt, ticks = false) {
|
|
@@ -658,15 +755,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
658
755
|
};
|
|
659
756
|
st(t0);
|
|
660
757
|
}
|
|
661
|
-
const ctr = (
|
|
662
|
-
const t = tiles[i];
|
|
758
|
+
const ctr = (t) => {
|
|
663
759
|
return { x: t.offsetLeft + t.offsetWidth / 2, y: t.offsetTop + t.offsetHeight / 2 };
|
|
664
760
|
};
|
|
665
761
|
function drawLines(fresh = []) {
|
|
666
762
|
svg.setAttribute("viewBox", `0 0 ${gw.clientWidth} ${gw.clientHeight}`);
|
|
667
763
|
svg.innerHTML = cur.map((k) => {
|
|
668
|
-
const a = ctr(
|
|
669
|
-
const dx = b.x - a.x, dy = b.y - a.y, d = Math.hypot(dx, dy) || 1, ex = dx / d *
|
|
764
|
+
const a = ctr(cellTiles[cellLines[k][0]]), b = ctr(cellTiles[cellLines[k][n - 1]]);
|
|
765
|
+
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;
|
|
670
766
|
const at = `x1="${a.x - ex}" y1="${a.y - ey}" x2="${b.x + ex}" y2="${b.y + ey}" pathLength="1"`;
|
|
671
767
|
return `<g${fresh.includes(k) ? ' class="pkb-draw"' : ""}><line class="pkb-u" ${at}/><line class="pkb-o" ${at}/></g>`;
|
|
672
768
|
}).join("");
|
|
@@ -713,29 +809,29 @@ function createPackingBingo(container, options = {}) {
|
|
|
713
809
|
later(() => sfx.reach(), 150);
|
|
714
810
|
}
|
|
715
811
|
function bingo(fresh, pts) {
|
|
716
|
-
const
|
|
717
|
-
const seq = [...new Set(fresh.flatMap((k) =>
|
|
812
|
+
const nl = fresh.length;
|
|
813
|
+
const seq = [...new Set(fresh.flatMap((k) => cellLines[k]))].map((c) => cellTiles[c]);
|
|
718
814
|
const step = Math.min(70, 300 / seq.length);
|
|
719
815
|
seq.forEach(
|
|
720
|
-
(
|
|
721
|
-
hop(
|
|
722
|
-
const p = rel(
|
|
816
|
+
(t, j) => later(() => {
|
|
817
|
+
hop(t, 0);
|
|
818
|
+
const p = rel(t);
|
|
723
819
|
burst(p.x, p.y, 14, { speed: 5, g: 0.15, life: 30, size: 0.7, star: 0.8 });
|
|
724
820
|
sfx.note(j + 3);
|
|
725
821
|
}, j * step)
|
|
726
822
|
);
|
|
727
823
|
later(() => {
|
|
728
|
-
$("bT").textContent =
|
|
729
|
-
$("bS").textContent =
|
|
824
|
+
$("bT").textContent = nl >= 3 ? M.bannerTriple : nl === 2 ? M.bannerDouble : M.bannerSingle;
|
|
825
|
+
$("bS").textContent = nl > 1 ? M.bannerMulti(nl) : M.bannerNth(cur.length);
|
|
730
826
|
replay($("banner"), "pkb-show");
|
|
731
827
|
if (!calm) {
|
|
732
828
|
replay($("flash"), "pkb-go");
|
|
733
829
|
replay(board, "pkb-punch");
|
|
734
830
|
}
|
|
735
831
|
const c = rel(gw);
|
|
736
|
-
burst(c.x, c.y,
|
|
737
|
-
sfx.bingo(
|
|
738
|
-
buzz(
|
|
832
|
+
burst(c.x, c.y, nl > 1 ? 180 : 110, { speed: nl > 1 ? 16 : 13, star: 0.3 });
|
|
833
|
+
sfx.bingo(nl);
|
|
834
|
+
buzz(nl > 1 ? [40, 60, 40, 60, 90] : [40, 60, 40]);
|
|
739
835
|
if (pts) later(() => showGain(pts, c.x, c.y + 105, "big"), 350);
|
|
740
836
|
}, seq.length * step + 60);
|
|
741
837
|
}
|
|
@@ -743,9 +839,10 @@ function createPackingBingo(container, options = {}) {
|
|
|
743
839
|
const bonus = gotP ? 0 : 1e3;
|
|
744
840
|
gotP = true;
|
|
745
841
|
score += bonus;
|
|
746
|
-
|
|
842
|
+
cellTiles.forEach((t, c) => hop(t, far(c) * 180 / Math.max(1, n - 1)));
|
|
747
843
|
sfx.perfect();
|
|
748
844
|
if (!calm) replay($("flash"), "pkb-go");
|
|
845
|
+
$("pbody").textContent = perfectText(M, N);
|
|
749
846
|
$("sScore").textContent = "0";
|
|
750
847
|
$("sCombo").textContent = "\xD70";
|
|
751
848
|
$("sBingo").textContent = "0";
|
|
@@ -795,27 +892,28 @@ function createPackingBingo(container, options = {}) {
|
|
|
795
892
|
function render(src, quiet) {
|
|
796
893
|
const events = [];
|
|
797
894
|
const on = checked, c = on.filter(Boolean).length;
|
|
798
|
-
cur =
|
|
895
|
+
cur = lines.flatMap((l, k) => l.every((i) => on[i]) ? [k] : []);
|
|
799
896
|
const fresh = cur.filter((k) => !prevLines.has(k));
|
|
800
897
|
if ([...prevLines].some((k) => !cur.includes(k))) {
|
|
801
898
|
cancelFx();
|
|
802
|
-
if (c <
|
|
899
|
+
if (c < N) perfectEl.classList.remove("pkb-show");
|
|
803
900
|
}
|
|
804
|
-
const hit = new Set(cur.flatMap((k) =>
|
|
901
|
+
const hit = new Set(cur.flatMap((k) => cellLines[k]));
|
|
805
902
|
const reach = /* @__PURE__ */ new Set();
|
|
806
|
-
for (const l of
|
|
903
|
+
for (const l of lines) {
|
|
807
904
|
const m = l.filter((i) => !on[i]);
|
|
808
905
|
if (m.length === 1) reach.add(m[0]);
|
|
809
906
|
}
|
|
810
907
|
tiles.forEach((t, i) => {
|
|
811
908
|
t.classList.toggle("pkb-on", on[i]);
|
|
812
|
-
t.classList.toggle("pkb-line", hit.has(i));
|
|
909
|
+
t.classList.toggle("pkb-line", hit.has(L.itemCell[i]));
|
|
813
910
|
t.classList.toggle("pkb-reach", reach.has(i));
|
|
814
911
|
});
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
kick.
|
|
818
|
-
|
|
912
|
+
freeTile?.classList.toggle("pkb-line", hit.has(L.center));
|
|
913
|
+
board.classList.toggle("pkb-all", c === N);
|
|
914
|
+
kick.classList.toggle("pkb-gold", c === N);
|
|
915
|
+
kick.textContent = c === N ? M.perfectTag : M.tag;
|
|
916
|
+
$("cnt").textContent = `${c}/${N}`;
|
|
819
917
|
$("bng").textContent = String(cur.length);
|
|
820
918
|
const bp = $("bp");
|
|
821
919
|
if (cur.length > lastN) {
|
|
@@ -832,7 +930,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
832
930
|
score += linePts;
|
|
833
931
|
if (quiet) {
|
|
834
932
|
if (linePts) countScore();
|
|
835
|
-
} else if (c ===
|
|
933
|
+
} else if (c === N && !prevAll) perfect(events);
|
|
836
934
|
else if (fresh.length) {
|
|
837
935
|
bingo(fresh, linePts);
|
|
838
936
|
const e = { newLines: fresh, totalLines: cur.length, score };
|
|
@@ -843,9 +941,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
843
941
|
}
|
|
844
942
|
prevLines = new Set(cur);
|
|
845
943
|
prevReach = reach;
|
|
846
|
-
prevAll = c ===
|
|
944
|
+
prevAll = c === N;
|
|
847
945
|
const labels = [...reach].map((i) => items[i].label);
|
|
848
|
-
$("msg").textContent = c ===
|
|
946
|
+
$("msg").textContent = c === N ? M.statusDone : labels.length ? M.statusReach(labels) : cur.length ? M.statusLines(cur.length, N - c) : M.statusStart(N - c);
|
|
849
947
|
return events;
|
|
850
948
|
}
|
|
851
949
|
const flush = (events) => {
|
|
@@ -872,7 +970,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
872
970
|
got.clear();
|
|
873
971
|
gotL.clear();
|
|
874
972
|
checked.forEach((v, i) => v && got.add(i));
|
|
875
|
-
|
|
973
|
+
lines.forEach((l, k) => l.every((i) => checked[i]) && gotL.add(k));
|
|
876
974
|
prevLines = new Set(gotL);
|
|
877
975
|
prevReach = /* @__PURE__ */ new Set();
|
|
878
976
|
prevAll = checked.every(Boolean);
|
|
@@ -932,12 +1030,13 @@ function createPackingBingo(container, options = {}) {
|
|
|
932
1030
|
}
|
|
933
1031
|
};
|
|
934
1032
|
$("dB").addEventListener("click", () => {
|
|
935
|
-
const open = () =>
|
|
936
|
-
const pick = checked.every(Boolean) ? [...
|
|
1033
|
+
const open = () => lines.map((l) => l.filter((i) => !checked[i])).filter((m) => m.length).sort((a, b) => a.length - b.length)[0] ?? [];
|
|
1034
|
+
const pick = checked.every(Boolean) ? [...lines[0]] : open();
|
|
937
1035
|
void run(pick.map((i) => ({ i, delay: 320 })));
|
|
938
1036
|
});
|
|
939
1037
|
$("dP").addEventListener("click", () => {
|
|
940
|
-
const
|
|
1038
|
+
const all = items.map((_, i) => i);
|
|
1039
|
+
const miss = checked.every(Boolean) ? all : all.filter((i) => !checked[i]);
|
|
941
1040
|
void run(miss.map((i, k) => k < miss.length - 1 ? { i, delay: 170, quiet: true } : { i, delay: 450 }));
|
|
942
1041
|
});
|
|
943
1042
|
$("dR").addEventListener("click", () => {
|
|
@@ -965,14 +1064,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
965
1064
|
return {
|
|
966
1065
|
setChecked(next, opts = {}) {
|
|
967
1066
|
if (dead) return;
|
|
968
|
-
const
|
|
969
|
-
const changed =
|
|
1067
|
+
const n2 = normalize(next);
|
|
1068
|
+
const changed = n2.flatMap((v, i) => v !== checked[i] ? [i] : []);
|
|
970
1069
|
if (!changed.length) return;
|
|
971
1070
|
if (!opts.animate || changed.length !== 1) {
|
|
972
|
-
baseline(
|
|
1071
|
+
baseline(n2);
|
|
973
1072
|
return;
|
|
974
1073
|
}
|
|
975
|
-
flush(apply(
|
|
1074
|
+
flush(apply(n2, changed[0], false));
|
|
976
1075
|
},
|
|
977
1076
|
reset() {
|
|
978
1077
|
if (!dead) request(normalize(void 0), null, { reset: true });
|
|
@@ -981,7 +1080,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
981
1080
|
if (!dead) render(null, true);
|
|
982
1081
|
},
|
|
983
1082
|
getState() {
|
|
984
|
-
return { checked: checked.slice(), lines: cur.slice(), score, maxCombo, complete: checked.every(Boolean) };
|
|
1083
|
+
return { checked: checked.slice(), lines: cur.slice(), score, maxCombo, complete: checked.every(Boolean), size: n, free: L.free };
|
|
985
1084
|
},
|
|
986
1085
|
destroy() {
|
|
987
1086
|
if (dead) return;
|
|
@@ -1000,6 +1099,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1000
1099
|
0 && (module.exports = {
|
|
1001
1100
|
DEFAULT_ITEMS,
|
|
1002
1101
|
LINES,
|
|
1102
|
+
bingoLines,
|
|
1003
1103
|
createPackingBingo,
|
|
1004
1104
|
en,
|
|
1005
1105
|
ja
|