packing-bingo 0.2.0 → 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 +130 -85
- package/dist/{chunk-WGZKPODU.js → chunk-Z34GMJW2.js} +190 -108
- package/dist/core.cjs +191 -108
- package/dist/core.d.cts +46 -10
- package/dist/core.d.ts +46 -10
- package/dist/core.js +3 -1
- package/dist/index.cjs +191 -108
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -1
- package/dist/packing-bingo.global.js +37 -33
- package/package.json +2 -2
|
@@ -2,29 +2,98 @@
|
|
|
2
2
|
|
|
3
3
|
// src/items.ts
|
|
4
4
|
var DEFAULT_ITEMS = [
|
|
5
|
-
{ emoji: "\u{
|
|
6
|
-
{ emoji: "\u{
|
|
7
|
-
{ emoji: "\u{
|
|
8
|
-
{ emoji: "\u{
|
|
9
|
-
{ emoji: "\u{
|
|
10
|
-
{ emoji: "\u{
|
|
11
|
-
{ emoji: "\u{1F9E5}", label: "\u4E0A\u7740" },
|
|
12
|
-
{ emoji: "\u{1FAA5}", label: "\u6D17\u9762\u7528\u5177" },
|
|
13
|
-
{ emoji: "\u{1F6C1}", label: "\u30BF\u30AA\u30EB" },
|
|
14
|
-
{ emoji: "\u{1F927}", label: "\u30CF\u30F3\u30AB\u30C1\u30FB\u30C6\u30A3\u30C3\u30B7\u30E5" },
|
|
5
|
+
{ emoji: "\u{1F45B}", label: "\u8CA1\u5E03" },
|
|
6
|
+
{ emoji: "\u{1F4F1}", label: "\u30B9\u30DE\u30DB" },
|
|
7
|
+
{ emoji: "\u{1F50B}", label: "\u30E2\u30D0\u30A4\u30EB\u200B\u5145\u96FB\u5668" },
|
|
8
|
+
{ emoji: "\u{1F4CB}", label: "\u5065\u5EB7\u8ABF\u67FB\u7968" },
|
|
9
|
+
{ emoji: "\u{1FAAA}", label: "\u30DE\u30A4\u30CA\u30AB\u30FC\u30C9\u200B\u307E\u305F\u306F\u4FDD\u967A\u8A3C" },
|
|
10
|
+
{ emoji: "\u{1F4D6}", label: "\u3057\u304A\u308A" },
|
|
15
11
|
{ emoji: "\u{1F637}", label: "\u30DE\u30B9\u30AF" },
|
|
16
|
-
{ emoji: "\u{
|
|
17
|
-
{ emoji: "\
|
|
18
|
-
{ emoji: "\u270F\uFE0F", label: "\u7B46\u8A18\u7528\u5177" },
|
|
12
|
+
{ emoji: "\u{1F927}", label: "\u30CF\u30F3\u30AB\u30C1" },
|
|
13
|
+
{ emoji: "\u{1F48A}", label: "\u5E38\u5099\u85AC" },
|
|
19
14
|
{ emoji: "\u{1F6CD}\uFE0F", label: "\u30D3\u30CB\u30FC\u30EB\u888B" },
|
|
20
|
-
{ emoji: "\u{
|
|
15
|
+
{ emoji: "\u{1F9E4}", label: "\u8ECD\u624B\uFF08\u5B66\u6821\u652F\u7D66\uFF09" },
|
|
16
|
+
{ emoji: "\u{1F526}", label: "\u30D8\u30C3\u30C9\u30E9\u30A4\u30C8" },
|
|
17
|
+
{ emoji: "\u{1F455}", label: "\u30B8\u30E3\u30FC\u30B8" },
|
|
18
|
+
{ emoji: "\u2614", label: "\u96E8\u5177" },
|
|
19
|
+
{ emoji: "\u{1F6C1}", label: "\u30BF\u30AA\u30EB" },
|
|
20
|
+
{ emoji: "\u{1F9F4}", label: "\u65E5\u713C\u3051\u6B62\u3081" }
|
|
21
21
|
];
|
|
22
22
|
|
|
23
|
+
// src/layout.ts
|
|
24
|
+
var MIN_SIZE = 2;
|
|
25
|
+
var MAX_SIZE = 10;
|
|
26
|
+
var fail = (msg) => {
|
|
27
|
+
throw new Error(`[packing-bingo] ${msg}`);
|
|
28
|
+
};
|
|
29
|
+
function layoutItems(options) {
|
|
30
|
+
if (options.items) return options.items;
|
|
31
|
+
const n = options.size;
|
|
32
|
+
if (n == null || !Number.isInteger(n) || n < MIN_SIZE) return DEFAULT_ITEMS;
|
|
33
|
+
return DEFAULT_ITEMS.slice(0, n * n - (options.free ? 1 : 0));
|
|
34
|
+
}
|
|
35
|
+
function checkSize(size, free) {
|
|
36
|
+
if (!Number.isInteger(size) || size < MIN_SIZE || size > MAX_SIZE)
|
|
37
|
+
fail(`"size" must be a whole number from ${MIN_SIZE} to ${MAX_SIZE} (got ${size}).`);
|
|
38
|
+
if (free && size % 2 === 0) fail(`"free" needs an odd size such as 3 or 5, so that the card has a center (got ${size}).`);
|
|
39
|
+
}
|
|
40
|
+
function shape(n, free) {
|
|
41
|
+
const seq = (f) => Array.from({ length: n }, (_, k) => f(k));
|
|
42
|
+
const cellLines = [
|
|
43
|
+
...seq((r) => seq((c) => r * n + c)),
|
|
44
|
+
// rows
|
|
45
|
+
...seq((c) => seq((r) => r * n + c)),
|
|
46
|
+
// columns
|
|
47
|
+
seq((k) => k * (n + 1)),
|
|
48
|
+
// top left to bottom right
|
|
49
|
+
seq((k) => (k + 1) * (n - 1))
|
|
50
|
+
// top right to bottom left
|
|
51
|
+
];
|
|
52
|
+
const center = free ? (n * n - 1) / 2 : -1;
|
|
53
|
+
const toItem = (c) => free && c > center ? c - 1 : c;
|
|
54
|
+
return { center, cellLines, lines: cellLines.map((l) => l.filter((c) => c !== center).map(toItem)) };
|
|
55
|
+
}
|
|
56
|
+
function resolveLayout(options) {
|
|
57
|
+
const items = layoutItems(options);
|
|
58
|
+
const free = !!options.free;
|
|
59
|
+
let size = options.size;
|
|
60
|
+
if (size == null) {
|
|
61
|
+
const len = items.length;
|
|
62
|
+
const sideOf = (cells) => {
|
|
63
|
+
const r = Math.round(Math.sqrt(cells));
|
|
64
|
+
return r * r === cells && r >= MIN_SIZE && r <= MAX_SIZE ? r : 0;
|
|
65
|
+
};
|
|
66
|
+
size = sideOf(len + (free ? 1 : 0));
|
|
67
|
+
if (!size) {
|
|
68
|
+
const other = sideOf(len + (free ? 0 : 1));
|
|
69
|
+
const hint = free ? other ? `; ${len} items fill a ${other}\xD7${other} card without "free"` : "" : other % 2 ? `, or set "free" for a ${other}\xD7${other} card with a FREE center` : "";
|
|
70
|
+
fail(
|
|
71
|
+
free ? `With "free", "items" must have one less than an odd square number of entries (8, 24, 48, 80)${hint} (got ${len}).` : `"items" must have a square number of entries (4, 9, 16, 25, \u2026, 100)${hint} (got ${len}).`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
checkSize(size, free);
|
|
76
|
+
const want = size * size - (free ? 1 : 0);
|
|
77
|
+
if (items.length !== want) {
|
|
78
|
+
const card = `a ${size}\xD7${size} card${free ? " with a FREE center" : ""}`;
|
|
79
|
+
fail(
|
|
80
|
+
options.items ? `"items" must have exactly ${want} entries for ${card} (got ${items.length}).` : `A ${card.slice(2)} needs ${want} items, but DEFAULT_ITEMS has only ${DEFAULT_ITEMS.length}. Pass "items".`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const { center, cellLines, lines } = shape(size, free);
|
|
84
|
+
const itemCell = items.map((_, i) => free && i >= center ? i + 1 : i);
|
|
85
|
+
return { size, free, items, itemCell, center, cellLines, lines };
|
|
86
|
+
}
|
|
87
|
+
function bingoLines(size, free = false) {
|
|
88
|
+
checkSize(size, free);
|
|
89
|
+
return shape(size, free).lines;
|
|
90
|
+
}
|
|
91
|
+
|
|
23
92
|
// src/messages.ts
|
|
24
93
|
var ja = {
|
|
25
94
|
title: "\u6301\u3061\u3082\u306E\u30D3\u30F3\u30B4",
|
|
26
95
|
tag: "\u4FEE\u5B66\u65C5\u884C",
|
|
27
|
-
subtitle: "
|
|
96
|
+
subtitle: "",
|
|
28
97
|
packed: "\u305D\u308D\u3063\u305F",
|
|
29
98
|
bingo: "BINGO",
|
|
30
99
|
score: "SCORE",
|
|
@@ -34,6 +103,7 @@ var ja = {
|
|
|
34
103
|
soundOn: "\u{1F50A} \u97F3\u3042\u308A",
|
|
35
104
|
soundOff: "\u{1F507} \u97F3\u306A\u3057",
|
|
36
105
|
stamp: "OK",
|
|
106
|
+
free: "FREE",
|
|
37
107
|
reachBadge: "\u30EA\u30FC\u30C1",
|
|
38
108
|
reachPop: "\u30EA\u30FC\u30C1!",
|
|
39
109
|
bannerSingle: "BINGO!",
|
|
@@ -41,14 +111,15 @@ var ja = {
|
|
|
41
111
|
bannerTriple: "\u30C8\u30EA\u30D7\u30EB\u30D3\u30F3\u30B4!",
|
|
42
112
|
bannerNth: (n) => `${n} \u30E9\u30A4\u30F3\u76EE\uFF01`,
|
|
43
113
|
bannerMulti: (n) => `\u4E00\u6C17\u306B ${n} \u30E9\u30A4\u30F3\uFF01`,
|
|
44
|
-
statusStart: (
|
|
114
|
+
statusStart: () => "",
|
|
45
115
|
statusLines: (lines, left) => `\u30D3\u30F3\u30B4 ${lines} \u30E9\u30A4\u30F3\uFF01 \u306E\u3053\u308A ${left} \u3053`,
|
|
46
116
|
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`,
|
|
47
117
|
statusDone: "\u{1F389} \u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u51FA\u767A\u3067\u304D\u307E\u3059",
|
|
48
118
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
49
119
|
perfectStamp: "\u6E96\u5099\n\u5B8C\u4E86",
|
|
50
120
|
perfectTitle: "\u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01",
|
|
51
|
-
perfectBody:
|
|
121
|
+
perfectBody: (n) => `${n}\u3053 \u305C\u3093\u3076\u305D\u308D\u3063\u305F\uFF01
|
|
122
|
+
\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u3001\u3044\u3063\u3066\u3089\u3063\u3057\u3083\u3044\uFF01`,
|
|
52
123
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
53
124
|
statScore: "SCORE",
|
|
54
125
|
statCombo: "MAX COMBO",
|
|
@@ -62,7 +133,7 @@ var ja = {
|
|
|
62
133
|
var en = {
|
|
63
134
|
title: "Packing Bingo",
|
|
64
135
|
tag: "School trip",
|
|
65
|
-
subtitle: "
|
|
136
|
+
subtitle: "",
|
|
66
137
|
packed: "PACKED",
|
|
67
138
|
bingo: "BINGO",
|
|
68
139
|
score: "SCORE",
|
|
@@ -72,6 +143,7 @@ var en = {
|
|
|
72
143
|
soundOn: "\u{1F50A} Sound on",
|
|
73
144
|
soundOff: "\u{1F507} Muted",
|
|
74
145
|
stamp: "OK",
|
|
146
|
+
free: "FREE",
|
|
75
147
|
reachBadge: "REACH",
|
|
76
148
|
reachPop: "Reach!",
|
|
77
149
|
bannerSingle: "BINGO!",
|
|
@@ -79,14 +151,15 @@ var en = {
|
|
|
79
151
|
bannerTriple: "TRIPLE BINGO!",
|
|
80
152
|
bannerNth: (n) => `Line ${n}!`,
|
|
81
153
|
bannerMulti: (n) => `${n} lines at once!`,
|
|
82
|
-
statusStart: (
|
|
154
|
+
statusStart: () => "",
|
|
83
155
|
statusLines: (lines, left) => `${lines} bingo line${lines === 1 ? "" : "s"}! ${left} to go`,
|
|
84
156
|
statusReach: (labels) => `Reach! Pack ${labels.slice(0, 2).join(" or ")}${labels.length > 2 ? " (or more)" : ""} for a bingo`,
|
|
85
157
|
statusDone: "\u{1F389} Perfect! Nothing left behind.",
|
|
86
158
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
87
159
|
perfectStamp: "ALL\nSET",
|
|
88
160
|
perfectTitle: "Perfect!",
|
|
89
|
-
perfectBody:
|
|
161
|
+
perfectBody: (n) => `All ${n} packed!
|
|
162
|
+
Have a great trip!`,
|
|
90
163
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
91
164
|
statScore: "SCORE",
|
|
92
165
|
statCombo: "MAX COMBO",
|
|
@@ -200,7 +273,7 @@ function resolveStorageKey(options) {
|
|
|
200
273
|
if (options.controlled) return null;
|
|
201
274
|
if (options.storageKey !== void 0) return options.storageKey || null;
|
|
202
275
|
if (options.demo) return null;
|
|
203
|
-
return itemsKey(options
|
|
276
|
+
return itemsKey(layoutItems(options));
|
|
204
277
|
}
|
|
205
278
|
function itemsKey(items) {
|
|
206
279
|
const text = items.map((it) => `${it.emoji}\0${it.label}`).join("");
|
|
@@ -215,14 +288,14 @@ function itemsKey(items) {
|
|
|
215
288
|
// src/styles.ts
|
|
216
289
|
var FONT_URL = "https://fonts.googleapis.com/css2?family=Dela+Gothic+One&family=M+PLUS+Rounded+1c:wght@500;800&display=swap";
|
|
217
290
|
var CSS = `
|
|
218
|
-
.pkb{--pk-sun:var(--pkb-sun,#FFD54F);--pk-ink:var(--pkb-ink,#2A1F14);--pk-paper:var(--pkb-paper,#FFFDF6);--pk-red:var(--pkb-red,#E8473B);--pk-mint:var(--pkb-mint,#BDEBD2);--pk-display:"Dela Gothic One",var(--pkb-font-fallback,sans-serif);--pk-body:"M PLUS Rounded 1c","Hiragino Maru Gothic ProN",var(--pkb-font-fallback,sans-serif);container-type:inline-size;isolation:isolate;width:100%}
|
|
291
|
+
.pkb{--pk-sun:var(--pkb-sun,#FFD54F);--pk-ink:var(--pkb-ink,#2A1F14);--pk-paper:var(--pkb-paper,#FFFDF6);--pk-red:var(--pkb-red,#E8473B);--pk-mint:var(--pkb-mint,#BDEBD2);--pk-display:"Dela Gothic One",var(--pkb-font-fallback,sans-serif);--pk-body:"M PLUS Rounded 1c","Hiragino Maru Gothic ProN",var(--pkb-font-fallback,sans-serif);--pk-n:4;--pk-s:1;--pk-d:1;container-type:inline-size;isolation:isolate;width:100%}
|
|
219
292
|
.pkb,.pkb *{box-sizing:border-box;margin:0;padding:0}
|
|
220
293
|
.pkb-board{position:relative;overflow:hidden;max-width:720px;margin:0 auto;padding:22px 26px 20px;border-radius:24px;background-color:var(--pk-sun);background-image:radial-gradient(rgba(42,31,20,.12) 1.3px,transparent 1.6px);background-size:16px 16px;border:2.5px solid var(--pk-ink);color:var(--pk-ink);font-family:var(--pk-body);line-height:1.4;text-align:left}
|
|
221
294
|
.pkb-board.pkb-shake{animation:pkb-shake .45s cubic-bezier(.36,.07,.19,.97)}
|
|
222
295
|
@keyframes pkb-shake{10%,90%{transform:translate(-1px,1px)}20%,80%{transform:translate(3px,-2px)}30%,50%,70%{transform:translate(-6px,2px)}40%,60%{transform:translate(6px,-1px)}}
|
|
223
296
|
.pkb-board.pkb-punch{animation:pkb-punch .4s cubic-bezier(.3,1.6,.5,1)}
|
|
224
297
|
@keyframes pkb-punch{30%{transform:scale(1.018)}}
|
|
225
|
-
.pkb-head{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-bottom:18px}
|
|
298
|
+
.pkb-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px 16px;margin-bottom:18px}
|
|
226
299
|
.pkb-tags{display:flex;gap:8px;align-items:center}
|
|
227
300
|
.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}
|
|
228
301
|
.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}
|
|
@@ -230,7 +303,7 @@ var CSS = `
|
|
|
230
303
|
.pkb button{font-family:var(--pk-body)}
|
|
231
304
|
.pkb-snd{appearance:none;border:2px solid var(--pk-ink);background:var(--pk-paper);border-radius:999px;padding:2px 9px;font-weight:800;font-size:11px;line-height:1.4;color:var(--pk-ink);cursor:pointer}
|
|
232
305
|
.pkb-title{font-family:var(--pk-display);font-weight:400;font-size:34px;line-height:1.15;margin-top:8px;letter-spacing:.02em;color:inherit}
|
|
233
|
-
.pkb-sub{font-weight:800;font-size:13px;margin-top:4px}
|
|
306
|
+
.pkb-sub{font-weight:800;font-size:13px;margin-top:4px;word-break:auto-phrase}
|
|
234
307
|
.pkb-side{display:flex;flex-direction:column;gap:8px;flex:none}
|
|
235
308
|
.pkb-score{display:flex;gap:10px}
|
|
236
309
|
.pkb-pill{min-width:78px;background:var(--pk-paper);border:2.5px solid var(--pk-ink);border-radius:14px;padding:6px 10px 5px;text-align:center;box-shadow:3px 3px 0 var(--pk-ink)}
|
|
@@ -249,16 +322,18 @@ var CSS = `
|
|
|
249
322
|
.pkb-combo.pkb-on .pkb-bar i{animation:pkb-drain 2.5s linear forwards}
|
|
250
323
|
@keyframes pkb-drain{from{transform:scaleX(1)}to{transform:scaleX(0)}}
|
|
251
324
|
.pkb-gw{position:relative}
|
|
252
|
-
.pkb-grid{display:grid;grid-template-columns:repeat(
|
|
253
|
-
.pkb-tile{position:relative;aspect-ratio:1/.82;background:var(--pk-paper);border:2.5px solid var(--pk-ink);border-radius:16px;box-shadow:4px 4px 0 var(--pk-ink);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;cursor:pointer;user-select:none;-webkit-tap-highlight-color:transparent;transition:transform .1s,box-shadow .1s,background-color .25s}
|
|
254
|
-
.pkb-tile:active{transform:translate(3px,3px);box-shadow:1px 1px 0 var(--pk-ink)}
|
|
325
|
+
.pkb-grid{display:grid;grid-template-columns:repeat(var(--pk-n),minmax(0,1fr));gap:calc(12px*var(--pk-d))}
|
|
326
|
+
.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}
|
|
327
|
+
.pkb-tile:active:not(.pkb-free){transform:translate(3px,3px);box-shadow:1px 1px 0 var(--pk-ink)}
|
|
255
328
|
.pkb-tile input{position:absolute;opacity:0;pointer-events:none;width:1px;height:1px}
|
|
256
329
|
.pkb-tile:has(input:focus-visible){outline:3px solid var(--pk-ink);outline-offset:3px}
|
|
257
|
-
.pkb-emo{font-size:32px;line-height:1;transition:opacity .2s}
|
|
258
|
-
.pkb-lbl{font-weight:800;font-size:13px;line-height:1.25;text-align:center;padding:0 6px;transition:opacity .2s;word-break:keep-all;overflow-wrap:anywhere}
|
|
259
|
-
.pkb-stamp{position:absolute;left:50%;top:50%;width:64%;aspect-ratio:1;border-radius:50%;border:3px solid var(--pk-red);box-shadow:inset 0 0 0 2px var(--pk-paper),inset 0 0 0 4px var(--pk-red);color:var(--pk-red);display:grid;place-items:center;font-family:var(--pk-display);font-size:20px;letter-spacing:.04em;opacity:0;transform:translate(-50%,-50%) rotate(-16deg) scale(1.7);transition:transform .25s cubic-bezier(.3,1.5,.5,1),opacity .15s;pointer-events:none}
|
|
330
|
+
.pkb-emo{font-size:max(14px,calc(32px*var(--pk-s)));line-height:1;transition:opacity .2s}
|
|
331
|
+
.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}
|
|
332
|
+
.pkb-stamp{position:absolute;left:50%;top:50%;width:64%;aspect-ratio:1;border-radius:50%;border:3px solid var(--pk-red);box-shadow:inset 0 0 0 2px var(--pk-paper),inset 0 0 0 4px var(--pk-red);color:var(--pk-red);display:grid;place-items:center;font-family:var(--pk-display);font-size:max(9px,calc(20px*var(--pk-s)));letter-spacing:.04em;opacity:0;transform:translate(-50%,-50%) rotate(-16deg) scale(1.7);transition:transform .25s cubic-bezier(.3,1.5,.5,1),opacity .15s;pointer-events:none}
|
|
260
333
|
.pkb-tile.pkb-on .pkb-stamp{opacity:.9;transform:translate(-50%,-50%) rotate(-16deg) scale(1)}
|
|
261
334
|
.pkb-tile.pkb-on .pkb-emo,.pkb-tile.pkb-on .pkb-lbl{opacity:.35}
|
|
335
|
+
.pkb-tile.pkb-free{cursor:default;container-type:inline-size}
|
|
336
|
+
.pkb-tile.pkb-free .pkb-stamp{width:72%;opacity:1;transition:none;font-size:17cqi;letter-spacing:.02em;line-height:1;text-align:center;background:var(--pk-red);color:var(--pk-paper);border-color:var(--pk-red);box-shadow:inset 0 0 0 2px var(--pk-red),inset 0 0 0 4px var(--pk-paper)}
|
|
262
337
|
.pkb-tile.pkb-line{background-color:var(--pk-mint)}
|
|
263
338
|
.pkb-tile.pkb-line::before{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:linear-gradient(110deg,transparent 30%,rgba(255,255,255,.9) 45%,transparent 60%) no-repeat;background-size:250% 100%;background-position:200% 0;animation:pkb-glint 3.2s ease-in-out var(--pkb-gd,0s) infinite}
|
|
264
339
|
@keyframes pkb-glint{0%{background-position:200% 0}30%,100%{background-position:-100% 0}}
|
|
@@ -275,11 +350,11 @@ var CSS = `
|
|
|
275
350
|
.pkb-lines{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible;z-index:2;transition:opacity .4s}
|
|
276
351
|
.pkb-board.pkb-all .pkb-lines{opacity:0}
|
|
277
352
|
.pkb-lines line{fill:none;stroke-linecap:round;stroke-dasharray:1;stroke-dashoffset:0}
|
|
278
|
-
.pkb-lines .pkb-u{stroke:var(--pk-paper);stroke-width:
|
|
279
|
-
.pkb-lines .pkb-o{stroke:var(--pk-ink);stroke-width:
|
|
353
|
+
.pkb-lines .pkb-u{stroke:var(--pk-paper);stroke-width:calc(13px*var(--pk-d))}
|
|
354
|
+
.pkb-lines .pkb-o{stroke:var(--pk-ink);stroke-width:calc(6px*var(--pk-d))}
|
|
280
355
|
.pkb-lines .pkb-draw line{animation:pkb-draw .45s ease-out .3s both}
|
|
281
356
|
@keyframes pkb-draw{from{stroke-dashoffset:1}}
|
|
282
|
-
.pkb-foot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:18px;font-weight:800;font-size:13px}
|
|
357
|
+
.pkb-foot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:18px;font-weight:800;font-size:13px;word-break:auto-phrase}
|
|
283
358
|
.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}
|
|
284
359
|
.pkb-flash{position:absolute;inset:0;z-index:7;background:#fff;opacity:0;pointer-events:none}
|
|
285
360
|
.pkb-flash.pkb-go{animation:pkb-flash .4s ease-out}
|
|
@@ -294,7 +369,7 @@ var CSS = `
|
|
|
294
369
|
.pkb-float{position:absolute;z-index:11;pointer-events:none;white-space:nowrap;font-family:var(--pk-display);font-weight:400;line-height:1;-webkit-text-stroke:4px var(--pk-ink);paint-order:stroke fill;transform:translate(-50%,-50%);animation:pkb-rise .95s cubic-bezier(.2,.9,.3,1) both}
|
|
295
370
|
.pkb-float.pkb-pt{font-size:22px;color:var(--pk-paper)}
|
|
296
371
|
.pkb-float.pkb-cmb{font-size:17px}
|
|
297
|
-
.pkb-float.pkb-big{font-size:clamp(
|
|
372
|
+
.pkb-float.pkb-big{font-size:clamp(14px,5.6cqi,34px);color:#FFE066}
|
|
298
373
|
.pkb-float.pkb-rch{font-size:20px;color:var(--pk-red);-webkit-text-stroke:4px var(--pk-paper)}
|
|
299
374
|
@keyframes pkb-rise{0%{opacity:0;transform:translate(-50%,-20%) scale(.4)}18%{opacity:1;transform:translate(-50%,-60%) scale(1.2)}30%{transform:translate(-50%,-70%) scale(1)}75%{opacity:1}100%{opacity:0;transform:translate(-50%,-190%) scale(1)}}
|
|
300
375
|
.pkb-perfect{position:absolute;inset:0;z-index:8;display:grid;place-items:center;padding:20px;background:rgba(42,31,20,.5);opacity:0;visibility:hidden;transition:opacity .3s,visibility .3s}
|
|
@@ -305,7 +380,7 @@ var CSS = `
|
|
|
305
380
|
.pkb-perfect.pkb-show .pkb-medal{animation:pkb-slam .38s cubic-bezier(.5,0,.75,0) .35s forwards}
|
|
306
381
|
@keyframes pkb-slam{to{opacity:1;transform:rotate(-14deg) scale(1)}}
|
|
307
382
|
.pkb-ptitle{font-family:var(--pk-display);font-weight:400;font-size:30px;line-height:1.3;color:inherit}
|
|
308
|
-
.pkb-pbody{font-weight:800;font-size:13.5px;line-height:1.6;margin:6px 0 14px;white-space:pre-line}
|
|
383
|
+
.pkb-pbody{font-weight:800;font-size:13.5px;line-height:1.6;margin:6px 0 14px;white-space:pre-line;word-break:auto-phrase}
|
|
309
384
|
.pkb-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:16px}
|
|
310
385
|
.pkb-stats>div{border:2px solid var(--pk-ink);border-radius:12px;padding:6px 4px 5px;background:#fff}
|
|
311
386
|
.pkb-stats small{display:block;font-size:9.5px;font-weight:800;letter-spacing:.1em}
|
|
@@ -315,32 +390,20 @@ var CSS = `
|
|
|
315
390
|
.pkb-fx{position:absolute;inset:0;width:100%;height:100%;z-index:10;pointer-events:none}
|
|
316
391
|
.pkb-demo{max-width:720px;margin:12px auto 4px;display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-family:var(--pk-body);font-size:12px;color:#666}
|
|
317
392
|
.pkb-demo button{appearance:none;border:1px solid #ccc;background:#f3f3f3;color:#222;border-radius:999px;padding:5px 12px;font-size:12px;cursor:pointer}
|
|
318
|
-
@container (max-width:560px){.pkb-board{padding:18px 14px 16px}.pkb-head{flex-direction:column;align-items:stretch;gap:12px}.pkb-score{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.pkb-pill,.pkb-pill.pkb-sc{min-width:0;padding:5px 4px 4px}.pkb-pill b{font-size:19px}.pkb-title{font-size:26px}.pkb-grid{gap:8px}.pkb-tile{border-radius:12px;box-shadow:3px 3px 0 var(--pk-ink);gap:3px;aspect-ratio:1/1}.pkb-emo{font-size:24px}.pkb-lbl{font-size:10.5px;padding:0 2px}.pkb-stamp{font-size:14px}.pkb-tile.pkb-reach::after{font-size:9px;padding:2px 5px;top:-9px;right:-5px}.pkb-lines .pkb-u{stroke-width:
|
|
393
|
+
@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}}
|
|
394
|
+
@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}}
|
|
395
|
+
@container (max-width:320px){.pkb-board{padding-inline:8px}.pkb-grid{gap:max(3px,calc(5px*var(--pk-d)))}.pkb-lbl{font-size:max(8.5px,calc(9.5px*var(--pk-s)));padding:0}}
|
|
319
396
|
@media (prefers-reduced-motion:reduce){.pkb-board.pkb-shake,.pkb-board.pkb-punch,.pkb-flash.pkb-go,.pkb-tile.pkb-hop,.pkb-tile.pkb-squash,.pkb-tile.pkb-reach,.pkb-tile.pkb-line::before,.pkb-pill.pkb-hot,.pkb-bump,.pkb-tile.pkb-reach::after,.pkb-banner::before,.pkb-lines .pkb-draw line,.pkb-kicker.pkb-gold{animation:none}.pkb-banner.pkb-show{animation:pkb-fade 1.8s forwards}.pkb-float{animation:pkb-fade 1s both}.pkb-perfect.pkb-show .pkb-medal{animation:none;opacity:1;transform:rotate(-14deg)}.pkb-card{transition:none}}
|
|
320
397
|
@keyframes pkb-fade{0%,100%{opacity:0}15%,80%{opacity:1}}
|
|
321
398
|
`;
|
|
322
399
|
|
|
323
400
|
// src/core.ts
|
|
324
|
-
var LINES =
|
|
325
|
-
[0, 1, 2, 3],
|
|
326
|
-
[4, 5, 6, 7],
|
|
327
|
-
[8, 9, 10, 11],
|
|
328
|
-
[12, 13, 14, 15],
|
|
329
|
-
[0, 4, 8, 12],
|
|
330
|
-
[1, 5, 9, 13],
|
|
331
|
-
[2, 6, 10, 14],
|
|
332
|
-
[3, 7, 11, 15],
|
|
333
|
-
[0, 5, 10, 15],
|
|
334
|
-
[3, 6, 9, 12]
|
|
335
|
-
];
|
|
336
|
-
var DEMO_START = [0, 1, 2, 3, 5, 10];
|
|
401
|
+
var LINES = bingoLines(4);
|
|
337
402
|
var CONFETTI = ["#E8473B", "#2F6FED", "#1FA971", "#FFFDF6", "#2A1F14", "#FF9F1C"];
|
|
338
403
|
var COMBO_COLORS = ["#FF9F1C", "#1FA971", "#2F6FED", "#9B5DE5", "#E8473B"];
|
|
339
404
|
var COMBO_MS = 2500;
|
|
340
405
|
var MUTE_KEY = "packing-bingo-muted";
|
|
341
406
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
342
|
-
var normalize = (v) => Array.from({ length: 16 }, (_, i) => !!v?.[i]);
|
|
343
|
-
var demoStart = () => Array.from({ length: 16 }, (_, i) => DEMO_START.includes(i));
|
|
344
407
|
function readStore(key) {
|
|
345
408
|
try {
|
|
346
409
|
return localStorage.getItem(key);
|
|
@@ -369,15 +432,21 @@ function ensureAssets(fonts) {
|
|
|
369
432
|
document.head.appendChild(l);
|
|
370
433
|
}
|
|
371
434
|
}
|
|
372
|
-
function template(
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
435
|
+
function template(L, M, demo) {
|
|
436
|
+
const n = L.size;
|
|
437
|
+
const tiles = Array.from({ length: n * n }, (_, c) => {
|
|
438
|
+
const gd = `--pkb-gd:${+((Math.floor(c / n) + c % n) * 0.36 / Math.max(1, n - 1)).toFixed(3)}s`;
|
|
439
|
+
if (c === L.center)
|
|
440
|
+
return `<div class="pkb-tile pkb-free pkb-on" style="${gd}"><span class="pkb-stamp">${esc(M.free)}</span></div>`;
|
|
441
|
+
const it = L.items[L.itemCell.indexOf(c)];
|
|
442
|
+
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>`;
|
|
443
|
+
}).join("");
|
|
444
|
+
const scale = `--pk-n:${n};--pk-s:${+Math.min(1.35, 4 / n).toFixed(3)};--pk-d:${+Math.min(1, 4 / n).toFixed(3)}`;
|
|
376
445
|
const demoBar = demo ? `<div class="pkb-demo"><span>${esc(M.demoLabel)}</span><button type="button" data-r="dB">${esc(M.demoBingo)}</button><button type="button" data-r="dP">${esc(M.demoPerfect)}</button><button type="button" data-r="dR">${esc(M.demoRestore)}</button></div>` : "";
|
|
377
|
-
return `<div class="pkb"><div class="pkb-board">
|
|
446
|
+
return `<div class="pkb" style="${scale}"><div class="pkb-board">
|
|
378
447
|
<div class="pkb-head"><div>
|
|
379
448
|
<div class="pkb-tags"><span class="pkb-kicker" data-r="kick">${esc(M.tag)}</span><button type="button" class="pkb-snd" data-r="snd"></button></div>
|
|
380
|
-
<h2 class="pkb-title">${esc(M.title)}</h2
|
|
449
|
+
<h2 class="pkb-title">${esc(M.title)}</h2>${M.subtitle ? `<p class="pkb-sub">${esc(M.subtitle)}</p>` : ""}</div>
|
|
381
450
|
<div class="pkb-side"><div class="pkb-score">
|
|
382
451
|
<div class="pkb-pill"><small>${esc(M.packed)}</small><b data-r="cnt"></b></div>
|
|
383
452
|
<div class="pkb-pill" data-r="bp"><small>${esc(M.bingo)}</small><b data-r="bng"></b></div>
|
|
@@ -388,14 +457,22 @@ function template(items, M, demo) {
|
|
|
388
457
|
<div class="pkb-foot"><span data-r="msg" aria-live="polite"></span><button type="button" class="pkb-reset" data-r="reset">${esc(M.reset)}</button></div>
|
|
389
458
|
<div class="pkb-flash" data-r="flash"></div>
|
|
390
459
|
<div class="pkb-perfect" data-r="perfect"><div class="pkb-card" role="dialog" aria-label="${esc(M.perfectTitle)}">
|
|
391
|
-
<div class="pkb-medal">${esc(M.perfectStamp)}</div><h3 class="pkb-ptitle">${esc(M.perfectTitle)}</h3><p class="pkb-pbody">${esc(M.
|
|
460
|
+
<div class="pkb-medal">${esc(M.perfectStamp)}</div><h3 class="pkb-ptitle">${esc(M.perfectTitle)}</h3><p class="pkb-pbody" data-r="pbody">${esc(perfectText(M, L.items.length))}</p>
|
|
392
461
|
<div class="pkb-stats"><div><small>${esc(M.statScore)}</small><b data-r="sScore">0</b></div><div><small>${esc(M.statCombo)}</small><b data-r="sCombo">\xD70</b></div><div><small>${esc(M.statLines)}</small><b data-r="sBingo">0</b></div></div>
|
|
393
462
|
<button type="button" class="pkb-close" data-r="close">${esc(M.close)}</button></div></div>
|
|
394
463
|
<canvas class="pkb-fx"></canvas></div>${demoBar}</div>`;
|
|
395
464
|
}
|
|
465
|
+
var perfectText = (M, total) => typeof M.perfectBody === "function" ? M.perfectBody(total) : M.perfectBody;
|
|
396
466
|
function createPackingBingo(container, options = {}) {
|
|
397
|
-
const
|
|
398
|
-
|
|
467
|
+
const L = resolveLayout(options);
|
|
468
|
+
const { items, lines, cellLines } = L;
|
|
469
|
+
const n = L.size, N = items.length;
|
|
470
|
+
const normalize = (v) => Array.from({ length: N }, (_, i) => !!v?.[i]);
|
|
471
|
+
const demoCells = [...cellLines[0], ...cellLines[2 * n].slice(1, -1)];
|
|
472
|
+
const demoItems = new Set(L.itemCell.flatMap((c, i) => demoCells.includes(c) ? [i] : []));
|
|
473
|
+
const demoStart = () => Array.from({ length: N }, (_, i) => demoItems.has(i));
|
|
474
|
+
const far = (c) => Math.floor(c / n) + c % n;
|
|
475
|
+
const dScale = Math.min(1, 4 / n);
|
|
399
476
|
const M = { ...ja, ...options.messages };
|
|
400
477
|
if (options.title != null) M.title = options.title;
|
|
401
478
|
if (options.tag != null) M.tag = options.tag;
|
|
@@ -416,7 +493,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
416
493
|
const wait = (ms) => new Promise((r) => window.setTimeout(r, ms));
|
|
417
494
|
let gen = 0;
|
|
418
495
|
ensureAssets(options.fonts !== false);
|
|
419
|
-
container.innerHTML = template(
|
|
496
|
+
container.innerHTML = template(L, M, demo);
|
|
420
497
|
const $ = (r) => {
|
|
421
498
|
const el = container.querySelector(`[data-r="${r}"]`);
|
|
422
499
|
if (!el) throw new Error(`[packing-bingo] internal: missing element "${r}"`);
|
|
@@ -428,7 +505,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
428
505
|
const svg = container.querySelector(".pkb-lines");
|
|
429
506
|
const cv = container.querySelector(".pkb-fx");
|
|
430
507
|
const ctx = cv.getContext("2d");
|
|
431
|
-
const
|
|
508
|
+
const cellTiles = Array.from(grid.children);
|
|
509
|
+
const tiles = L.itemCell.map((c) => cellTiles[c]);
|
|
510
|
+
const freeTile = L.center >= 0 ? cellTiles[L.center] : null;
|
|
432
511
|
const ins = tiles.map((t) => t.querySelector("input"));
|
|
433
512
|
const kick = $("kick"), snd = $("snd"), perfectEl = $("perfect");
|
|
434
513
|
let checked = normalize(void 0);
|
|
@@ -463,14 +542,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
463
542
|
cv.width = Math.round(BW * dpr);
|
|
464
543
|
cv.height = Math.round(BH * dpr);
|
|
465
544
|
};
|
|
466
|
-
const piece = (x, y, vx, vy, g, s, life,
|
|
545
|
+
const piece = (x, y, vx, vy, g, s, life, shape2) => ({
|
|
467
546
|
x,
|
|
468
547
|
y,
|
|
469
548
|
vx,
|
|
470
549
|
vy,
|
|
471
550
|
g,
|
|
472
551
|
life,
|
|
473
|
-
shape,
|
|
552
|
+
shape: shape2,
|
|
474
553
|
w: (5 + Math.random() * 6) * s,
|
|
475
554
|
h: (3 + Math.random() * 5) * s,
|
|
476
555
|
r: Math.random() * 6,
|
|
@@ -480,9 +559,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
480
559
|
const go = () => {
|
|
481
560
|
if (!raf && ctx) raf = requestAnimationFrame(tick);
|
|
482
561
|
};
|
|
483
|
-
function burst(x, y,
|
|
562
|
+
function burst(x, y, n2, o = {}) {
|
|
484
563
|
if (calm) return;
|
|
485
|
-
for (let i = 0; i <
|
|
564
|
+
for (let i = 0; i < n2; i++) {
|
|
486
565
|
const a = (o.angle ?? -Math.PI / 2) + (Math.random() - 0.5) * (o.spread ?? Math.PI * 2);
|
|
487
566
|
const v = (o.speed ?? 10) * (0.35 + Math.random() * 0.85);
|
|
488
567
|
const sh = Math.random() < (o.star ?? 0.15) ? "star" : Math.random() < 0.3 ? "round" : "rect";
|
|
@@ -573,11 +652,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
573
652
|
void el.offsetWidth;
|
|
574
653
|
el.classList.add(cls);
|
|
575
654
|
};
|
|
576
|
-
const hop = (
|
|
655
|
+
const hop = (t, d) => {
|
|
577
656
|
if (calm) return;
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
replay(
|
|
657
|
+
t.classList.remove("pkb-squash");
|
|
658
|
+
t.style.setProperty("--pkb-d", d + "ms");
|
|
659
|
+
replay(t, "pkb-hop");
|
|
581
660
|
};
|
|
582
661
|
grid.addEventListener("animationend", (e) => {
|
|
583
662
|
const t = e.target;
|
|
@@ -625,8 +704,8 @@ function createPackingBingo(container, options = {}) {
|
|
|
625
704
|
st(t0);
|
|
626
705
|
if (!calm) replay($("sp"), "pkb-bump");
|
|
627
706
|
}
|
|
628
|
-
const showGain = (
|
|
629
|
-
floatText(`+${
|
|
707
|
+
const showGain = (pts, x, y, cls = "pt") => {
|
|
708
|
+
floatText(`+${pts}`, x, y, cls);
|
|
630
709
|
countScore();
|
|
631
710
|
};
|
|
632
711
|
function countUp(el, a, b, d, fmt, ticks = false) {
|
|
@@ -646,15 +725,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
646
725
|
};
|
|
647
726
|
st(t0);
|
|
648
727
|
}
|
|
649
|
-
const ctr = (
|
|
650
|
-
const t = tiles[i];
|
|
728
|
+
const ctr = (t) => {
|
|
651
729
|
return { x: t.offsetLeft + t.offsetWidth / 2, y: t.offsetTop + t.offsetHeight / 2 };
|
|
652
730
|
};
|
|
653
731
|
function drawLines(fresh = []) {
|
|
654
732
|
svg.setAttribute("viewBox", `0 0 ${gw.clientWidth} ${gw.clientHeight}`);
|
|
655
733
|
svg.innerHTML = cur.map((k) => {
|
|
656
|
-
const a = ctr(
|
|
657
|
-
const dx = b.x - a.x, dy = b.y - a.y, d = Math.hypot(dx, dy) || 1, ex = dx / d *
|
|
734
|
+
const a = ctr(cellTiles[cellLines[k][0]]), b = ctr(cellTiles[cellLines[k][n - 1]]);
|
|
735
|
+
const dx = b.x - a.x, dy = b.y - a.y, d = Math.hypot(dx, dy) || 1, r = 26 * dScale, ex = dx / d * r, ey = dy / d * r;
|
|
658
736
|
const at = `x1="${a.x - ex}" y1="${a.y - ey}" x2="${b.x + ex}" y2="${b.y + ey}" pathLength="1"`;
|
|
659
737
|
return `<g${fresh.includes(k) ? ' class="pkb-draw"' : ""}><line class="pkb-u" ${at}/><line class="pkb-o" ${at}/></g>`;
|
|
660
738
|
}).join("");
|
|
@@ -701,29 +779,29 @@ function createPackingBingo(container, options = {}) {
|
|
|
701
779
|
later(() => sfx.reach(), 150);
|
|
702
780
|
}
|
|
703
781
|
function bingo(fresh, pts) {
|
|
704
|
-
const
|
|
705
|
-
const seq = [...new Set(fresh.flatMap((k) =>
|
|
782
|
+
const nl = fresh.length;
|
|
783
|
+
const seq = [...new Set(fresh.flatMap((k) => cellLines[k]))].map((c) => cellTiles[c]);
|
|
706
784
|
const step = Math.min(70, 300 / seq.length);
|
|
707
785
|
seq.forEach(
|
|
708
|
-
(
|
|
709
|
-
hop(
|
|
710
|
-
const p = rel(
|
|
786
|
+
(t, j) => later(() => {
|
|
787
|
+
hop(t, 0);
|
|
788
|
+
const p = rel(t);
|
|
711
789
|
burst(p.x, p.y, 14, { speed: 5, g: 0.15, life: 30, size: 0.7, star: 0.8 });
|
|
712
790
|
sfx.note(j + 3);
|
|
713
791
|
}, j * step)
|
|
714
792
|
);
|
|
715
793
|
later(() => {
|
|
716
|
-
$("bT").textContent =
|
|
717
|
-
$("bS").textContent =
|
|
794
|
+
$("bT").textContent = nl >= 3 ? M.bannerTriple : nl === 2 ? M.bannerDouble : M.bannerSingle;
|
|
795
|
+
$("bS").textContent = nl > 1 ? M.bannerMulti(nl) : M.bannerNth(cur.length);
|
|
718
796
|
replay($("banner"), "pkb-show");
|
|
719
797
|
if (!calm) {
|
|
720
798
|
replay($("flash"), "pkb-go");
|
|
721
799
|
replay(board, "pkb-punch");
|
|
722
800
|
}
|
|
723
801
|
const c = rel(gw);
|
|
724
|
-
burst(c.x, c.y,
|
|
725
|
-
sfx.bingo(
|
|
726
|
-
buzz(
|
|
802
|
+
burst(c.x, c.y, nl > 1 ? 180 : 110, { speed: nl > 1 ? 16 : 13, star: 0.3 });
|
|
803
|
+
sfx.bingo(nl);
|
|
804
|
+
buzz(nl > 1 ? [40, 60, 40, 60, 90] : [40, 60, 40]);
|
|
727
805
|
if (pts) later(() => showGain(pts, c.x, c.y + 105, "big"), 350);
|
|
728
806
|
}, seq.length * step + 60);
|
|
729
807
|
}
|
|
@@ -731,9 +809,10 @@ function createPackingBingo(container, options = {}) {
|
|
|
731
809
|
const bonus = gotP ? 0 : 1e3;
|
|
732
810
|
gotP = true;
|
|
733
811
|
score += bonus;
|
|
734
|
-
|
|
812
|
+
cellTiles.forEach((t, c) => hop(t, far(c) * 180 / Math.max(1, n - 1)));
|
|
735
813
|
sfx.perfect();
|
|
736
814
|
if (!calm) replay($("flash"), "pkb-go");
|
|
815
|
+
$("pbody").textContent = perfectText(M, N);
|
|
737
816
|
$("sScore").textContent = "0";
|
|
738
817
|
$("sCombo").textContent = "\xD70";
|
|
739
818
|
$("sBingo").textContent = "0";
|
|
@@ -783,27 +862,28 @@ function createPackingBingo(container, options = {}) {
|
|
|
783
862
|
function render(src, quiet) {
|
|
784
863
|
const events = [];
|
|
785
864
|
const on = checked, c = on.filter(Boolean).length;
|
|
786
|
-
cur =
|
|
865
|
+
cur = lines.flatMap((l, k) => l.every((i) => on[i]) ? [k] : []);
|
|
787
866
|
const fresh = cur.filter((k) => !prevLines.has(k));
|
|
788
867
|
if ([...prevLines].some((k) => !cur.includes(k))) {
|
|
789
868
|
cancelFx();
|
|
790
|
-
if (c <
|
|
869
|
+
if (c < N) perfectEl.classList.remove("pkb-show");
|
|
791
870
|
}
|
|
792
|
-
const hit = new Set(cur.flatMap((k) =>
|
|
871
|
+
const hit = new Set(cur.flatMap((k) => cellLines[k]));
|
|
793
872
|
const reach = /* @__PURE__ */ new Set();
|
|
794
|
-
for (const l of
|
|
873
|
+
for (const l of lines) {
|
|
795
874
|
const m = l.filter((i) => !on[i]);
|
|
796
875
|
if (m.length === 1) reach.add(m[0]);
|
|
797
876
|
}
|
|
798
877
|
tiles.forEach((t, i) => {
|
|
799
878
|
t.classList.toggle("pkb-on", on[i]);
|
|
800
|
-
t.classList.toggle("pkb-line", hit.has(i));
|
|
879
|
+
t.classList.toggle("pkb-line", hit.has(L.itemCell[i]));
|
|
801
880
|
t.classList.toggle("pkb-reach", reach.has(i));
|
|
802
881
|
});
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
kick.
|
|
806
|
-
|
|
882
|
+
freeTile?.classList.toggle("pkb-line", hit.has(L.center));
|
|
883
|
+
board.classList.toggle("pkb-all", c === N);
|
|
884
|
+
kick.classList.toggle("pkb-gold", c === N);
|
|
885
|
+
kick.textContent = c === N ? M.perfectTag : M.tag;
|
|
886
|
+
$("cnt").textContent = `${c}/${N}`;
|
|
807
887
|
$("bng").textContent = String(cur.length);
|
|
808
888
|
const bp = $("bp");
|
|
809
889
|
if (cur.length > lastN) {
|
|
@@ -820,7 +900,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
820
900
|
score += linePts;
|
|
821
901
|
if (quiet) {
|
|
822
902
|
if (linePts) countScore();
|
|
823
|
-
} else if (c ===
|
|
903
|
+
} else if (c === N && !prevAll) perfect(events);
|
|
824
904
|
else if (fresh.length) {
|
|
825
905
|
bingo(fresh, linePts);
|
|
826
906
|
const e = { newLines: fresh, totalLines: cur.length, score };
|
|
@@ -831,9 +911,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
831
911
|
}
|
|
832
912
|
prevLines = new Set(cur);
|
|
833
913
|
prevReach = reach;
|
|
834
|
-
prevAll = c ===
|
|
914
|
+
prevAll = c === N;
|
|
835
915
|
const labels = [...reach].map((i) => items[i].label);
|
|
836
|
-
$("msg").textContent = c ===
|
|
916
|
+
$("msg").textContent = c === N ? M.statusDone : labels.length ? M.statusReach(labels) : cur.length ? M.statusLines(cur.length, N - c) : M.statusStart(N - c);
|
|
837
917
|
return events;
|
|
838
918
|
}
|
|
839
919
|
const flush = (events) => {
|
|
@@ -860,7 +940,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
860
940
|
got.clear();
|
|
861
941
|
gotL.clear();
|
|
862
942
|
checked.forEach((v, i) => v && got.add(i));
|
|
863
|
-
|
|
943
|
+
lines.forEach((l, k) => l.every((i) => checked[i]) && gotL.add(k));
|
|
864
944
|
prevLines = new Set(gotL);
|
|
865
945
|
prevReach = /* @__PURE__ */ new Set();
|
|
866
946
|
prevAll = checked.every(Boolean);
|
|
@@ -920,12 +1000,13 @@ function createPackingBingo(container, options = {}) {
|
|
|
920
1000
|
}
|
|
921
1001
|
};
|
|
922
1002
|
$("dB").addEventListener("click", () => {
|
|
923
|
-
const open = () =>
|
|
924
|
-
const pick = checked.every(Boolean) ? [...
|
|
1003
|
+
const open = () => lines.map((l) => l.filter((i) => !checked[i])).filter((m) => m.length).sort((a, b) => a.length - b.length)[0] ?? [];
|
|
1004
|
+
const pick = checked.every(Boolean) ? [...lines[0]] : open();
|
|
925
1005
|
void run(pick.map((i) => ({ i, delay: 320 })));
|
|
926
1006
|
});
|
|
927
1007
|
$("dP").addEventListener("click", () => {
|
|
928
|
-
const
|
|
1008
|
+
const all = items.map((_, i) => i);
|
|
1009
|
+
const miss = checked.every(Boolean) ? all : all.filter((i) => !checked[i]);
|
|
929
1010
|
void run(miss.map((i, k) => k < miss.length - 1 ? { i, delay: 170, quiet: true } : { i, delay: 450 }));
|
|
930
1011
|
});
|
|
931
1012
|
$("dR").addEventListener("click", () => {
|
|
@@ -953,14 +1034,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
953
1034
|
return {
|
|
954
1035
|
setChecked(next, opts = {}) {
|
|
955
1036
|
if (dead) return;
|
|
956
|
-
const
|
|
957
|
-
const changed =
|
|
1037
|
+
const n2 = normalize(next);
|
|
1038
|
+
const changed = n2.flatMap((v, i) => v !== checked[i] ? [i] : []);
|
|
958
1039
|
if (!changed.length) return;
|
|
959
1040
|
if (!opts.animate || changed.length !== 1) {
|
|
960
|
-
baseline(
|
|
1041
|
+
baseline(n2);
|
|
961
1042
|
return;
|
|
962
1043
|
}
|
|
963
|
-
flush(apply(
|
|
1044
|
+
flush(apply(n2, changed[0], false));
|
|
964
1045
|
},
|
|
965
1046
|
reset() {
|
|
966
1047
|
if (!dead) request(normalize(void 0), null, { reset: true });
|
|
@@ -969,7 +1050,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
969
1050
|
if (!dead) render(null, true);
|
|
970
1051
|
},
|
|
971
1052
|
getState() {
|
|
972
|
-
return { checked: checked.slice(), lines: cur.slice(), score, maxCombo, complete: checked.every(Boolean) };
|
|
1053
|
+
return { checked: checked.slice(), lines: cur.slice(), score, maxCombo, complete: checked.every(Boolean), size: n, free: L.free };
|
|
973
1054
|
},
|
|
974
1055
|
destroy() {
|
|
975
1056
|
if (dead) return;
|
|
@@ -987,6 +1068,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
987
1068
|
|
|
988
1069
|
export {
|
|
989
1070
|
DEFAULT_ITEMS,
|
|
1071
|
+
bingoLines,
|
|
990
1072
|
ja,
|
|
991
1073
|
en,
|
|
992
1074
|
resolveStorageKey,
|