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
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",
|
|
@@ -229,7 +303,7 @@ function resolveStorageKey(options) {
|
|
|
229
303
|
if (options.controlled) return null;
|
|
230
304
|
if (options.storageKey !== void 0) return options.storageKey || null;
|
|
231
305
|
if (options.demo) return null;
|
|
232
|
-
return itemsKey(options
|
|
306
|
+
return itemsKey(layoutItems(options));
|
|
233
307
|
}
|
|
234
308
|
function itemsKey(items) {
|
|
235
309
|
const text = items.map((it) => `${it.emoji}\0${it.label}`).join("");
|
|
@@ -244,14 +318,14 @@ function itemsKey(items) {
|
|
|
244
318
|
// src/styles.ts
|
|
245
319
|
var FONT_URL = "https://fonts.googleapis.com/css2?family=Dela+Gothic+One&family=M+PLUS+Rounded+1c:wght@500;800&display=swap";
|
|
246
320
|
var CSS = `
|
|
247
|
-
.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%}
|
|
248
322
|
.pkb,.pkb *{box-sizing:border-box;margin:0;padding:0}
|
|
249
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}
|
|
250
324
|
.pkb-board.pkb-shake{animation:pkb-shake .45s cubic-bezier(.36,.07,.19,.97)}
|
|
251
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)}}
|
|
252
326
|
.pkb-board.pkb-punch{animation:pkb-punch .4s cubic-bezier(.3,1.6,.5,1)}
|
|
253
327
|
@keyframes pkb-punch{30%{transform:scale(1.018)}}
|
|
254
|
-
.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}
|
|
255
329
|
.pkb-tags{display:flex;gap:8px;align-items:center}
|
|
256
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}
|
|
257
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}
|
|
@@ -259,7 +333,7 @@ var CSS = `
|
|
|
259
333
|
.pkb button{font-family:var(--pk-body)}
|
|
260
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}
|
|
261
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}
|
|
262
|
-
.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}
|
|
263
337
|
.pkb-side{display:flex;flex-direction:column;gap:8px;flex:none}
|
|
264
338
|
.pkb-score{display:flex;gap:10px}
|
|
265
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)}
|
|
@@ -278,16 +352,18 @@ var CSS = `
|
|
|
278
352
|
.pkb-combo.pkb-on .pkb-bar i{animation:pkb-drain 2.5s linear forwards}
|
|
279
353
|
@keyframes pkb-drain{from{transform:scaleX(1)}to{transform:scaleX(0)}}
|
|
280
354
|
.pkb-gw{position:relative}
|
|
281
|
-
.pkb-grid{display:grid;grid-template-columns:repeat(
|
|
282
|
-
.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}
|
|
283
|
-
.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)}
|
|
284
358
|
.pkb-tile input{position:absolute;opacity:0;pointer-events:none;width:1px;height:1px}
|
|
285
359
|
.pkb-tile:has(input:focus-visible){outline:3px solid var(--pk-ink);outline-offset:3px}
|
|
286
|
-
.pkb-emo{font-size:32px;line-height:1;transition:opacity .2s}
|
|
287
|
-
.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}
|
|
288
|
-
.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}
|
|
289
363
|
.pkb-tile.pkb-on .pkb-stamp{opacity:.9;transform:translate(-50%,-50%) rotate(-16deg) scale(1)}
|
|
290
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)}
|
|
291
367
|
.pkb-tile.pkb-line{background-color:var(--pk-mint)}
|
|
292
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}
|
|
293
369
|
@keyframes pkb-glint{0%{background-position:200% 0}30%,100%{background-position:-100% 0}}
|
|
@@ -304,11 +380,11 @@ var CSS = `
|
|
|
304
380
|
.pkb-lines{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible;z-index:2;transition:opacity .4s}
|
|
305
381
|
.pkb-board.pkb-all .pkb-lines{opacity:0}
|
|
306
382
|
.pkb-lines line{fill:none;stroke-linecap:round;stroke-dasharray:1;stroke-dashoffset:0}
|
|
307
|
-
.pkb-lines .pkb-u{stroke:var(--pk-paper);stroke-width:
|
|
308
|
-
.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))}
|
|
309
385
|
.pkb-lines .pkb-draw line{animation:pkb-draw .45s ease-out .3s both}
|
|
310
386
|
@keyframes pkb-draw{from{stroke-dashoffset:1}}
|
|
311
|
-
.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}
|
|
312
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}
|
|
313
389
|
.pkb-flash{position:absolute;inset:0;z-index:7;background:#fff;opacity:0;pointer-events:none}
|
|
314
390
|
.pkb-flash.pkb-go{animation:pkb-flash .4s ease-out}
|
|
@@ -323,7 +399,7 @@ var CSS = `
|
|
|
323
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}
|
|
324
400
|
.pkb-float.pkb-pt{font-size:22px;color:var(--pk-paper)}
|
|
325
401
|
.pkb-float.pkb-cmb{font-size:17px}
|
|
326
|
-
.pkb-float.pkb-big{font-size:clamp(
|
|
402
|
+
.pkb-float.pkb-big{font-size:clamp(14px,5.6cqi,34px);color:#FFE066}
|
|
327
403
|
.pkb-float.pkb-rch{font-size:20px;color:var(--pk-red);-webkit-text-stroke:4px var(--pk-paper)}
|
|
328
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)}}
|
|
329
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}
|
|
@@ -334,7 +410,7 @@ var CSS = `
|
|
|
334
410
|
.pkb-perfect.pkb-show .pkb-medal{animation:pkb-slam .38s cubic-bezier(.5,0,.75,0) .35s forwards}
|
|
335
411
|
@keyframes pkb-slam{to{opacity:1;transform:rotate(-14deg) scale(1)}}
|
|
336
412
|
.pkb-ptitle{font-family:var(--pk-display);font-weight:400;font-size:30px;line-height:1.3;color:inherit}
|
|
337
|
-
.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}
|
|
338
414
|
.pkb-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:16px}
|
|
339
415
|
.pkb-stats>div{border:2px solid var(--pk-ink);border-radius:12px;padding:6px 4px 5px;background:#fff}
|
|
340
416
|
.pkb-stats small{display:block;font-size:9.5px;font-weight:800;letter-spacing:.1em}
|
|
@@ -344,32 +420,20 @@ var CSS = `
|
|
|
344
420
|
.pkb-fx{position:absolute;inset:0;width:100%;height:100%;z-index:10;pointer-events:none}
|
|
345
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}
|
|
346
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}
|
|
347
|
-
@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}}
|
|
348
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}}
|
|
349
427
|
@keyframes pkb-fade{0%,100%{opacity:0}15%,80%{opacity:1}}
|
|
350
428
|
`;
|
|
351
429
|
|
|
352
430
|
// src/core.ts
|
|
353
|
-
var LINES =
|
|
354
|
-
[0, 1, 2, 3],
|
|
355
|
-
[4, 5, 6, 7],
|
|
356
|
-
[8, 9, 10, 11],
|
|
357
|
-
[12, 13, 14, 15],
|
|
358
|
-
[0, 4, 8, 12],
|
|
359
|
-
[1, 5, 9, 13],
|
|
360
|
-
[2, 6, 10, 14],
|
|
361
|
-
[3, 7, 11, 15],
|
|
362
|
-
[0, 5, 10, 15],
|
|
363
|
-
[3, 6, 9, 12]
|
|
364
|
-
];
|
|
365
|
-
var DEMO_START = [0, 1, 2, 3, 5, 10];
|
|
431
|
+
var LINES = bingoLines(4);
|
|
366
432
|
var CONFETTI = ["#E8473B", "#2F6FED", "#1FA971", "#FFFDF6", "#2A1F14", "#FF9F1C"];
|
|
367
433
|
var COMBO_COLORS = ["#FF9F1C", "#1FA971", "#2F6FED", "#9B5DE5", "#E8473B"];
|
|
368
434
|
var COMBO_MS = 2500;
|
|
369
435
|
var MUTE_KEY = "packing-bingo-muted";
|
|
370
436
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
371
|
-
var normalize = (v) => Array.from({ length: 16 }, (_, i) => !!v?.[i]);
|
|
372
|
-
var demoStart = () => Array.from({ length: 16 }, (_, i) => DEMO_START.includes(i));
|
|
373
437
|
function readStore(key) {
|
|
374
438
|
try {
|
|
375
439
|
return localStorage.getItem(key);
|
|
@@ -398,15 +462,21 @@ function ensureAssets(fonts) {
|
|
|
398
462
|
document.head.appendChild(l);
|
|
399
463
|
}
|
|
400
464
|
}
|
|
401
|
-
function template(
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
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)}`;
|
|
405
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>` : "";
|
|
406
|
-
return `<div class="pkb"><div class="pkb-board">
|
|
476
|
+
return `<div class="pkb" style="${scale}"><div class="pkb-board">
|
|
407
477
|
<div class="pkb-head"><div>
|
|
408
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>
|
|
409
|
-
<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>
|
|
410
480
|
<div class="pkb-side"><div class="pkb-score">
|
|
411
481
|
<div class="pkb-pill"><small>${esc(M.packed)}</small><b data-r="cnt"></b></div>
|
|
412
482
|
<div class="pkb-pill" data-r="bp"><small>${esc(M.bingo)}</small><b data-r="bng"></b></div>
|
|
@@ -417,14 +487,22 @@ function template(items, M, demo) {
|
|
|
417
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>
|
|
418
488
|
<div class="pkb-flash" data-r="flash"></div>
|
|
419
489
|
<div class="pkb-perfect" data-r="perfect"><div class="pkb-card" role="dialog" aria-label="${esc(M.perfectTitle)}">
|
|
420
|
-
<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>
|
|
421
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>
|
|
422
492
|
<button type="button" class="pkb-close" data-r="close">${esc(M.close)}</button></div></div>
|
|
423
493
|
<canvas class="pkb-fx"></canvas></div>${demoBar}</div>`;
|
|
424
494
|
}
|
|
495
|
+
var perfectText = (M, total) => typeof M.perfectBody === "function" ? M.perfectBody(total) : M.perfectBody;
|
|
425
496
|
function createPackingBingo(container, options = {}) {
|
|
426
|
-
const
|
|
427
|
-
|
|
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);
|
|
428
506
|
const M = { ...ja, ...options.messages };
|
|
429
507
|
if (options.title != null) M.title = options.title;
|
|
430
508
|
if (options.tag != null) M.tag = options.tag;
|
|
@@ -445,7 +523,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
445
523
|
const wait = (ms) => new Promise((r) => window.setTimeout(r, ms));
|
|
446
524
|
let gen = 0;
|
|
447
525
|
ensureAssets(options.fonts !== false);
|
|
448
|
-
container.innerHTML = template(
|
|
526
|
+
container.innerHTML = template(L, M, demo);
|
|
449
527
|
const $ = (r) => {
|
|
450
528
|
const el = container.querySelector(`[data-r="${r}"]`);
|
|
451
529
|
if (!el) throw new Error(`[packing-bingo] internal: missing element "${r}"`);
|
|
@@ -457,7 +535,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
457
535
|
const svg = container.querySelector(".pkb-lines");
|
|
458
536
|
const cv = container.querySelector(".pkb-fx");
|
|
459
537
|
const ctx = cv.getContext("2d");
|
|
460
|
-
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;
|
|
461
541
|
const ins = tiles.map((t) => t.querySelector("input"));
|
|
462
542
|
const kick = $("kick"), snd = $("snd"), perfectEl = $("perfect");
|
|
463
543
|
let checked = normalize(void 0);
|
|
@@ -492,14 +572,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
492
572
|
cv.width = Math.round(BW * dpr);
|
|
493
573
|
cv.height = Math.round(BH * dpr);
|
|
494
574
|
};
|
|
495
|
-
const piece = (x, y, vx, vy, g, s, life,
|
|
575
|
+
const piece = (x, y, vx, vy, g, s, life, shape2) => ({
|
|
496
576
|
x,
|
|
497
577
|
y,
|
|
498
578
|
vx,
|
|
499
579
|
vy,
|
|
500
580
|
g,
|
|
501
581
|
life,
|
|
502
|
-
shape,
|
|
582
|
+
shape: shape2,
|
|
503
583
|
w: (5 + Math.random() * 6) * s,
|
|
504
584
|
h: (3 + Math.random() * 5) * s,
|
|
505
585
|
r: Math.random() * 6,
|
|
@@ -509,9 +589,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
509
589
|
const go = () => {
|
|
510
590
|
if (!raf && ctx) raf = requestAnimationFrame(tick);
|
|
511
591
|
};
|
|
512
|
-
function burst(x, y,
|
|
592
|
+
function burst(x, y, n2, o = {}) {
|
|
513
593
|
if (calm) return;
|
|
514
|
-
for (let i = 0; i <
|
|
594
|
+
for (let i = 0; i < n2; i++) {
|
|
515
595
|
const a = (o.angle ?? -Math.PI / 2) + (Math.random() - 0.5) * (o.spread ?? Math.PI * 2);
|
|
516
596
|
const v = (o.speed ?? 10) * (0.35 + Math.random() * 0.85);
|
|
517
597
|
const sh = Math.random() < (o.star ?? 0.15) ? "star" : Math.random() < 0.3 ? "round" : "rect";
|
|
@@ -602,11 +682,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
602
682
|
void el.offsetWidth;
|
|
603
683
|
el.classList.add(cls);
|
|
604
684
|
};
|
|
605
|
-
const hop = (
|
|
685
|
+
const hop = (t, d) => {
|
|
606
686
|
if (calm) return;
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
replay(
|
|
687
|
+
t.classList.remove("pkb-squash");
|
|
688
|
+
t.style.setProperty("--pkb-d", d + "ms");
|
|
689
|
+
replay(t, "pkb-hop");
|
|
610
690
|
};
|
|
611
691
|
grid.addEventListener("animationend", (e) => {
|
|
612
692
|
const t = e.target;
|
|
@@ -654,8 +734,8 @@ function createPackingBingo(container, options = {}) {
|
|
|
654
734
|
st(t0);
|
|
655
735
|
if (!calm) replay($("sp"), "pkb-bump");
|
|
656
736
|
}
|
|
657
|
-
const showGain = (
|
|
658
|
-
floatText(`+${
|
|
737
|
+
const showGain = (pts, x, y, cls = "pt") => {
|
|
738
|
+
floatText(`+${pts}`, x, y, cls);
|
|
659
739
|
countScore();
|
|
660
740
|
};
|
|
661
741
|
function countUp(el, a, b, d, fmt, ticks = false) {
|
|
@@ -675,15 +755,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
675
755
|
};
|
|
676
756
|
st(t0);
|
|
677
757
|
}
|
|
678
|
-
const ctr = (
|
|
679
|
-
const t = tiles[i];
|
|
758
|
+
const ctr = (t) => {
|
|
680
759
|
return { x: t.offsetLeft + t.offsetWidth / 2, y: t.offsetTop + t.offsetHeight / 2 };
|
|
681
760
|
};
|
|
682
761
|
function drawLines(fresh = []) {
|
|
683
762
|
svg.setAttribute("viewBox", `0 0 ${gw.clientWidth} ${gw.clientHeight}`);
|
|
684
763
|
svg.innerHTML = cur.map((k) => {
|
|
685
|
-
const a = ctr(
|
|
686
|
-
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;
|
|
687
766
|
const at = `x1="${a.x - ex}" y1="${a.y - ey}" x2="${b.x + ex}" y2="${b.y + ey}" pathLength="1"`;
|
|
688
767
|
return `<g${fresh.includes(k) ? ' class="pkb-draw"' : ""}><line class="pkb-u" ${at}/><line class="pkb-o" ${at}/></g>`;
|
|
689
768
|
}).join("");
|
|
@@ -730,29 +809,29 @@ function createPackingBingo(container, options = {}) {
|
|
|
730
809
|
later(() => sfx.reach(), 150);
|
|
731
810
|
}
|
|
732
811
|
function bingo(fresh, pts) {
|
|
733
|
-
const
|
|
734
|
-
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]);
|
|
735
814
|
const step = Math.min(70, 300 / seq.length);
|
|
736
815
|
seq.forEach(
|
|
737
|
-
(
|
|
738
|
-
hop(
|
|
739
|
-
const p = rel(
|
|
816
|
+
(t, j) => later(() => {
|
|
817
|
+
hop(t, 0);
|
|
818
|
+
const p = rel(t);
|
|
740
819
|
burst(p.x, p.y, 14, { speed: 5, g: 0.15, life: 30, size: 0.7, star: 0.8 });
|
|
741
820
|
sfx.note(j + 3);
|
|
742
821
|
}, j * step)
|
|
743
822
|
);
|
|
744
823
|
later(() => {
|
|
745
|
-
$("bT").textContent =
|
|
746
|
-
$("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);
|
|
747
826
|
replay($("banner"), "pkb-show");
|
|
748
827
|
if (!calm) {
|
|
749
828
|
replay($("flash"), "pkb-go");
|
|
750
829
|
replay(board, "pkb-punch");
|
|
751
830
|
}
|
|
752
831
|
const c = rel(gw);
|
|
753
|
-
burst(c.x, c.y,
|
|
754
|
-
sfx.bingo(
|
|
755
|
-
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]);
|
|
756
835
|
if (pts) later(() => showGain(pts, c.x, c.y + 105, "big"), 350);
|
|
757
836
|
}, seq.length * step + 60);
|
|
758
837
|
}
|
|
@@ -760,9 +839,10 @@ function createPackingBingo(container, options = {}) {
|
|
|
760
839
|
const bonus = gotP ? 0 : 1e3;
|
|
761
840
|
gotP = true;
|
|
762
841
|
score += bonus;
|
|
763
|
-
|
|
842
|
+
cellTiles.forEach((t, c) => hop(t, far(c) * 180 / Math.max(1, n - 1)));
|
|
764
843
|
sfx.perfect();
|
|
765
844
|
if (!calm) replay($("flash"), "pkb-go");
|
|
845
|
+
$("pbody").textContent = perfectText(M, N);
|
|
766
846
|
$("sScore").textContent = "0";
|
|
767
847
|
$("sCombo").textContent = "\xD70";
|
|
768
848
|
$("sBingo").textContent = "0";
|
|
@@ -812,27 +892,28 @@ function createPackingBingo(container, options = {}) {
|
|
|
812
892
|
function render(src, quiet) {
|
|
813
893
|
const events = [];
|
|
814
894
|
const on = checked, c = on.filter(Boolean).length;
|
|
815
|
-
cur =
|
|
895
|
+
cur = lines.flatMap((l, k) => l.every((i) => on[i]) ? [k] : []);
|
|
816
896
|
const fresh = cur.filter((k) => !prevLines.has(k));
|
|
817
897
|
if ([...prevLines].some((k) => !cur.includes(k))) {
|
|
818
898
|
cancelFx();
|
|
819
|
-
if (c <
|
|
899
|
+
if (c < N) perfectEl.classList.remove("pkb-show");
|
|
820
900
|
}
|
|
821
|
-
const hit = new Set(cur.flatMap((k) =>
|
|
901
|
+
const hit = new Set(cur.flatMap((k) => cellLines[k]));
|
|
822
902
|
const reach = /* @__PURE__ */ new Set();
|
|
823
|
-
for (const l of
|
|
903
|
+
for (const l of lines) {
|
|
824
904
|
const m = l.filter((i) => !on[i]);
|
|
825
905
|
if (m.length === 1) reach.add(m[0]);
|
|
826
906
|
}
|
|
827
907
|
tiles.forEach((t, i) => {
|
|
828
908
|
t.classList.toggle("pkb-on", on[i]);
|
|
829
|
-
t.classList.toggle("pkb-line", hit.has(i));
|
|
909
|
+
t.classList.toggle("pkb-line", hit.has(L.itemCell[i]));
|
|
830
910
|
t.classList.toggle("pkb-reach", reach.has(i));
|
|
831
911
|
});
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
kick.
|
|
835
|
-
|
|
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}`;
|
|
836
917
|
$("bng").textContent = String(cur.length);
|
|
837
918
|
const bp = $("bp");
|
|
838
919
|
if (cur.length > lastN) {
|
|
@@ -849,7 +930,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
849
930
|
score += linePts;
|
|
850
931
|
if (quiet) {
|
|
851
932
|
if (linePts) countScore();
|
|
852
|
-
} else if (c ===
|
|
933
|
+
} else if (c === N && !prevAll) perfect(events);
|
|
853
934
|
else if (fresh.length) {
|
|
854
935
|
bingo(fresh, linePts);
|
|
855
936
|
const e = { newLines: fresh, totalLines: cur.length, score };
|
|
@@ -860,9 +941,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
860
941
|
}
|
|
861
942
|
prevLines = new Set(cur);
|
|
862
943
|
prevReach = reach;
|
|
863
|
-
prevAll = c ===
|
|
944
|
+
prevAll = c === N;
|
|
864
945
|
const labels = [...reach].map((i) => items[i].label);
|
|
865
|
-
$("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);
|
|
866
947
|
return events;
|
|
867
948
|
}
|
|
868
949
|
const flush = (events) => {
|
|
@@ -889,7 +970,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
889
970
|
got.clear();
|
|
890
971
|
gotL.clear();
|
|
891
972
|
checked.forEach((v, i) => v && got.add(i));
|
|
892
|
-
|
|
973
|
+
lines.forEach((l, k) => l.every((i) => checked[i]) && gotL.add(k));
|
|
893
974
|
prevLines = new Set(gotL);
|
|
894
975
|
prevReach = /* @__PURE__ */ new Set();
|
|
895
976
|
prevAll = checked.every(Boolean);
|
|
@@ -949,12 +1030,13 @@ function createPackingBingo(container, options = {}) {
|
|
|
949
1030
|
}
|
|
950
1031
|
};
|
|
951
1032
|
$("dB").addEventListener("click", () => {
|
|
952
|
-
const open = () =>
|
|
953
|
-
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();
|
|
954
1035
|
void run(pick.map((i) => ({ i, delay: 320 })));
|
|
955
1036
|
});
|
|
956
1037
|
$("dP").addEventListener("click", () => {
|
|
957
|
-
const
|
|
1038
|
+
const all = items.map((_, i) => i);
|
|
1039
|
+
const miss = checked.every(Boolean) ? all : all.filter((i) => !checked[i]);
|
|
958
1040
|
void run(miss.map((i, k) => k < miss.length - 1 ? { i, delay: 170, quiet: true } : { i, delay: 450 }));
|
|
959
1041
|
});
|
|
960
1042
|
$("dR").addEventListener("click", () => {
|
|
@@ -982,14 +1064,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
982
1064
|
return {
|
|
983
1065
|
setChecked(next, opts = {}) {
|
|
984
1066
|
if (dead) return;
|
|
985
|
-
const
|
|
986
|
-
const changed =
|
|
1067
|
+
const n2 = normalize(next);
|
|
1068
|
+
const changed = n2.flatMap((v, i) => v !== checked[i] ? [i] : []);
|
|
987
1069
|
if (!changed.length) return;
|
|
988
1070
|
if (!opts.animate || changed.length !== 1) {
|
|
989
|
-
baseline(
|
|
1071
|
+
baseline(n2);
|
|
990
1072
|
return;
|
|
991
1073
|
}
|
|
992
|
-
flush(apply(
|
|
1074
|
+
flush(apply(n2, changed[0], false));
|
|
993
1075
|
},
|
|
994
1076
|
reset() {
|
|
995
1077
|
if (!dead) request(normalize(void 0), null, { reset: true });
|
|
@@ -998,7 +1080,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
998
1080
|
if (!dead) render(null, true);
|
|
999
1081
|
},
|
|
1000
1082
|
getState() {
|
|
1001
|
-
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 };
|
|
1002
1084
|
},
|
|
1003
1085
|
destroy() {
|
|
1004
1086
|
if (dead) return;
|
|
@@ -1017,6 +1099,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1017
1099
|
0 && (module.exports = {
|
|
1018
1100
|
DEFAULT_ITEMS,
|
|
1019
1101
|
LINES,
|
|
1102
|
+
bingoLines,
|
|
1020
1103
|
createPackingBingo,
|
|
1021
1104
|
en,
|
|
1022
1105
|
ja
|