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
|
@@ -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",
|
|
@@ -195,17 +268,34 @@ function createSound(isMuted) {
|
|
|
195
268
|
};
|
|
196
269
|
}
|
|
197
270
|
|
|
271
|
+
// src/storage-key.ts
|
|
272
|
+
function resolveStorageKey(options) {
|
|
273
|
+
if (options.controlled) return null;
|
|
274
|
+
if (options.storageKey !== void 0) return options.storageKey || null;
|
|
275
|
+
if (options.demo) return null;
|
|
276
|
+
return itemsKey(layoutItems(options));
|
|
277
|
+
}
|
|
278
|
+
function itemsKey(items) {
|
|
279
|
+
const text = items.map((it) => `${it.emoji}\0${it.label}`).join("");
|
|
280
|
+
let h = 2166136261;
|
|
281
|
+
for (let i = 0; i < text.length; i++) {
|
|
282
|
+
h ^= text.charCodeAt(i);
|
|
283
|
+
h = Math.imul(h, 16777619);
|
|
284
|
+
}
|
|
285
|
+
return `packing-bingo:${(h >>> 0).toString(36)}`;
|
|
286
|
+
}
|
|
287
|
+
|
|
198
288
|
// src/styles.ts
|
|
199
289
|
var FONT_URL = "https://fonts.googleapis.com/css2?family=Dela+Gothic+One&family=M+PLUS+Rounded+1c:wght@500;800&display=swap";
|
|
200
290
|
var CSS = `
|
|
201
|
-
.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%}
|
|
202
292
|
.pkb,.pkb *{box-sizing:border-box;margin:0;padding:0}
|
|
203
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}
|
|
204
294
|
.pkb-board.pkb-shake{animation:pkb-shake .45s cubic-bezier(.36,.07,.19,.97)}
|
|
205
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)}}
|
|
206
296
|
.pkb-board.pkb-punch{animation:pkb-punch .4s cubic-bezier(.3,1.6,.5,1)}
|
|
207
297
|
@keyframes pkb-punch{30%{transform:scale(1.018)}}
|
|
208
|
-
.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}
|
|
209
299
|
.pkb-tags{display:flex;gap:8px;align-items:center}
|
|
210
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}
|
|
211
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}
|
|
@@ -213,7 +303,7 @@ var CSS = `
|
|
|
213
303
|
.pkb button{font-family:var(--pk-body)}
|
|
214
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}
|
|
215
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}
|
|
216
|
-
.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}
|
|
217
307
|
.pkb-side{display:flex;flex-direction:column;gap:8px;flex:none}
|
|
218
308
|
.pkb-score{display:flex;gap:10px}
|
|
219
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)}
|
|
@@ -232,16 +322,18 @@ var CSS = `
|
|
|
232
322
|
.pkb-combo.pkb-on .pkb-bar i{animation:pkb-drain 2.5s linear forwards}
|
|
233
323
|
@keyframes pkb-drain{from{transform:scaleX(1)}to{transform:scaleX(0)}}
|
|
234
324
|
.pkb-gw{position:relative}
|
|
235
|
-
.pkb-grid{display:grid;grid-template-columns:repeat(
|
|
236
|
-
.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}
|
|
237
|
-
.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)}
|
|
238
328
|
.pkb-tile input{position:absolute;opacity:0;pointer-events:none;width:1px;height:1px}
|
|
239
329
|
.pkb-tile:has(input:focus-visible){outline:3px solid var(--pk-ink);outline-offset:3px}
|
|
240
|
-
.pkb-emo{font-size:32px;line-height:1;transition:opacity .2s}
|
|
241
|
-
.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}
|
|
242
|
-
.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}
|
|
243
333
|
.pkb-tile.pkb-on .pkb-stamp{opacity:.9;transform:translate(-50%,-50%) rotate(-16deg) scale(1)}
|
|
244
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)}
|
|
245
337
|
.pkb-tile.pkb-line{background-color:var(--pk-mint)}
|
|
246
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}
|
|
247
339
|
@keyframes pkb-glint{0%{background-position:200% 0}30%,100%{background-position:-100% 0}}
|
|
@@ -258,11 +350,11 @@ var CSS = `
|
|
|
258
350
|
.pkb-lines{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible;z-index:2;transition:opacity .4s}
|
|
259
351
|
.pkb-board.pkb-all .pkb-lines{opacity:0}
|
|
260
352
|
.pkb-lines line{fill:none;stroke-linecap:round;stroke-dasharray:1;stroke-dashoffset:0}
|
|
261
|
-
.pkb-lines .pkb-u{stroke:var(--pk-paper);stroke-width:
|
|
262
|
-
.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))}
|
|
263
355
|
.pkb-lines .pkb-draw line{animation:pkb-draw .45s ease-out .3s both}
|
|
264
356
|
@keyframes pkb-draw{from{stroke-dashoffset:1}}
|
|
265
|
-
.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}
|
|
266
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}
|
|
267
359
|
.pkb-flash{position:absolute;inset:0;z-index:7;background:#fff;opacity:0;pointer-events:none}
|
|
268
360
|
.pkb-flash.pkb-go{animation:pkb-flash .4s ease-out}
|
|
@@ -277,7 +369,7 @@ var CSS = `
|
|
|
277
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}
|
|
278
370
|
.pkb-float.pkb-pt{font-size:22px;color:var(--pk-paper)}
|
|
279
371
|
.pkb-float.pkb-cmb{font-size:17px}
|
|
280
|
-
.pkb-float.pkb-big{font-size:clamp(
|
|
372
|
+
.pkb-float.pkb-big{font-size:clamp(14px,5.6cqi,34px);color:#FFE066}
|
|
281
373
|
.pkb-float.pkb-rch{font-size:20px;color:var(--pk-red);-webkit-text-stroke:4px var(--pk-paper)}
|
|
282
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)}}
|
|
283
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}
|
|
@@ -288,7 +380,7 @@ var CSS = `
|
|
|
288
380
|
.pkb-perfect.pkb-show .pkb-medal{animation:pkb-slam .38s cubic-bezier(.5,0,.75,0) .35s forwards}
|
|
289
381
|
@keyframes pkb-slam{to{opacity:1;transform:rotate(-14deg) scale(1)}}
|
|
290
382
|
.pkb-ptitle{font-family:var(--pk-display);font-weight:400;font-size:30px;line-height:1.3;color:inherit}
|
|
291
|
-
.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}
|
|
292
384
|
.pkb-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:16px}
|
|
293
385
|
.pkb-stats>div{border:2px solid var(--pk-ink);border-radius:12px;padding:6px 4px 5px;background:#fff}
|
|
294
386
|
.pkb-stats small{display:block;font-size:9.5px;font-weight:800;letter-spacing:.1em}
|
|
@@ -298,32 +390,20 @@ var CSS = `
|
|
|
298
390
|
.pkb-fx{position:absolute;inset:0;width:100%;height:100%;z-index:10;pointer-events:none}
|
|
299
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}
|
|
300
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}
|
|
301
|
-
@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}}
|
|
302
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}}
|
|
303
397
|
@keyframes pkb-fade{0%,100%{opacity:0}15%,80%{opacity:1}}
|
|
304
398
|
`;
|
|
305
399
|
|
|
306
400
|
// src/core.ts
|
|
307
|
-
var LINES =
|
|
308
|
-
[0, 1, 2, 3],
|
|
309
|
-
[4, 5, 6, 7],
|
|
310
|
-
[8, 9, 10, 11],
|
|
311
|
-
[12, 13, 14, 15],
|
|
312
|
-
[0, 4, 8, 12],
|
|
313
|
-
[1, 5, 9, 13],
|
|
314
|
-
[2, 6, 10, 14],
|
|
315
|
-
[3, 7, 11, 15],
|
|
316
|
-
[0, 5, 10, 15],
|
|
317
|
-
[3, 6, 9, 12]
|
|
318
|
-
];
|
|
319
|
-
var DEMO_START = [0, 1, 2, 3, 5, 10];
|
|
401
|
+
var LINES = bingoLines(4);
|
|
320
402
|
var CONFETTI = ["#E8473B", "#2F6FED", "#1FA971", "#FFFDF6", "#2A1F14", "#FF9F1C"];
|
|
321
403
|
var COMBO_COLORS = ["#FF9F1C", "#1FA971", "#2F6FED", "#9B5DE5", "#E8473B"];
|
|
322
404
|
var COMBO_MS = 2500;
|
|
323
405
|
var MUTE_KEY = "packing-bingo-muted";
|
|
324
406
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
325
|
-
var normalize = (v) => Array.from({ length: 16 }, (_, i) => !!v?.[i]);
|
|
326
|
-
var demoStart = () => Array.from({ length: 16 }, (_, i) => DEMO_START.includes(i));
|
|
327
407
|
function readStore(key) {
|
|
328
408
|
try {
|
|
329
409
|
return localStorage.getItem(key);
|
|
@@ -352,15 +432,21 @@ function ensureAssets(fonts) {
|
|
|
352
432
|
document.head.appendChild(l);
|
|
353
433
|
}
|
|
354
434
|
}
|
|
355
|
-
function template(
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
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)}`;
|
|
359
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>` : "";
|
|
360
|
-
return `<div class="pkb"><div class="pkb-board">
|
|
446
|
+
return `<div class="pkb" style="${scale}"><div class="pkb-board">
|
|
361
447
|
<div class="pkb-head"><div>
|
|
362
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>
|
|
363
|
-
<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>
|
|
364
450
|
<div class="pkb-side"><div class="pkb-score">
|
|
365
451
|
<div class="pkb-pill"><small>${esc(M.packed)}</small><b data-r="cnt"></b></div>
|
|
366
452
|
<div class="pkb-pill" data-r="bp"><small>${esc(M.bingo)}</small><b data-r="bng"></b></div>
|
|
@@ -371,20 +457,28 @@ function template(items, M, demo) {
|
|
|
371
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>
|
|
372
458
|
<div class="pkb-flash" data-r="flash"></div>
|
|
373
459
|
<div class="pkb-perfect" data-r="perfect"><div class="pkb-card" role="dialog" aria-label="${esc(M.perfectTitle)}">
|
|
374
|
-
<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>
|
|
375
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>
|
|
376
462
|
<button type="button" class="pkb-close" data-r="close">${esc(M.close)}</button></div></div>
|
|
377
463
|
<canvas class="pkb-fx"></canvas></div>${demoBar}</div>`;
|
|
378
464
|
}
|
|
465
|
+
var perfectText = (M, total) => typeof M.perfectBody === "function" ? M.perfectBody(total) : M.perfectBody;
|
|
379
466
|
function createPackingBingo(container, options = {}) {
|
|
380
|
-
const
|
|
381
|
-
|
|
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);
|
|
382
476
|
const M = { ...ja, ...options.messages };
|
|
383
477
|
if (options.title != null) M.title = options.title;
|
|
384
478
|
if (options.tag != null) M.tag = options.tag;
|
|
385
479
|
if (options.subtitle != null) M.subtitle = options.subtitle;
|
|
386
480
|
const controlled = !!options.controlled;
|
|
387
|
-
const storageKey =
|
|
481
|
+
const storageKey = resolveStorageKey(options);
|
|
388
482
|
const demo = !!options.demo;
|
|
389
483
|
const calm = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
390
484
|
let dead = false;
|
|
@@ -399,7 +493,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
399
493
|
const wait = (ms) => new Promise((r) => window.setTimeout(r, ms));
|
|
400
494
|
let gen = 0;
|
|
401
495
|
ensureAssets(options.fonts !== false);
|
|
402
|
-
container.innerHTML = template(
|
|
496
|
+
container.innerHTML = template(L, M, demo);
|
|
403
497
|
const $ = (r) => {
|
|
404
498
|
const el = container.querySelector(`[data-r="${r}"]`);
|
|
405
499
|
if (!el) throw new Error(`[packing-bingo] internal: missing element "${r}"`);
|
|
@@ -411,7 +505,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
411
505
|
const svg = container.querySelector(".pkb-lines");
|
|
412
506
|
const cv = container.querySelector(".pkb-fx");
|
|
413
507
|
const ctx = cv.getContext("2d");
|
|
414
|
-
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;
|
|
415
511
|
const ins = tiles.map((t) => t.querySelector("input"));
|
|
416
512
|
const kick = $("kick"), snd = $("snd"), perfectEl = $("perfect");
|
|
417
513
|
let checked = normalize(void 0);
|
|
@@ -446,14 +542,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
446
542
|
cv.width = Math.round(BW * dpr);
|
|
447
543
|
cv.height = Math.round(BH * dpr);
|
|
448
544
|
};
|
|
449
|
-
const piece = (x, y, vx, vy, g, s, life,
|
|
545
|
+
const piece = (x, y, vx, vy, g, s, life, shape2) => ({
|
|
450
546
|
x,
|
|
451
547
|
y,
|
|
452
548
|
vx,
|
|
453
549
|
vy,
|
|
454
550
|
g,
|
|
455
551
|
life,
|
|
456
|
-
shape,
|
|
552
|
+
shape: shape2,
|
|
457
553
|
w: (5 + Math.random() * 6) * s,
|
|
458
554
|
h: (3 + Math.random() * 5) * s,
|
|
459
555
|
r: Math.random() * 6,
|
|
@@ -463,9 +559,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
463
559
|
const go = () => {
|
|
464
560
|
if (!raf && ctx) raf = requestAnimationFrame(tick);
|
|
465
561
|
};
|
|
466
|
-
function burst(x, y,
|
|
562
|
+
function burst(x, y, n2, o = {}) {
|
|
467
563
|
if (calm) return;
|
|
468
|
-
for (let i = 0; i <
|
|
564
|
+
for (let i = 0; i < n2; i++) {
|
|
469
565
|
const a = (o.angle ?? -Math.PI / 2) + (Math.random() - 0.5) * (o.spread ?? Math.PI * 2);
|
|
470
566
|
const v = (o.speed ?? 10) * (0.35 + Math.random() * 0.85);
|
|
471
567
|
const sh = Math.random() < (o.star ?? 0.15) ? "star" : Math.random() < 0.3 ? "round" : "rect";
|
|
@@ -556,11 +652,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
556
652
|
void el.offsetWidth;
|
|
557
653
|
el.classList.add(cls);
|
|
558
654
|
};
|
|
559
|
-
const hop = (
|
|
655
|
+
const hop = (t, d) => {
|
|
560
656
|
if (calm) return;
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
replay(
|
|
657
|
+
t.classList.remove("pkb-squash");
|
|
658
|
+
t.style.setProperty("--pkb-d", d + "ms");
|
|
659
|
+
replay(t, "pkb-hop");
|
|
564
660
|
};
|
|
565
661
|
grid.addEventListener("animationend", (e) => {
|
|
566
662
|
const t = e.target;
|
|
@@ -608,8 +704,8 @@ function createPackingBingo(container, options = {}) {
|
|
|
608
704
|
st(t0);
|
|
609
705
|
if (!calm) replay($("sp"), "pkb-bump");
|
|
610
706
|
}
|
|
611
|
-
const showGain = (
|
|
612
|
-
floatText(`+${
|
|
707
|
+
const showGain = (pts, x, y, cls = "pt") => {
|
|
708
|
+
floatText(`+${pts}`, x, y, cls);
|
|
613
709
|
countScore();
|
|
614
710
|
};
|
|
615
711
|
function countUp(el, a, b, d, fmt, ticks = false) {
|
|
@@ -629,15 +725,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
629
725
|
};
|
|
630
726
|
st(t0);
|
|
631
727
|
}
|
|
632
|
-
const ctr = (
|
|
633
|
-
const t = tiles[i];
|
|
728
|
+
const ctr = (t) => {
|
|
634
729
|
return { x: t.offsetLeft + t.offsetWidth / 2, y: t.offsetTop + t.offsetHeight / 2 };
|
|
635
730
|
};
|
|
636
731
|
function drawLines(fresh = []) {
|
|
637
732
|
svg.setAttribute("viewBox", `0 0 ${gw.clientWidth} ${gw.clientHeight}`);
|
|
638
733
|
svg.innerHTML = cur.map((k) => {
|
|
639
|
-
const a = ctr(
|
|
640
|
-
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;
|
|
641
736
|
const at = `x1="${a.x - ex}" y1="${a.y - ey}" x2="${b.x + ex}" y2="${b.y + ey}" pathLength="1"`;
|
|
642
737
|
return `<g${fresh.includes(k) ? ' class="pkb-draw"' : ""}><line class="pkb-u" ${at}/><line class="pkb-o" ${at}/></g>`;
|
|
643
738
|
}).join("");
|
|
@@ -684,29 +779,29 @@ function createPackingBingo(container, options = {}) {
|
|
|
684
779
|
later(() => sfx.reach(), 150);
|
|
685
780
|
}
|
|
686
781
|
function bingo(fresh, pts) {
|
|
687
|
-
const
|
|
688
|
-
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]);
|
|
689
784
|
const step = Math.min(70, 300 / seq.length);
|
|
690
785
|
seq.forEach(
|
|
691
|
-
(
|
|
692
|
-
hop(
|
|
693
|
-
const p = rel(
|
|
786
|
+
(t, j) => later(() => {
|
|
787
|
+
hop(t, 0);
|
|
788
|
+
const p = rel(t);
|
|
694
789
|
burst(p.x, p.y, 14, { speed: 5, g: 0.15, life: 30, size: 0.7, star: 0.8 });
|
|
695
790
|
sfx.note(j + 3);
|
|
696
791
|
}, j * step)
|
|
697
792
|
);
|
|
698
793
|
later(() => {
|
|
699
|
-
$("bT").textContent =
|
|
700
|
-
$("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);
|
|
701
796
|
replay($("banner"), "pkb-show");
|
|
702
797
|
if (!calm) {
|
|
703
798
|
replay($("flash"), "pkb-go");
|
|
704
799
|
replay(board, "pkb-punch");
|
|
705
800
|
}
|
|
706
801
|
const c = rel(gw);
|
|
707
|
-
burst(c.x, c.y,
|
|
708
|
-
sfx.bingo(
|
|
709
|
-
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]);
|
|
710
805
|
if (pts) later(() => showGain(pts, c.x, c.y + 105, "big"), 350);
|
|
711
806
|
}, seq.length * step + 60);
|
|
712
807
|
}
|
|
@@ -714,9 +809,10 @@ function createPackingBingo(container, options = {}) {
|
|
|
714
809
|
const bonus = gotP ? 0 : 1e3;
|
|
715
810
|
gotP = true;
|
|
716
811
|
score += bonus;
|
|
717
|
-
|
|
812
|
+
cellTiles.forEach((t, c) => hop(t, far(c) * 180 / Math.max(1, n - 1)));
|
|
718
813
|
sfx.perfect();
|
|
719
814
|
if (!calm) replay($("flash"), "pkb-go");
|
|
815
|
+
$("pbody").textContent = perfectText(M, N);
|
|
720
816
|
$("sScore").textContent = "0";
|
|
721
817
|
$("sCombo").textContent = "\xD70";
|
|
722
818
|
$("sBingo").textContent = "0";
|
|
@@ -766,27 +862,28 @@ function createPackingBingo(container, options = {}) {
|
|
|
766
862
|
function render(src, quiet) {
|
|
767
863
|
const events = [];
|
|
768
864
|
const on = checked, c = on.filter(Boolean).length;
|
|
769
|
-
cur =
|
|
865
|
+
cur = lines.flatMap((l, k) => l.every((i) => on[i]) ? [k] : []);
|
|
770
866
|
const fresh = cur.filter((k) => !prevLines.has(k));
|
|
771
867
|
if ([...prevLines].some((k) => !cur.includes(k))) {
|
|
772
868
|
cancelFx();
|
|
773
|
-
if (c <
|
|
869
|
+
if (c < N) perfectEl.classList.remove("pkb-show");
|
|
774
870
|
}
|
|
775
|
-
const hit = new Set(cur.flatMap((k) =>
|
|
871
|
+
const hit = new Set(cur.flatMap((k) => cellLines[k]));
|
|
776
872
|
const reach = /* @__PURE__ */ new Set();
|
|
777
|
-
for (const l of
|
|
873
|
+
for (const l of lines) {
|
|
778
874
|
const m = l.filter((i) => !on[i]);
|
|
779
875
|
if (m.length === 1) reach.add(m[0]);
|
|
780
876
|
}
|
|
781
877
|
tiles.forEach((t, i) => {
|
|
782
878
|
t.classList.toggle("pkb-on", on[i]);
|
|
783
|
-
t.classList.toggle("pkb-line", hit.has(i));
|
|
879
|
+
t.classList.toggle("pkb-line", hit.has(L.itemCell[i]));
|
|
784
880
|
t.classList.toggle("pkb-reach", reach.has(i));
|
|
785
881
|
});
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
kick.
|
|
789
|
-
|
|
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}`;
|
|
790
887
|
$("bng").textContent = String(cur.length);
|
|
791
888
|
const bp = $("bp");
|
|
792
889
|
if (cur.length > lastN) {
|
|
@@ -803,7 +900,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
803
900
|
score += linePts;
|
|
804
901
|
if (quiet) {
|
|
805
902
|
if (linePts) countScore();
|
|
806
|
-
} else if (c ===
|
|
903
|
+
} else if (c === N && !prevAll) perfect(events);
|
|
807
904
|
else if (fresh.length) {
|
|
808
905
|
bingo(fresh, linePts);
|
|
809
906
|
const e = { newLines: fresh, totalLines: cur.length, score };
|
|
@@ -814,9 +911,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
814
911
|
}
|
|
815
912
|
prevLines = new Set(cur);
|
|
816
913
|
prevReach = reach;
|
|
817
|
-
prevAll = c ===
|
|
914
|
+
prevAll = c === N;
|
|
818
915
|
const labels = [...reach].map((i) => items[i].label);
|
|
819
|
-
$("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);
|
|
820
917
|
return events;
|
|
821
918
|
}
|
|
822
919
|
const flush = (events) => {
|
|
@@ -843,7 +940,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
843
940
|
got.clear();
|
|
844
941
|
gotL.clear();
|
|
845
942
|
checked.forEach((v, i) => v && got.add(i));
|
|
846
|
-
|
|
943
|
+
lines.forEach((l, k) => l.every((i) => checked[i]) && gotL.add(k));
|
|
847
944
|
prevLines = new Set(gotL);
|
|
848
945
|
prevReach = /* @__PURE__ */ new Set();
|
|
849
946
|
prevAll = checked.every(Boolean);
|
|
@@ -903,12 +1000,13 @@ function createPackingBingo(container, options = {}) {
|
|
|
903
1000
|
}
|
|
904
1001
|
};
|
|
905
1002
|
$("dB").addEventListener("click", () => {
|
|
906
|
-
const open = () =>
|
|
907
|
-
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();
|
|
908
1005
|
void run(pick.map((i) => ({ i, delay: 320 })));
|
|
909
1006
|
});
|
|
910
1007
|
$("dP").addEventListener("click", () => {
|
|
911
|
-
const
|
|
1008
|
+
const all = items.map((_, i) => i);
|
|
1009
|
+
const miss = checked.every(Boolean) ? all : all.filter((i) => !checked[i]);
|
|
912
1010
|
void run(miss.map((i, k) => k < miss.length - 1 ? { i, delay: 170, quiet: true } : { i, delay: 450 }));
|
|
913
1011
|
});
|
|
914
1012
|
$("dR").addEventListener("click", () => {
|
|
@@ -936,14 +1034,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
936
1034
|
return {
|
|
937
1035
|
setChecked(next, opts = {}) {
|
|
938
1036
|
if (dead) return;
|
|
939
|
-
const
|
|
940
|
-
const changed =
|
|
1037
|
+
const n2 = normalize(next);
|
|
1038
|
+
const changed = n2.flatMap((v, i) => v !== checked[i] ? [i] : []);
|
|
941
1039
|
if (!changed.length) return;
|
|
942
1040
|
if (!opts.animate || changed.length !== 1) {
|
|
943
|
-
baseline(
|
|
1041
|
+
baseline(n2);
|
|
944
1042
|
return;
|
|
945
1043
|
}
|
|
946
|
-
flush(apply(
|
|
1044
|
+
flush(apply(n2, changed[0], false));
|
|
947
1045
|
},
|
|
948
1046
|
reset() {
|
|
949
1047
|
if (!dead) request(normalize(void 0), null, { reset: true });
|
|
@@ -952,7 +1050,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
952
1050
|
if (!dead) render(null, true);
|
|
953
1051
|
},
|
|
954
1052
|
getState() {
|
|
955
|
-
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 };
|
|
956
1054
|
},
|
|
957
1055
|
destroy() {
|
|
958
1056
|
if (dead) return;
|
|
@@ -970,8 +1068,10 @@ function createPackingBingo(container, options = {}) {
|
|
|
970
1068
|
|
|
971
1069
|
export {
|
|
972
1070
|
DEFAULT_ITEMS,
|
|
1071
|
+
bingoLines,
|
|
973
1072
|
ja,
|
|
974
1073
|
en,
|
|
1074
|
+
resolveStorageKey,
|
|
975
1075
|
LINES,
|
|
976
1076
|
createPackingBingo
|
|
977
1077
|
};
|