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/index.cjs
CHANGED
|
@@ -24,6 +24,7 @@ __export(src_exports, {
|
|
|
24
24
|
DEFAULT_ITEMS: () => DEFAULT_ITEMS,
|
|
25
25
|
LINES: () => LINES,
|
|
26
26
|
PackingBingo: () => PackingBingo,
|
|
27
|
+
bingoLines: () => bingoLines,
|
|
27
28
|
createPackingBingo: () => createPackingBingo,
|
|
28
29
|
en: () => en,
|
|
29
30
|
ja: () => ja
|
|
@@ -35,29 +36,98 @@ var import_react = require("react");
|
|
|
35
36
|
|
|
36
37
|
// src/items.ts
|
|
37
38
|
var DEFAULT_ITEMS = [
|
|
38
|
-
{ emoji: "\u{
|
|
39
|
-
{ emoji: "\u{
|
|
40
|
-
{ emoji: "\u{
|
|
41
|
-
{ emoji: "\u{
|
|
42
|
-
{ emoji: "\u{
|
|
43
|
-
{ emoji: "\u{
|
|
44
|
-
{ emoji: "\u{1F9E5}", label: "\u4E0A\u7740" },
|
|
45
|
-
{ emoji: "\u{1FAA5}", label: "\u6D17\u9762\u7528\u5177" },
|
|
46
|
-
{ emoji: "\u{1F6C1}", label: "\u30BF\u30AA\u30EB" },
|
|
47
|
-
{ emoji: "\u{1F927}", label: "\u30CF\u30F3\u30AB\u30C1\u30FB\u30C6\u30A3\u30C3\u30B7\u30E5" },
|
|
39
|
+
{ emoji: "\u{1F45B}", label: "\u8CA1\u5E03" },
|
|
40
|
+
{ emoji: "\u{1F4F1}", label: "\u30B9\u30DE\u30DB" },
|
|
41
|
+
{ emoji: "\u{1F50B}", label: "\u30E2\u30D0\u30A4\u30EB\u200B\u5145\u96FB\u5668" },
|
|
42
|
+
{ emoji: "\u{1F4CB}", label: "\u5065\u5EB7\u8ABF\u67FB\u7968" },
|
|
43
|
+
{ emoji: "\u{1FAAA}", label: "\u30DE\u30A4\u30CA\u30AB\u30FC\u30C9\u200B\u307E\u305F\u306F\u4FDD\u967A\u8A3C" },
|
|
44
|
+
{ emoji: "\u{1F4D6}", label: "\u3057\u304A\u308A" },
|
|
48
45
|
{ emoji: "\u{1F637}", label: "\u30DE\u30B9\u30AF" },
|
|
49
|
-
{ emoji: "\u{
|
|
50
|
-
{ emoji: "\
|
|
51
|
-
{ emoji: "\u270F\uFE0F", label: "\u7B46\u8A18\u7528\u5177" },
|
|
46
|
+
{ emoji: "\u{1F927}", label: "\u30CF\u30F3\u30AB\u30C1" },
|
|
47
|
+
{ emoji: "\u{1F48A}", label: "\u5E38\u5099\u85AC" },
|
|
52
48
|
{ emoji: "\u{1F6CD}\uFE0F", label: "\u30D3\u30CB\u30FC\u30EB\u888B" },
|
|
53
|
-
{ emoji: "\u{
|
|
49
|
+
{ emoji: "\u{1F9E4}", label: "\u8ECD\u624B\uFF08\u5B66\u6821\u652F\u7D66\uFF09" },
|
|
50
|
+
{ emoji: "\u{1F526}", label: "\u30D8\u30C3\u30C9\u30E9\u30A4\u30C8" },
|
|
51
|
+
{ emoji: "\u{1F455}", label: "\u30B8\u30E3\u30FC\u30B8" },
|
|
52
|
+
{ emoji: "\u2614", label: "\u96E8\u5177" },
|
|
53
|
+
{ emoji: "\u{1F6C1}", label: "\u30BF\u30AA\u30EB" },
|
|
54
|
+
{ emoji: "\u{1F9F4}", label: "\u65E5\u713C\u3051\u6B62\u3081" }
|
|
54
55
|
];
|
|
55
56
|
|
|
57
|
+
// src/layout.ts
|
|
58
|
+
var MIN_SIZE = 2;
|
|
59
|
+
var MAX_SIZE = 10;
|
|
60
|
+
var fail = (msg) => {
|
|
61
|
+
throw new Error(`[packing-bingo] ${msg}`);
|
|
62
|
+
};
|
|
63
|
+
function layoutItems(options) {
|
|
64
|
+
if (options.items) return options.items;
|
|
65
|
+
const n = options.size;
|
|
66
|
+
if (n == null || !Number.isInteger(n) || n < MIN_SIZE) return DEFAULT_ITEMS;
|
|
67
|
+
return DEFAULT_ITEMS.slice(0, n * n - (options.free ? 1 : 0));
|
|
68
|
+
}
|
|
69
|
+
function checkSize(size, free) {
|
|
70
|
+
if (!Number.isInteger(size) || size < MIN_SIZE || size > MAX_SIZE)
|
|
71
|
+
fail(`"size" must be a whole number from ${MIN_SIZE} to ${MAX_SIZE} (got ${size}).`);
|
|
72
|
+
if (free && size % 2 === 0) fail(`"free" needs an odd size such as 3 or 5, so that the card has a center (got ${size}).`);
|
|
73
|
+
}
|
|
74
|
+
function shape(n, free) {
|
|
75
|
+
const seq = (f) => Array.from({ length: n }, (_, k) => f(k));
|
|
76
|
+
const cellLines = [
|
|
77
|
+
...seq((r) => seq((c) => r * n + c)),
|
|
78
|
+
// rows
|
|
79
|
+
...seq((c) => seq((r) => r * n + c)),
|
|
80
|
+
// columns
|
|
81
|
+
seq((k) => k * (n + 1)),
|
|
82
|
+
// top left to bottom right
|
|
83
|
+
seq((k) => (k + 1) * (n - 1))
|
|
84
|
+
// top right to bottom left
|
|
85
|
+
];
|
|
86
|
+
const center = free ? (n * n - 1) / 2 : -1;
|
|
87
|
+
const toItem = (c) => free && c > center ? c - 1 : c;
|
|
88
|
+
return { center, cellLines, lines: cellLines.map((l) => l.filter((c) => c !== center).map(toItem)) };
|
|
89
|
+
}
|
|
90
|
+
function resolveLayout(options) {
|
|
91
|
+
const items = layoutItems(options);
|
|
92
|
+
const free = !!options.free;
|
|
93
|
+
let size = options.size;
|
|
94
|
+
if (size == null) {
|
|
95
|
+
const len = items.length;
|
|
96
|
+
const sideOf = (cells) => {
|
|
97
|
+
const r = Math.round(Math.sqrt(cells));
|
|
98
|
+
return r * r === cells && r >= MIN_SIZE && r <= MAX_SIZE ? r : 0;
|
|
99
|
+
};
|
|
100
|
+
size = sideOf(len + (free ? 1 : 0));
|
|
101
|
+
if (!size) {
|
|
102
|
+
const other = sideOf(len + (free ? 0 : 1));
|
|
103
|
+
const hint = free ? other ? `; ${len} items fill a ${other}\xD7${other} card without "free"` : "" : other % 2 ? `, or set "free" for a ${other}\xD7${other} card with a FREE center` : "";
|
|
104
|
+
fail(
|
|
105
|
+
free ? `With "free", "items" must have one less than an odd square number of entries (8, 24, 48, 80)${hint} (got ${len}).` : `"items" must have a square number of entries (4, 9, 16, 25, \u2026, 100)${hint} (got ${len}).`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
checkSize(size, free);
|
|
110
|
+
const want = size * size - (free ? 1 : 0);
|
|
111
|
+
if (items.length !== want) {
|
|
112
|
+
const card = `a ${size}\xD7${size} card${free ? " with a FREE center" : ""}`;
|
|
113
|
+
fail(
|
|
114
|
+
options.items ? `"items" must have exactly ${want} entries for ${card} (got ${items.length}).` : `A ${card.slice(2)} needs ${want} items, but DEFAULT_ITEMS has only ${DEFAULT_ITEMS.length}. Pass "items".`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
const { center, cellLines, lines } = shape(size, free);
|
|
118
|
+
const itemCell = items.map((_, i) => free && i >= center ? i + 1 : i);
|
|
119
|
+
return { size, free, items, itemCell, center, cellLines, lines };
|
|
120
|
+
}
|
|
121
|
+
function bingoLines(size, free = false) {
|
|
122
|
+
checkSize(size, free);
|
|
123
|
+
return shape(size, free).lines;
|
|
124
|
+
}
|
|
125
|
+
|
|
56
126
|
// src/messages.ts
|
|
57
127
|
var ja = {
|
|
58
128
|
title: "\u6301\u3061\u3082\u306E\u30D3\u30F3\u30B4",
|
|
59
129
|
tag: "\u4FEE\u5B66\u65C5\u884C",
|
|
60
|
-
subtitle: "
|
|
130
|
+
subtitle: "",
|
|
61
131
|
packed: "\u305D\u308D\u3063\u305F",
|
|
62
132
|
bingo: "BINGO",
|
|
63
133
|
score: "SCORE",
|
|
@@ -67,6 +137,7 @@ var ja = {
|
|
|
67
137
|
soundOn: "\u{1F50A} \u97F3\u3042\u308A",
|
|
68
138
|
soundOff: "\u{1F507} \u97F3\u306A\u3057",
|
|
69
139
|
stamp: "OK",
|
|
140
|
+
free: "FREE",
|
|
70
141
|
reachBadge: "\u30EA\u30FC\u30C1",
|
|
71
142
|
reachPop: "\u30EA\u30FC\u30C1!",
|
|
72
143
|
bannerSingle: "BINGO!",
|
|
@@ -74,14 +145,15 @@ var ja = {
|
|
|
74
145
|
bannerTriple: "\u30C8\u30EA\u30D7\u30EB\u30D3\u30F3\u30B4!",
|
|
75
146
|
bannerNth: (n) => `${n} \u30E9\u30A4\u30F3\u76EE\uFF01`,
|
|
76
147
|
bannerMulti: (n) => `\u4E00\u6C17\u306B ${n} \u30E9\u30A4\u30F3\uFF01`,
|
|
77
|
-
statusStart: (
|
|
148
|
+
statusStart: () => "",
|
|
78
149
|
statusLines: (lines, left) => `\u30D3\u30F3\u30B4 ${lines} \u30E9\u30A4\u30F3\uFF01 \u306E\u3053\u308A ${left} \u3053`,
|
|
79
150
|
statusReach: (labels) => `\u30EA\u30FC\u30C1\uFF01 \u3042\u3068${labels.slice(0, 2).map((l) => `\u300C${l}\u300D`).join("\u304B")}${labels.length > 2 ? "\u306A\u3069" : ""}\u3067\u30D3\u30F3\u30B4`,
|
|
80
151
|
statusDone: "\u{1F389} \u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u51FA\u767A\u3067\u304D\u307E\u3059",
|
|
81
152
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
82
153
|
perfectStamp: "\u6E96\u5099\n\u5B8C\u4E86",
|
|
83
154
|
perfectTitle: "\u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01",
|
|
84
|
-
perfectBody:
|
|
155
|
+
perfectBody: (n) => `${n}\u3053 \u305C\u3093\u3076\u305D\u308D\u3063\u305F\uFF01
|
|
156
|
+
\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u3001\u3044\u3063\u3066\u3089\u3063\u3057\u3083\u3044\uFF01`,
|
|
85
157
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
86
158
|
statScore: "SCORE",
|
|
87
159
|
statCombo: "MAX COMBO",
|
|
@@ -95,7 +167,7 @@ var ja = {
|
|
|
95
167
|
var en = {
|
|
96
168
|
title: "Packing Bingo",
|
|
97
169
|
tag: "School trip",
|
|
98
|
-
subtitle: "
|
|
170
|
+
subtitle: "",
|
|
99
171
|
packed: "PACKED",
|
|
100
172
|
bingo: "BINGO",
|
|
101
173
|
score: "SCORE",
|
|
@@ -105,6 +177,7 @@ var en = {
|
|
|
105
177
|
soundOn: "\u{1F50A} Sound on",
|
|
106
178
|
soundOff: "\u{1F507} Muted",
|
|
107
179
|
stamp: "OK",
|
|
180
|
+
free: "FREE",
|
|
108
181
|
reachBadge: "REACH",
|
|
109
182
|
reachPop: "Reach!",
|
|
110
183
|
bannerSingle: "BINGO!",
|
|
@@ -112,14 +185,15 @@ var en = {
|
|
|
112
185
|
bannerTriple: "TRIPLE BINGO!",
|
|
113
186
|
bannerNth: (n) => `Line ${n}!`,
|
|
114
187
|
bannerMulti: (n) => `${n} lines at once!`,
|
|
115
|
-
statusStart: (
|
|
188
|
+
statusStart: () => "",
|
|
116
189
|
statusLines: (lines, left) => `${lines} bingo line${lines === 1 ? "" : "s"}! ${left} to go`,
|
|
117
190
|
statusReach: (labels) => `Reach! Pack ${labels.slice(0, 2).join(" or ")}${labels.length > 2 ? " (or more)" : ""} for a bingo`,
|
|
118
191
|
statusDone: "\u{1F389} Perfect! Nothing left behind.",
|
|
119
192
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
120
193
|
perfectStamp: "ALL\nSET",
|
|
121
194
|
perfectTitle: "Perfect!",
|
|
122
|
-
perfectBody:
|
|
195
|
+
perfectBody: (n) => `All ${n} packed!
|
|
196
|
+
Have a great trip!`,
|
|
123
197
|
perfectBonus: (n) => `PERFECT BONUS +${n}`,
|
|
124
198
|
statScore: "SCORE",
|
|
125
199
|
statCombo: "MAX COMBO",
|
|
@@ -233,7 +307,7 @@ function resolveStorageKey(options) {
|
|
|
233
307
|
if (options.controlled) return null;
|
|
234
308
|
if (options.storageKey !== void 0) return options.storageKey || null;
|
|
235
309
|
if (options.demo) return null;
|
|
236
|
-
return itemsKey(options
|
|
310
|
+
return itemsKey(layoutItems(options));
|
|
237
311
|
}
|
|
238
312
|
function itemsKey(items) {
|
|
239
313
|
const text = items.map((it) => `${it.emoji}\0${it.label}`).join("");
|
|
@@ -248,14 +322,14 @@ function itemsKey(items) {
|
|
|
248
322
|
// src/styles.ts
|
|
249
323
|
var FONT_URL = "https://fonts.googleapis.com/css2?family=Dela+Gothic+One&family=M+PLUS+Rounded+1c:wght@500;800&display=swap";
|
|
250
324
|
var CSS = `
|
|
251
|
-
.pkb{--pk-sun:var(--pkb-sun,#FFD54F);--pk-ink:var(--pkb-ink,#2A1F14);--pk-paper:var(--pkb-paper,#FFFDF6);--pk-red:var(--pkb-red,#E8473B);--pk-mint:var(--pkb-mint,#BDEBD2);--pk-display:"Dela Gothic One",var(--pkb-font-fallback,sans-serif);--pk-body:"M PLUS Rounded 1c","Hiragino Maru Gothic ProN",var(--pkb-font-fallback,sans-serif);container-type:inline-size;isolation:isolate;width:100%}
|
|
325
|
+
.pkb{--pk-sun:var(--pkb-sun,#FFD54F);--pk-ink:var(--pkb-ink,#2A1F14);--pk-paper:var(--pkb-paper,#FFFDF6);--pk-red:var(--pkb-red,#E8473B);--pk-mint:var(--pkb-mint,#BDEBD2);--pk-display:"Dela Gothic One",var(--pkb-font-fallback,sans-serif);--pk-body:"M PLUS Rounded 1c","Hiragino Maru Gothic ProN",var(--pkb-font-fallback,sans-serif);--pk-n:4;--pk-s:1;--pk-d:1;container-type:inline-size;isolation:isolate;width:100%}
|
|
252
326
|
.pkb,.pkb *{box-sizing:border-box;margin:0;padding:0}
|
|
253
327
|
.pkb-board{position:relative;overflow:hidden;max-width:720px;margin:0 auto;padding:22px 26px 20px;border-radius:24px;background-color:var(--pk-sun);background-image:radial-gradient(rgba(42,31,20,.12) 1.3px,transparent 1.6px);background-size:16px 16px;border:2.5px solid var(--pk-ink);color:var(--pk-ink);font-family:var(--pk-body);line-height:1.4;text-align:left}
|
|
254
328
|
.pkb-board.pkb-shake{animation:pkb-shake .45s cubic-bezier(.36,.07,.19,.97)}
|
|
255
329
|
@keyframes pkb-shake{10%,90%{transform:translate(-1px,1px)}20%,80%{transform:translate(3px,-2px)}30%,50%,70%{transform:translate(-6px,2px)}40%,60%{transform:translate(6px,-1px)}}
|
|
256
330
|
.pkb-board.pkb-punch{animation:pkb-punch .4s cubic-bezier(.3,1.6,.5,1)}
|
|
257
331
|
@keyframes pkb-punch{30%{transform:scale(1.018)}}
|
|
258
|
-
.pkb-head{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-bottom:18px}
|
|
332
|
+
.pkb-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px 16px;margin-bottom:18px}
|
|
259
333
|
.pkb-tags{display:flex;gap:8px;align-items:center}
|
|
260
334
|
.pkb-kicker{display:inline-block;background:var(--pk-ink);color:var(--pk-sun);font-weight:800;font-size:11px;letter-spacing:.14em;padding:4px 10px;border-radius:999px}
|
|
261
335
|
.pkb-kicker.pkb-gold{color:#fff;background:linear-gradient(90deg,#E8473B,#FF9F1C,#1FA971,#2F6FED,#E8473B);background-size:200% 100%;animation:pkb-rb 2s linear infinite}
|
|
@@ -263,7 +337,7 @@ var CSS = `
|
|
|
263
337
|
.pkb button{font-family:var(--pk-body)}
|
|
264
338
|
.pkb-snd{appearance:none;border:2px solid var(--pk-ink);background:var(--pk-paper);border-radius:999px;padding:2px 9px;font-weight:800;font-size:11px;line-height:1.4;color:var(--pk-ink);cursor:pointer}
|
|
265
339
|
.pkb-title{font-family:var(--pk-display);font-weight:400;font-size:34px;line-height:1.15;margin-top:8px;letter-spacing:.02em;color:inherit}
|
|
266
|
-
.pkb-sub{font-weight:800;font-size:13px;margin-top:4px}
|
|
340
|
+
.pkb-sub{font-weight:800;font-size:13px;margin-top:4px;word-break:auto-phrase}
|
|
267
341
|
.pkb-side{display:flex;flex-direction:column;gap:8px;flex:none}
|
|
268
342
|
.pkb-score{display:flex;gap:10px}
|
|
269
343
|
.pkb-pill{min-width:78px;background:var(--pk-paper);border:2.5px solid var(--pk-ink);border-radius:14px;padding:6px 10px 5px;text-align:center;box-shadow:3px 3px 0 var(--pk-ink)}
|
|
@@ -282,16 +356,18 @@ var CSS = `
|
|
|
282
356
|
.pkb-combo.pkb-on .pkb-bar i{animation:pkb-drain 2.5s linear forwards}
|
|
283
357
|
@keyframes pkb-drain{from{transform:scaleX(1)}to{transform:scaleX(0)}}
|
|
284
358
|
.pkb-gw{position:relative}
|
|
285
|
-
.pkb-grid{display:grid;grid-template-columns:repeat(
|
|
286
|
-
.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}
|
|
287
|
-
.pkb-tile:active{transform:translate(3px,3px);box-shadow:1px 1px 0 var(--pk-ink)}
|
|
359
|
+
.pkb-grid{display:grid;grid-template-columns:repeat(var(--pk-n),minmax(0,1fr));gap:calc(12px*var(--pk-d))}
|
|
360
|
+
.pkb-tile{position:relative;min-width:0;place-self:stretch;aspect-ratio:1/.82;background:var(--pk-paper);border:2.5px solid var(--pk-ink);border-radius:calc(16px*var(--pk-d));box-shadow:4px 4px 0 var(--pk-ink);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:calc(6px*var(--pk-d));cursor:pointer;user-select:none;-webkit-tap-highlight-color:transparent;transition:transform .1s,box-shadow .1s,background-color .25s}
|
|
361
|
+
.pkb-tile:active:not(.pkb-free){transform:translate(3px,3px);box-shadow:1px 1px 0 var(--pk-ink)}
|
|
288
362
|
.pkb-tile input{position:absolute;opacity:0;pointer-events:none;width:1px;height:1px}
|
|
289
363
|
.pkb-tile:has(input:focus-visible){outline:3px solid var(--pk-ink);outline-offset:3px}
|
|
290
|
-
.pkb-emo{font-size:32px;line-height:1;transition:opacity .2s}
|
|
291
|
-
.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}
|
|
292
|
-
.pkb-stamp{position:absolute;left:50%;top:50%;width:64%;aspect-ratio:1;border-radius:50%;border:3px solid var(--pk-red);box-shadow:inset 0 0 0 2px var(--pk-paper),inset 0 0 0 4px var(--pk-red);color:var(--pk-red);display:grid;place-items:center;font-family:var(--pk-display);font-size:20px;letter-spacing:.04em;opacity:0;transform:translate(-50%,-50%) rotate(-16deg) scale(1.7);transition:transform .25s cubic-bezier(.3,1.5,.5,1),opacity .15s;pointer-events:none}
|
|
364
|
+
.pkb-emo{font-size:max(14px,calc(32px*var(--pk-s)));line-height:1;transition:opacity .2s}
|
|
365
|
+
.pkb-lbl{font-weight:800;font-size:max(9px,calc(13px*var(--pk-s)));line-height:1.25;text-align:center;padding:0 calc(6px*var(--pk-d));transition:opacity .2s;word-break:keep-all;overflow-wrap:anywhere}
|
|
366
|
+
.pkb-stamp{position:absolute;left:50%;top:50%;width:64%;aspect-ratio:1;border-radius:50%;border:3px solid var(--pk-red);box-shadow:inset 0 0 0 2px var(--pk-paper),inset 0 0 0 4px var(--pk-red);color:var(--pk-red);display:grid;place-items:center;font-family:var(--pk-display);font-size:max(9px,calc(20px*var(--pk-s)));letter-spacing:.04em;opacity:0;transform:translate(-50%,-50%) rotate(-16deg) scale(1.7);transition:transform .25s cubic-bezier(.3,1.5,.5,1),opacity .15s;pointer-events:none}
|
|
293
367
|
.pkb-tile.pkb-on .pkb-stamp{opacity:.9;transform:translate(-50%,-50%) rotate(-16deg) scale(1)}
|
|
294
368
|
.pkb-tile.pkb-on .pkb-emo,.pkb-tile.pkb-on .pkb-lbl{opacity:.35}
|
|
369
|
+
.pkb-tile.pkb-free{cursor:default;container-type:inline-size}
|
|
370
|
+
.pkb-tile.pkb-free .pkb-stamp{width:72%;opacity:1;transition:none;font-size:17cqi;letter-spacing:.02em;line-height:1;text-align:center;background:var(--pk-red);color:var(--pk-paper);border-color:var(--pk-red);box-shadow:inset 0 0 0 2px var(--pk-red),inset 0 0 0 4px var(--pk-paper)}
|
|
295
371
|
.pkb-tile.pkb-line{background-color:var(--pk-mint)}
|
|
296
372
|
.pkb-tile.pkb-line::before{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:linear-gradient(110deg,transparent 30%,rgba(255,255,255,.9) 45%,transparent 60%) no-repeat;background-size:250% 100%;background-position:200% 0;animation:pkb-glint 3.2s ease-in-out var(--pkb-gd,0s) infinite}
|
|
297
373
|
@keyframes pkb-glint{0%{background-position:200% 0}30%,100%{background-position:-100% 0}}
|
|
@@ -308,11 +384,11 @@ var CSS = `
|
|
|
308
384
|
.pkb-lines{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible;z-index:2;transition:opacity .4s}
|
|
309
385
|
.pkb-board.pkb-all .pkb-lines{opacity:0}
|
|
310
386
|
.pkb-lines line{fill:none;stroke-linecap:round;stroke-dasharray:1;stroke-dashoffset:0}
|
|
311
|
-
.pkb-lines .pkb-u{stroke:var(--pk-paper);stroke-width:
|
|
312
|
-
.pkb-lines .pkb-o{stroke:var(--pk-ink);stroke-width:
|
|
387
|
+
.pkb-lines .pkb-u{stroke:var(--pk-paper);stroke-width:calc(13px*var(--pk-d))}
|
|
388
|
+
.pkb-lines .pkb-o{stroke:var(--pk-ink);stroke-width:calc(6px*var(--pk-d))}
|
|
313
389
|
.pkb-lines .pkb-draw line{animation:pkb-draw .45s ease-out .3s both}
|
|
314
390
|
@keyframes pkb-draw{from{stroke-dashoffset:1}}
|
|
315
|
-
.pkb-foot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:18px;font-weight:800;font-size:13px}
|
|
391
|
+
.pkb-foot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:18px;font-weight:800;font-size:13px;word-break:auto-phrase}
|
|
316
392
|
.pkb-reset{appearance:none;flex:none;border:2px solid var(--pk-ink);background:transparent;border-radius:999px;padding:5px 12px;font-weight:800;font-size:12px;line-height:1.4;color:var(--pk-ink);cursor:pointer}
|
|
317
393
|
.pkb-flash{position:absolute;inset:0;z-index:7;background:#fff;opacity:0;pointer-events:none}
|
|
318
394
|
.pkb-flash.pkb-go{animation:pkb-flash .4s ease-out}
|
|
@@ -327,7 +403,7 @@ var CSS = `
|
|
|
327
403
|
.pkb-float{position:absolute;z-index:11;pointer-events:none;white-space:nowrap;font-family:var(--pk-display);font-weight:400;line-height:1;-webkit-text-stroke:4px var(--pk-ink);paint-order:stroke fill;transform:translate(-50%,-50%);animation:pkb-rise .95s cubic-bezier(.2,.9,.3,1) both}
|
|
328
404
|
.pkb-float.pkb-pt{font-size:22px;color:var(--pk-paper)}
|
|
329
405
|
.pkb-float.pkb-cmb{font-size:17px}
|
|
330
|
-
.pkb-float.pkb-big{font-size:clamp(
|
|
406
|
+
.pkb-float.pkb-big{font-size:clamp(14px,5.6cqi,34px);color:#FFE066}
|
|
331
407
|
.pkb-float.pkb-rch{font-size:20px;color:var(--pk-red);-webkit-text-stroke:4px var(--pk-paper)}
|
|
332
408
|
@keyframes pkb-rise{0%{opacity:0;transform:translate(-50%,-20%) scale(.4)}18%{opacity:1;transform:translate(-50%,-60%) scale(1.2)}30%{transform:translate(-50%,-70%) scale(1)}75%{opacity:1}100%{opacity:0;transform:translate(-50%,-190%) scale(1)}}
|
|
333
409
|
.pkb-perfect{position:absolute;inset:0;z-index:8;display:grid;place-items:center;padding:20px;background:rgba(42,31,20,.5);opacity:0;visibility:hidden;transition:opacity .3s,visibility .3s}
|
|
@@ -338,7 +414,7 @@ var CSS = `
|
|
|
338
414
|
.pkb-perfect.pkb-show .pkb-medal{animation:pkb-slam .38s cubic-bezier(.5,0,.75,0) .35s forwards}
|
|
339
415
|
@keyframes pkb-slam{to{opacity:1;transform:rotate(-14deg) scale(1)}}
|
|
340
416
|
.pkb-ptitle{font-family:var(--pk-display);font-weight:400;font-size:30px;line-height:1.3;color:inherit}
|
|
341
|
-
.pkb-pbody{font-weight:800;font-size:13.5px;line-height:1.6;margin:6px 0 14px;white-space:pre-line}
|
|
417
|
+
.pkb-pbody{font-weight:800;font-size:13.5px;line-height:1.6;margin:6px 0 14px;white-space:pre-line;word-break:auto-phrase}
|
|
342
418
|
.pkb-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:16px}
|
|
343
419
|
.pkb-stats>div{border:2px solid var(--pk-ink);border-radius:12px;padding:6px 4px 5px;background:#fff}
|
|
344
420
|
.pkb-stats small{display:block;font-size:9.5px;font-weight:800;letter-spacing:.1em}
|
|
@@ -348,32 +424,20 @@ var CSS = `
|
|
|
348
424
|
.pkb-fx{position:absolute;inset:0;width:100%;height:100%;z-index:10;pointer-events:none}
|
|
349
425
|
.pkb-demo{max-width:720px;margin:12px auto 4px;display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-family:var(--pk-body);font-size:12px;color:#666}
|
|
350
426
|
.pkb-demo button{appearance:none;border:1px solid #ccc;background:#f3f3f3;color:#222;border-radius:999px;padding:5px 12px;font-size:12px;cursor:pointer}
|
|
351
|
-
@container (max-width:560px){.pkb-board{padding:18px 14px 16px}.pkb-head{flex-direction:column;align-items:stretch;gap:12px}.pkb-score{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.pkb-pill,.pkb-pill.pkb-sc{min-width:0;padding:5px 4px 4px}.pkb-pill b{font-size:19px}.pkb-title{font-size:26px}.pkb-grid{gap:8px}.pkb-tile{border-radius:12px;box-shadow:3px 3px 0 var(--pk-ink);gap:3px;aspect-ratio:1/1}.pkb-emo{font-size:24px}.pkb-lbl{font-size:10.5px;padding:0 2px}.pkb-stamp{font-size:14px}.pkb-tile.pkb-reach::after{font-size:9px;padding:2px 5px;top:-9px;right:-5px}.pkb-lines .pkb-u{stroke-width:
|
|
427
|
+
@container (max-width:560px){.pkb-board{padding:18px 14px 16px}.pkb-head{flex-direction:column;align-items:stretch;gap:12px}.pkb-score{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.pkb-pill,.pkb-pill.pkb-sc{min-width:0;padding:5px 4px 4px}.pkb-pill b{font-size:19px}.pkb-title{font-size:26px}.pkb-grid{gap:max(3px,calc(8px*var(--pk-d)))}.pkb-tile{border-radius:calc(12px*var(--pk-d));box-shadow:3px 3px 0 var(--pk-ink);gap:calc(3px*var(--pk-d));aspect-ratio:1/1}.pkb-emo{font-size:max(13px,calc(24px*var(--pk-s)))}.pkb-lbl{font-size:max(8.5px,calc(10.5px*var(--pk-s)));padding:0 calc(2px*var(--pk-d))}.pkb-stamp{font-size:max(9px,calc(14px*var(--pk-s)))}.pkb-tile.pkb-reach::after{font-size:9px;padding:2px 5px;top:-9px;right:-5px}.pkb-lines .pkb-u{stroke-width:calc(10px*var(--pk-d))}.pkb-lines .pkb-o{stroke-width:calc(5px*var(--pk-d))}.pkb-banner::before{width:380px;height:380px;margin:-190px 0 0 -190px}.pkb-banner span{font-size:12px}.pkb-medal{width:96px;height:96px;font-size:20px}.pkb-card{padding:20px 14px 16px}.pkb-ptitle{font-size:24px}.pkb-stats b{font-size:17px}.pkb-float.pkb-pt{font-size:17px}.pkb-float.pkb-cmb{font-size:13px}}
|
|
428
|
+
@container (max-width:400px){.pkb-board{padding:16px 10px 14px}.pkb-grid{gap:max(3px,calc(6px*var(--pk-d)))}.pkb-lbl{font-size:max(8.5px,calc(10px*var(--pk-s)));padding:0 calc(1px*var(--pk-d))}.pkb-emo{font-size:max(12px,calc(22px*var(--pk-s)))}.pkb-stamp{font-size:max(9px,calc(13px*var(--pk-s)))}.pkb-foot{margin-top:14px;font-size:12px}.pkb-perfect{padding:12px}.pkb-stats{gap:6px}.pkb-stats>div{padding:6px 2px 5px}.pkb-stats small{letter-spacing:.02em}}
|
|
429
|
+
@container (max-width:320px){.pkb-board{padding-inline:8px}.pkb-grid{gap:max(3px,calc(5px*var(--pk-d)))}.pkb-lbl{font-size:max(8.5px,calc(9.5px*var(--pk-s)));padding:0}}
|
|
352
430
|
@media (prefers-reduced-motion:reduce){.pkb-board.pkb-shake,.pkb-board.pkb-punch,.pkb-flash.pkb-go,.pkb-tile.pkb-hop,.pkb-tile.pkb-squash,.pkb-tile.pkb-reach,.pkb-tile.pkb-line::before,.pkb-pill.pkb-hot,.pkb-bump,.pkb-tile.pkb-reach::after,.pkb-banner::before,.pkb-lines .pkb-draw line,.pkb-kicker.pkb-gold{animation:none}.pkb-banner.pkb-show{animation:pkb-fade 1.8s forwards}.pkb-float{animation:pkb-fade 1s both}.pkb-perfect.pkb-show .pkb-medal{animation:none;opacity:1;transform:rotate(-14deg)}.pkb-card{transition:none}}
|
|
353
431
|
@keyframes pkb-fade{0%,100%{opacity:0}15%,80%{opacity:1}}
|
|
354
432
|
`;
|
|
355
433
|
|
|
356
434
|
// src/core.ts
|
|
357
|
-
var LINES =
|
|
358
|
-
[0, 1, 2, 3],
|
|
359
|
-
[4, 5, 6, 7],
|
|
360
|
-
[8, 9, 10, 11],
|
|
361
|
-
[12, 13, 14, 15],
|
|
362
|
-
[0, 4, 8, 12],
|
|
363
|
-
[1, 5, 9, 13],
|
|
364
|
-
[2, 6, 10, 14],
|
|
365
|
-
[3, 7, 11, 15],
|
|
366
|
-
[0, 5, 10, 15],
|
|
367
|
-
[3, 6, 9, 12]
|
|
368
|
-
];
|
|
369
|
-
var DEMO_START = [0, 1, 2, 3, 5, 10];
|
|
435
|
+
var LINES = bingoLines(4);
|
|
370
436
|
var CONFETTI = ["#E8473B", "#2F6FED", "#1FA971", "#FFFDF6", "#2A1F14", "#FF9F1C"];
|
|
371
437
|
var COMBO_COLORS = ["#FF9F1C", "#1FA971", "#2F6FED", "#9B5DE5", "#E8473B"];
|
|
372
438
|
var COMBO_MS = 2500;
|
|
373
439
|
var MUTE_KEY = "packing-bingo-muted";
|
|
374
440
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
375
|
-
var normalize = (v) => Array.from({ length: 16 }, (_, i) => !!v?.[i]);
|
|
376
|
-
var demoStart = () => Array.from({ length: 16 }, (_, i) => DEMO_START.includes(i));
|
|
377
441
|
function readStore(key) {
|
|
378
442
|
try {
|
|
379
443
|
return localStorage.getItem(key);
|
|
@@ -402,15 +466,21 @@ function ensureAssets(fonts) {
|
|
|
402
466
|
document.head.appendChild(l);
|
|
403
467
|
}
|
|
404
468
|
}
|
|
405
|
-
function template(
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
469
|
+
function template(L, M, demo) {
|
|
470
|
+
const n = L.size;
|
|
471
|
+
const tiles = Array.from({ length: n * n }, (_, c) => {
|
|
472
|
+
const gd = `--pkb-gd:${+((Math.floor(c / n) + c % n) * 0.36 / Math.max(1, n - 1)).toFixed(3)}s`;
|
|
473
|
+
if (c === L.center)
|
|
474
|
+
return `<div class="pkb-tile pkb-free pkb-on" style="${gd}"><span class="pkb-stamp">${esc(M.free)}</span></div>`;
|
|
475
|
+
const it = L.items[L.itemCell.indexOf(c)];
|
|
476
|
+
return `<label class="pkb-tile" data-reach="${esc(M.reachBadge)}" style="${gd}"><input type="checkbox"><span class="pkb-emo" aria-hidden="true">${esc(it.emoji)}</span><span class="pkb-lbl">${esc(it.label).replace(/・/g, "\u30FB<wbr>")}</span><span class="pkb-stamp" aria-hidden="true">${esc(M.stamp)}</span></label>`;
|
|
477
|
+
}).join("");
|
|
478
|
+
const scale = `--pk-n:${n};--pk-s:${+Math.min(1.35, 4 / n).toFixed(3)};--pk-d:${+Math.min(1, 4 / n).toFixed(3)}`;
|
|
409
479
|
const demoBar = demo ? `<div class="pkb-demo"><span>${esc(M.demoLabel)}</span><button type="button" data-r="dB">${esc(M.demoBingo)}</button><button type="button" data-r="dP">${esc(M.demoPerfect)}</button><button type="button" data-r="dR">${esc(M.demoRestore)}</button></div>` : "";
|
|
410
|
-
return `<div class="pkb"><div class="pkb-board">
|
|
480
|
+
return `<div class="pkb" style="${scale}"><div class="pkb-board">
|
|
411
481
|
<div class="pkb-head"><div>
|
|
412
482
|
<div class="pkb-tags"><span class="pkb-kicker" data-r="kick">${esc(M.tag)}</span><button type="button" class="pkb-snd" data-r="snd"></button></div>
|
|
413
|
-
<h2 class="pkb-title">${esc(M.title)}</h2
|
|
483
|
+
<h2 class="pkb-title">${esc(M.title)}</h2>${M.subtitle ? `<p class="pkb-sub">${esc(M.subtitle)}</p>` : ""}</div>
|
|
414
484
|
<div class="pkb-side"><div class="pkb-score">
|
|
415
485
|
<div class="pkb-pill"><small>${esc(M.packed)}</small><b data-r="cnt"></b></div>
|
|
416
486
|
<div class="pkb-pill" data-r="bp"><small>${esc(M.bingo)}</small><b data-r="bng"></b></div>
|
|
@@ -421,14 +491,22 @@ function template(items, M, demo) {
|
|
|
421
491
|
<div class="pkb-foot"><span data-r="msg" aria-live="polite"></span><button type="button" class="pkb-reset" data-r="reset">${esc(M.reset)}</button></div>
|
|
422
492
|
<div class="pkb-flash" data-r="flash"></div>
|
|
423
493
|
<div class="pkb-perfect" data-r="perfect"><div class="pkb-card" role="dialog" aria-label="${esc(M.perfectTitle)}">
|
|
424
|
-
<div class="pkb-medal">${esc(M.perfectStamp)}</div><h3 class="pkb-ptitle">${esc(M.perfectTitle)}</h3><p class="pkb-pbody">${esc(M.
|
|
494
|
+
<div class="pkb-medal">${esc(M.perfectStamp)}</div><h3 class="pkb-ptitle">${esc(M.perfectTitle)}</h3><p class="pkb-pbody" data-r="pbody">${esc(perfectText(M, L.items.length))}</p>
|
|
425
495
|
<div class="pkb-stats"><div><small>${esc(M.statScore)}</small><b data-r="sScore">0</b></div><div><small>${esc(M.statCombo)}</small><b data-r="sCombo">\xD70</b></div><div><small>${esc(M.statLines)}</small><b data-r="sBingo">0</b></div></div>
|
|
426
496
|
<button type="button" class="pkb-close" data-r="close">${esc(M.close)}</button></div></div>
|
|
427
497
|
<canvas class="pkb-fx"></canvas></div>${demoBar}</div>`;
|
|
428
498
|
}
|
|
499
|
+
var perfectText = (M, total) => typeof M.perfectBody === "function" ? M.perfectBody(total) : M.perfectBody;
|
|
429
500
|
function createPackingBingo(container, options = {}) {
|
|
430
|
-
const
|
|
431
|
-
|
|
501
|
+
const L = resolveLayout(options);
|
|
502
|
+
const { items, lines, cellLines } = L;
|
|
503
|
+
const n = L.size, N = items.length;
|
|
504
|
+
const normalize = (v) => Array.from({ length: N }, (_, i) => !!v?.[i]);
|
|
505
|
+
const demoCells = [...cellLines[0], ...cellLines[2 * n].slice(1, -1)];
|
|
506
|
+
const demoItems = new Set(L.itemCell.flatMap((c, i) => demoCells.includes(c) ? [i] : []));
|
|
507
|
+
const demoStart = () => Array.from({ length: N }, (_, i) => demoItems.has(i));
|
|
508
|
+
const far = (c) => Math.floor(c / n) + c % n;
|
|
509
|
+
const dScale = Math.min(1, 4 / n);
|
|
432
510
|
const M = { ...ja, ...options.messages };
|
|
433
511
|
if (options.title != null) M.title = options.title;
|
|
434
512
|
if (options.tag != null) M.tag = options.tag;
|
|
@@ -449,7 +527,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
449
527
|
const wait = (ms) => new Promise((r) => window.setTimeout(r, ms));
|
|
450
528
|
let gen = 0;
|
|
451
529
|
ensureAssets(options.fonts !== false);
|
|
452
|
-
container.innerHTML = template(
|
|
530
|
+
container.innerHTML = template(L, M, demo);
|
|
453
531
|
const $ = (r) => {
|
|
454
532
|
const el = container.querySelector(`[data-r="${r}"]`);
|
|
455
533
|
if (!el) throw new Error(`[packing-bingo] internal: missing element "${r}"`);
|
|
@@ -461,7 +539,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
461
539
|
const svg = container.querySelector(".pkb-lines");
|
|
462
540
|
const cv = container.querySelector(".pkb-fx");
|
|
463
541
|
const ctx = cv.getContext("2d");
|
|
464
|
-
const
|
|
542
|
+
const cellTiles = Array.from(grid.children);
|
|
543
|
+
const tiles = L.itemCell.map((c) => cellTiles[c]);
|
|
544
|
+
const freeTile = L.center >= 0 ? cellTiles[L.center] : null;
|
|
465
545
|
const ins = tiles.map((t) => t.querySelector("input"));
|
|
466
546
|
const kick = $("kick"), snd = $("snd"), perfectEl = $("perfect");
|
|
467
547
|
let checked = normalize(void 0);
|
|
@@ -496,14 +576,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
496
576
|
cv.width = Math.round(BW * dpr);
|
|
497
577
|
cv.height = Math.round(BH * dpr);
|
|
498
578
|
};
|
|
499
|
-
const piece = (x, y, vx, vy, g, s, life,
|
|
579
|
+
const piece = (x, y, vx, vy, g, s, life, shape2) => ({
|
|
500
580
|
x,
|
|
501
581
|
y,
|
|
502
582
|
vx,
|
|
503
583
|
vy,
|
|
504
584
|
g,
|
|
505
585
|
life,
|
|
506
|
-
shape,
|
|
586
|
+
shape: shape2,
|
|
507
587
|
w: (5 + Math.random() * 6) * s,
|
|
508
588
|
h: (3 + Math.random() * 5) * s,
|
|
509
589
|
r: Math.random() * 6,
|
|
@@ -513,9 +593,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
513
593
|
const go = () => {
|
|
514
594
|
if (!raf && ctx) raf = requestAnimationFrame(tick);
|
|
515
595
|
};
|
|
516
|
-
function burst(x, y,
|
|
596
|
+
function burst(x, y, n2, o = {}) {
|
|
517
597
|
if (calm) return;
|
|
518
|
-
for (let i = 0; i <
|
|
598
|
+
for (let i = 0; i < n2; i++) {
|
|
519
599
|
const a = (o.angle ?? -Math.PI / 2) + (Math.random() - 0.5) * (o.spread ?? Math.PI * 2);
|
|
520
600
|
const v = (o.speed ?? 10) * (0.35 + Math.random() * 0.85);
|
|
521
601
|
const sh = Math.random() < (o.star ?? 0.15) ? "star" : Math.random() < 0.3 ? "round" : "rect";
|
|
@@ -606,11 +686,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
606
686
|
void el.offsetWidth;
|
|
607
687
|
el.classList.add(cls);
|
|
608
688
|
};
|
|
609
|
-
const hop = (
|
|
689
|
+
const hop = (t, d) => {
|
|
610
690
|
if (calm) return;
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
replay(
|
|
691
|
+
t.classList.remove("pkb-squash");
|
|
692
|
+
t.style.setProperty("--pkb-d", d + "ms");
|
|
693
|
+
replay(t, "pkb-hop");
|
|
614
694
|
};
|
|
615
695
|
grid.addEventListener("animationend", (e) => {
|
|
616
696
|
const t = e.target;
|
|
@@ -658,8 +738,8 @@ function createPackingBingo(container, options = {}) {
|
|
|
658
738
|
st(t0);
|
|
659
739
|
if (!calm) replay($("sp"), "pkb-bump");
|
|
660
740
|
}
|
|
661
|
-
const showGain = (
|
|
662
|
-
floatText(`+${
|
|
741
|
+
const showGain = (pts, x, y, cls = "pt") => {
|
|
742
|
+
floatText(`+${pts}`, x, y, cls);
|
|
663
743
|
countScore();
|
|
664
744
|
};
|
|
665
745
|
function countUp(el, a, b, d, fmt, ticks = false) {
|
|
@@ -679,15 +759,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
679
759
|
};
|
|
680
760
|
st(t0);
|
|
681
761
|
}
|
|
682
|
-
const ctr = (
|
|
683
|
-
const t = tiles[i];
|
|
762
|
+
const ctr = (t) => {
|
|
684
763
|
return { x: t.offsetLeft + t.offsetWidth / 2, y: t.offsetTop + t.offsetHeight / 2 };
|
|
685
764
|
};
|
|
686
765
|
function drawLines(fresh = []) {
|
|
687
766
|
svg.setAttribute("viewBox", `0 0 ${gw.clientWidth} ${gw.clientHeight}`);
|
|
688
767
|
svg.innerHTML = cur.map((k) => {
|
|
689
|
-
const a = ctr(
|
|
690
|
-
const dx = b.x - a.x, dy = b.y - a.y, d = Math.hypot(dx, dy) || 1, ex = dx / d *
|
|
768
|
+
const a = ctr(cellTiles[cellLines[k][0]]), b = ctr(cellTiles[cellLines[k][n - 1]]);
|
|
769
|
+
const dx = b.x - a.x, dy = b.y - a.y, d = Math.hypot(dx, dy) || 1, r = 26 * dScale, ex = dx / d * r, ey = dy / d * r;
|
|
691
770
|
const at = `x1="${a.x - ex}" y1="${a.y - ey}" x2="${b.x + ex}" y2="${b.y + ey}" pathLength="1"`;
|
|
692
771
|
return `<g${fresh.includes(k) ? ' class="pkb-draw"' : ""}><line class="pkb-u" ${at}/><line class="pkb-o" ${at}/></g>`;
|
|
693
772
|
}).join("");
|
|
@@ -734,29 +813,29 @@ function createPackingBingo(container, options = {}) {
|
|
|
734
813
|
later(() => sfx.reach(), 150);
|
|
735
814
|
}
|
|
736
815
|
function bingo(fresh, pts) {
|
|
737
|
-
const
|
|
738
|
-
const seq = [...new Set(fresh.flatMap((k) =>
|
|
816
|
+
const nl = fresh.length;
|
|
817
|
+
const seq = [...new Set(fresh.flatMap((k) => cellLines[k]))].map((c) => cellTiles[c]);
|
|
739
818
|
const step = Math.min(70, 300 / seq.length);
|
|
740
819
|
seq.forEach(
|
|
741
|
-
(
|
|
742
|
-
hop(
|
|
743
|
-
const p = rel(
|
|
820
|
+
(t, j) => later(() => {
|
|
821
|
+
hop(t, 0);
|
|
822
|
+
const p = rel(t);
|
|
744
823
|
burst(p.x, p.y, 14, { speed: 5, g: 0.15, life: 30, size: 0.7, star: 0.8 });
|
|
745
824
|
sfx.note(j + 3);
|
|
746
825
|
}, j * step)
|
|
747
826
|
);
|
|
748
827
|
later(() => {
|
|
749
|
-
$("bT").textContent =
|
|
750
|
-
$("bS").textContent =
|
|
828
|
+
$("bT").textContent = nl >= 3 ? M.bannerTriple : nl === 2 ? M.bannerDouble : M.bannerSingle;
|
|
829
|
+
$("bS").textContent = nl > 1 ? M.bannerMulti(nl) : M.bannerNth(cur.length);
|
|
751
830
|
replay($("banner"), "pkb-show");
|
|
752
831
|
if (!calm) {
|
|
753
832
|
replay($("flash"), "pkb-go");
|
|
754
833
|
replay(board, "pkb-punch");
|
|
755
834
|
}
|
|
756
835
|
const c = rel(gw);
|
|
757
|
-
burst(c.x, c.y,
|
|
758
|
-
sfx.bingo(
|
|
759
|
-
buzz(
|
|
836
|
+
burst(c.x, c.y, nl > 1 ? 180 : 110, { speed: nl > 1 ? 16 : 13, star: 0.3 });
|
|
837
|
+
sfx.bingo(nl);
|
|
838
|
+
buzz(nl > 1 ? [40, 60, 40, 60, 90] : [40, 60, 40]);
|
|
760
839
|
if (pts) later(() => showGain(pts, c.x, c.y + 105, "big"), 350);
|
|
761
840
|
}, seq.length * step + 60);
|
|
762
841
|
}
|
|
@@ -764,9 +843,10 @@ function createPackingBingo(container, options = {}) {
|
|
|
764
843
|
const bonus = gotP ? 0 : 1e3;
|
|
765
844
|
gotP = true;
|
|
766
845
|
score += bonus;
|
|
767
|
-
|
|
846
|
+
cellTiles.forEach((t, c) => hop(t, far(c) * 180 / Math.max(1, n - 1)));
|
|
768
847
|
sfx.perfect();
|
|
769
848
|
if (!calm) replay($("flash"), "pkb-go");
|
|
849
|
+
$("pbody").textContent = perfectText(M, N);
|
|
770
850
|
$("sScore").textContent = "0";
|
|
771
851
|
$("sCombo").textContent = "\xD70";
|
|
772
852
|
$("sBingo").textContent = "0";
|
|
@@ -816,27 +896,28 @@ function createPackingBingo(container, options = {}) {
|
|
|
816
896
|
function render(src, quiet) {
|
|
817
897
|
const events = [];
|
|
818
898
|
const on = checked, c = on.filter(Boolean).length;
|
|
819
|
-
cur =
|
|
899
|
+
cur = lines.flatMap((l, k) => l.every((i) => on[i]) ? [k] : []);
|
|
820
900
|
const fresh = cur.filter((k) => !prevLines.has(k));
|
|
821
901
|
if ([...prevLines].some((k) => !cur.includes(k))) {
|
|
822
902
|
cancelFx();
|
|
823
|
-
if (c <
|
|
903
|
+
if (c < N) perfectEl.classList.remove("pkb-show");
|
|
824
904
|
}
|
|
825
|
-
const hit = new Set(cur.flatMap((k) =>
|
|
905
|
+
const hit = new Set(cur.flatMap((k) => cellLines[k]));
|
|
826
906
|
const reach = /* @__PURE__ */ new Set();
|
|
827
|
-
for (const l of
|
|
907
|
+
for (const l of lines) {
|
|
828
908
|
const m = l.filter((i) => !on[i]);
|
|
829
909
|
if (m.length === 1) reach.add(m[0]);
|
|
830
910
|
}
|
|
831
911
|
tiles.forEach((t, i) => {
|
|
832
912
|
t.classList.toggle("pkb-on", on[i]);
|
|
833
|
-
t.classList.toggle("pkb-line", hit.has(i));
|
|
913
|
+
t.classList.toggle("pkb-line", hit.has(L.itemCell[i]));
|
|
834
914
|
t.classList.toggle("pkb-reach", reach.has(i));
|
|
835
915
|
});
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
kick.
|
|
839
|
-
|
|
916
|
+
freeTile?.classList.toggle("pkb-line", hit.has(L.center));
|
|
917
|
+
board.classList.toggle("pkb-all", c === N);
|
|
918
|
+
kick.classList.toggle("pkb-gold", c === N);
|
|
919
|
+
kick.textContent = c === N ? M.perfectTag : M.tag;
|
|
920
|
+
$("cnt").textContent = `${c}/${N}`;
|
|
840
921
|
$("bng").textContent = String(cur.length);
|
|
841
922
|
const bp = $("bp");
|
|
842
923
|
if (cur.length > lastN) {
|
|
@@ -853,7 +934,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
853
934
|
score += linePts;
|
|
854
935
|
if (quiet) {
|
|
855
936
|
if (linePts) countScore();
|
|
856
|
-
} else if (c ===
|
|
937
|
+
} else if (c === N && !prevAll) perfect(events);
|
|
857
938
|
else if (fresh.length) {
|
|
858
939
|
bingo(fresh, linePts);
|
|
859
940
|
const e = { newLines: fresh, totalLines: cur.length, score };
|
|
@@ -864,9 +945,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
864
945
|
}
|
|
865
946
|
prevLines = new Set(cur);
|
|
866
947
|
prevReach = reach;
|
|
867
|
-
prevAll = c ===
|
|
948
|
+
prevAll = c === N;
|
|
868
949
|
const labels = [...reach].map((i) => items[i].label);
|
|
869
|
-
$("msg").textContent = c ===
|
|
950
|
+
$("msg").textContent = c === N ? M.statusDone : labels.length ? M.statusReach(labels) : cur.length ? M.statusLines(cur.length, N - c) : M.statusStart(N - c);
|
|
870
951
|
return events;
|
|
871
952
|
}
|
|
872
953
|
const flush = (events) => {
|
|
@@ -893,7 +974,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
893
974
|
got.clear();
|
|
894
975
|
gotL.clear();
|
|
895
976
|
checked.forEach((v, i) => v && got.add(i));
|
|
896
|
-
|
|
977
|
+
lines.forEach((l, k) => l.every((i) => checked[i]) && gotL.add(k));
|
|
897
978
|
prevLines = new Set(gotL);
|
|
898
979
|
prevReach = /* @__PURE__ */ new Set();
|
|
899
980
|
prevAll = checked.every(Boolean);
|
|
@@ -953,12 +1034,13 @@ function createPackingBingo(container, options = {}) {
|
|
|
953
1034
|
}
|
|
954
1035
|
};
|
|
955
1036
|
$("dB").addEventListener("click", () => {
|
|
956
|
-
const open = () =>
|
|
957
|
-
const pick = checked.every(Boolean) ? [...
|
|
1037
|
+
const open = () => lines.map((l) => l.filter((i) => !checked[i])).filter((m) => m.length).sort((a, b) => a.length - b.length)[0] ?? [];
|
|
1038
|
+
const pick = checked.every(Boolean) ? [...lines[0]] : open();
|
|
958
1039
|
void run(pick.map((i) => ({ i, delay: 320 })));
|
|
959
1040
|
});
|
|
960
1041
|
$("dP").addEventListener("click", () => {
|
|
961
|
-
const
|
|
1042
|
+
const all = items.map((_, i) => i);
|
|
1043
|
+
const miss = checked.every(Boolean) ? all : all.filter((i) => !checked[i]);
|
|
962
1044
|
void run(miss.map((i, k) => k < miss.length - 1 ? { i, delay: 170, quiet: true } : { i, delay: 450 }));
|
|
963
1045
|
});
|
|
964
1046
|
$("dR").addEventListener("click", () => {
|
|
@@ -986,14 +1068,14 @@ function createPackingBingo(container, options = {}) {
|
|
|
986
1068
|
return {
|
|
987
1069
|
setChecked(next, opts = {}) {
|
|
988
1070
|
if (dead) return;
|
|
989
|
-
const
|
|
990
|
-
const changed =
|
|
1071
|
+
const n2 = normalize(next);
|
|
1072
|
+
const changed = n2.flatMap((v, i) => v !== checked[i] ? [i] : []);
|
|
991
1073
|
if (!changed.length) return;
|
|
992
1074
|
if (!opts.animate || changed.length !== 1) {
|
|
993
|
-
baseline(
|
|
1075
|
+
baseline(n2);
|
|
994
1076
|
return;
|
|
995
1077
|
}
|
|
996
|
-
flush(apply(
|
|
1078
|
+
flush(apply(n2, changed[0], false));
|
|
997
1079
|
},
|
|
998
1080
|
reset() {
|
|
999
1081
|
if (!dead) request(normalize(void 0), null, { reset: true });
|
|
@@ -1002,7 +1084,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1002
1084
|
if (!dead) render(null, true);
|
|
1003
1085
|
},
|
|
1004
1086
|
getState() {
|
|
1005
|
-
return { checked: checked.slice(), lines: cur.slice(), score, maxCombo, complete: checked.every(Boolean) };
|
|
1087
|
+
return { checked: checked.slice(), lines: cur.slice(), score, maxCombo, complete: checked.every(Boolean), size: n, free: L.free };
|
|
1006
1088
|
},
|
|
1007
1089
|
destroy() {
|
|
1008
1090
|
if (dead) return;
|
|
@@ -1085,6 +1167,7 @@ var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, re
|
|
|
1085
1167
|
DEFAULT_ITEMS,
|
|
1086
1168
|
LINES,
|
|
1087
1169
|
PackingBingo,
|
|
1170
|
+
bingoLines,
|
|
1088
1171
|
createPackingBingo,
|
|
1089
1172
|
en,
|
|
1090
1173
|
ja
|