packing-bingo 0.5.0 → 0.6.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # 持ちものビンゴ(Packing Bingo)
2
2
 
3
- **修学旅行の持ち物チェックリストを、4×4(3×3や5×5などにも変えられます)のビンゴにするReactコンポーネントです。持ち物をかばんに入れるたびにマスをタップしてスタンプを押し、タテ・ヨコ・ナナメがそろうと、コンボ・ファンファーレ・花火で盛り上がります。進み具合は各自のブラウザに自動で保存されます。**
3
+ **修学旅行の持ち物チェックを、音・振動・花火で楽しく進めるビンゴにするReactコンポーネントです。**
4
4
 
5
5
  [![Demo on GitHub Pages](https://img.shields.io/badge/demo-GitHub%20Pages-222222?logo=githubpages&logoColor=white)](https://jin-hiroo.github.io/packing-bingo/)
6
6
  [![npm version](https://img.shields.io/npm/v/packing-bingo?logo=npm)](https://www.npmjs.com/package/packing-bingo)
@@ -9,7 +9,7 @@
9
9
  [![TypeScript types included](https://img.shields.io/badge/types-TypeScript-3178C6?logo=typescript&logoColor=white)](#api)
10
10
  [![Standalone single HTML file](https://img.shields.io/badge/standalone-single%20HTML%20file-E34F26?logo=html5&logoColor=white)](#単体のページ)
11
11
 
12
- [デモサイト](https://jin-hiroo.github.io/packing-bingo/) · [クイックスタート](#クイックスタート) · [デモコード](#デモ) · [使い方の例](#使い方の例) · [API](#api) · [組み込むときの注意](#組み込むときの注意) · [Reactなしで使う](#reactなしで使う) · [English](#english) · [変更履歴](CHANGELOG.md)
12
+ [デモサイト](https://jin-hiroo.github.io/packing-bingo/) · [v0.6.0 Release](https://github.com/Jin-hiroo/packing-bingo/releases/tag/v0.6.0) · [クイックスタート](#クイックスタート) · [デモコード](#デモ) · [使い方の例](#使い方の例) · [API](#api) · [組み込むときの注意](#組み込むときの注意) · [Reactなしで使う](#reactなしで使う) · [English](#english) · [変更履歴](CHANGELOG.md)
13
13
 
14
14
  <p align="center">
15
15
  <a href="docs/images/bingo-desktop.png"><img src="docs/images/bingo-desktop.png" alt="持ち物が並んだ4×4のビンゴカード。一番上の列に続いて、左上からのナナメの列がそろった瞬間で、大きなBINGO!の文字、「2 ライン目!」、+100の得点、紙吹雪が出ている。上の欄には8/16、2ライン、290点、2 COMBOと表示されている。" width="560"></a>
@@ -18,25 +18,35 @@
18
18
  _2コンボで2列目がそろったところ。列に線が引かれ、BINGO!と+100が出て、紙吹雪が舞います。_
19
19
 
20
20
  <p>
21
- <a href="docs/images/phone-reach.png"><img src="docs/images/phone-reach.png" alt="スマホの画面。一番上の列がそろっていて、ナナメの列を完成させる最後のマス(日焼け止め)が点線で囲まれ、リーチの札が付いて点滅している。下の1行には「リーチ! あと「日焼け止め」でビンゴ」と出ている。" height="320"></a>
22
- <a href="docs/images/phone-combo.png"><img src="docs/images/phone-combo.png" alt="スマホの画面。3回続けてタップしたところで、+20と+30、2 COMBO!と3 COMBO!の表示、減っていくコンボのゲージ、新しく出た2つのリーチ!が見える。" height="320"></a>
23
- <a href="docs/images/phone-perfect.png"><img src="docs/images/phone-perfect.png" alt="スマホの画面。16個すべてにスタンプを押したあと。準備完了のスタンプと、得点3,410、最大コンボ×10、10ラインのパーフェクト画面に、紙吹雪と持ち物の絵文字が降っている。" height="320"></a>
21
+ <a href="docs/images/phone-reach.png"><img src="docs/images/phone-reach.png" alt="スマホ幅の画面。一番上の列がそろっていて、ナナメの列を完成させる最後のマス(日焼け止め)が点線で囲まれ、リーチの札が付いている。上の欄は6/16、1ライン、160点。" height="320"></a>
22
+ <a href="docs/images/phone-combo.png"><img src="docs/images/phone-combo.png" alt="スマホ幅の画面。3回続けてタップし、+10・+20・+30、2 COMBO!と3 COMBO!、コンボのゲージが見える。健康調査票のマスにはリーチの札が付いている。" height="320"></a>
23
+ <a href="docs/images/phone-perfect.png"><img src="docs/images/phone-perfect.png" alt="スマホ幅の画面。16個すべてにスタンプを押したあとの結果画面。準備完了のスタンプと、得点3,410、最大コンボ×10、10ラインを表示している。" height="320"></a>
24
24
  </p>
25
25
 
26
- _スマホでの表示。左から、1列そろって次の列にリーチがかかったところ、3回続けたコンボ、全部そろったときのパーフェクト画面です。_
26
+ _0.5.0のスマホ幅での表示。左から、1列そろって次の列にリーチがかかったところ、3回続けたコンボ、全部そろったときのパーフェクト画面です。_
27
+
28
+ <p>
29
+ <a href="docs/images/phone-infinite.png"><img src="docs/images/phone-infinite.png" alt="スマホ幅の画面。カード全体が暗い夜空になり、虹色のオーロラの中に大きな虹色の∞、INFINITEの文字、「無限ビンゴ」の札が出て、星が舞っている。左上のスイッチは「∞ 無限」が選ばれている。" height="320"></a>
30
+ <a href="docs/images/phone-infinite-play.png"><img src="docs/images/phone-infinite-play.png" alt="スマホ幅の無限ビンゴの画面。カードの枠が虹色で、上にストップウォッチ0:08.1、BINGO 3、SCORE 1,350、14 COMBO。そろった列は消えて、スタンプは3つだけ残っている。下に虹色の🏁 FINISHボタン。" height="320"></a>
31
+ <a href="docs/images/phone-finish.png"><img src="docs/images/phone-finish.png" alt="スマホ幅の画面。FINISHを押したあとの虹色の枠の結果画面。∞のメダル、「フィニッシュ!」、プレイタイム0:09.74、SCORE 1,350、MAX COMBO ×14、BINGO 3と、「もう一度」「とじる」のボタン。紙吹雪が舞っている。" height="320"></a>
32
+ </p>
33
+
34
+ _0.6.0の無限ビンゴ。左から、切り替えたときの演出、そろった列が消えていくプレイ中の画面、🏁 FINISHで終えたときの結果画面です。_
27
35
 
28
36
  ## できること
29
37
 
30
38
  修学旅行の前の晩の荷造りは面倒です。持ちものビンゴは16個の持ち物をビンゴカードに並べるので、持ち物を1つ入れるたびにスタンプが押せて、何個かごとにお祝いがあります。
31
39
 
32
- - **タップするたびに反応があります。** スタンプ、波紋、火花、少しずつ高くなる音、スマホでは振動。2.5秒以内に続けてタップすると、コンボになります。
40
+ - **タップするたびに手応えがあります。** 押し込んで弾むスタンプ、波紋、火花、打撃音で反応します。2.5秒以内に続けてタップするとコンボになり、音と演出が強まります。Androidでは3・6・9コンボで振動のリズムも変わります。
41
+ - **タイムアタックで始まります。** 最初はカード全体に、結果画面と同じ半透明の暗い背景が重なり、「Are you ready?」と、音量とおやすみモードの確認を出します。タップすると3・2・1のカウントダウンのあとSTART!でストップウォッチが動き出し、全部そろうと止まって、結果画面にクリアタイムが出ます。
42
+ - **無限ビンゴモードで遊び続けられます。** 見出しの「通常|∞ 無限」スイッチで切り替えると、カード全体が暗転して虹色のオーロラと∞が現れる演出が出ます。無限ビンゴでは、そろった列がお祝いのあとにスタンプごと消えるので、何度でもビンゴできて、得点はどこまでも伸びます。🏁 FINISHで終えると、得点・最大コンボ・ビンゴ数・プレイタイムの結果画面が出ます。荷造りのチェックには触れないので、通常に戻すと元のカードのままです([無限ビンゴモード](#無限ビンゴモード))。
33
43
  - **次の目標がいつも見えます。** あと1つで列がそろうマスは、リーチの札を付けて点滅します。
34
- - **列がそろうたびにお祝いします。** タテ・ヨコ・ナナメの列(4×4なら10列)のどれかがそろうと、BINGO!の文字と紙吹雪が出ます。すべてそろうと、花火とスコアカードのフィナーレになります。
44
+ - **ビンゴの数でお祝いが変わります。** タテ・ヨコ・ナナメの列(4×4なら10列)がそろうと、BINGO!の文字と紙吹雪が出ます。Androidではビンゴ・ダブル・トリプル以上で振動が変わり、全部そろうと約5秒、7発の花火・破裂音・振動がリズムを刻みます。結果画面は、アプリを切り替えて戻っても残ります。
35
45
  - **マスの数を選べます。** 3×3や5×5など、2×2から10×10までの正方形にできます。奇数のときは、真ん中をはじめからスタンプ済みのFREEマスにできます。[マスの数を変える](#マスの数を変える)を見てください。
36
46
  - **並びをシャッフルできます。** 🔀ボタンで、持ち物をランダムなマスに並べ替えます。チェックは持ち物といっしょに動くので、同じ持ち物で別の列をねらえます。並べ替えた並びも保存されます。
37
47
  - **得点は、そろえることへのごほうびです。** 付けたり外したりしても増えません。[得点のルール](#得点のルール)を見てください。
38
48
  - **進み具合は自動で保存されます。** 各自のブラウザ(localStorage)に保存されるので、ページを閉じても続きから始められます。サーバーには何も送りません。
39
- - **だれでも使えるように配慮しています。** マスは本物のチェックボックスなので、キーボードで操作できます。`prefers-reduced-motion`(動きを減らす設定)に従います。音は合成した音(音声ファイルなし)で、消すこともできます。
49
+ - **音・振動・動きを選べます。** キーボードで操作でき、視覚のアニメーションは `prefers-reduced-motion`(動きを減らす設定)に従います。音はボタンで、振動は `vibrate={false}` で止められます。iPhoneの振動はタップ時の軽い触覚だけで、演出に合わせた振動はAndroid向けです([音と振動](#音と振動))。
40
50
 
41
51
  ## 概要
42
52
 
@@ -44,13 +54,34 @@ _スマホでの表示。左から、1列そろって次の列にリーチがか
44
54
  | --- | --- |
45
55
  | インストール | `npm install packing-bingo`。ESMとCommonJSの両方、TypeScriptの型付き |
46
56
  | 必要なもの | React 18以上と `react-dom`。ほかに依存するパッケージはありません。CSSの読み込みもProviderも不要です |
47
- | サイズ | 約14 kB(minify・gzip後、CSS込み、Reactは含まず) |
57
+ | サイズ | 約23 kB(ブラウザ版のminify・gzip後、CSS込み、Reactは含まず) |
48
58
  | サーバー側での表示 | `"use client"` 付きで配布しています。サーバー側では空の `<div>` を出し、ブラウザでカードを作ります |
49
59
  | 見た目 | `<head>` に `<style>` を1つ追加します。クラス名はすべて `pkb-` で始まります。CSS変数で配色を変えられ、置いた場所の幅に合わせて自動で大きさが変わります |
50
60
  | 通信 | しません。見出しなどのフォントは、使うときにページで読み込みます([フォント](#フォント)) |
51
61
  | 保存 | localStorageだけ。進み具合、シャッフルした並び、音のオン・オフの設定 |
52
62
  | ブラウザ | Chrome・Edge 105以上、Safari 16以上、Firefox 121以上。今のところChromiumでしか試していません([詳しく](#対応ブラウザと確認済みの範囲)) |
53
63
 
64
+ ## 0.6.0へアップデート
65
+
66
+ ```sh
67
+ npm install packing-bingo@latest
68
+ ```
69
+
70
+ 0.6.0では、次のものを追加しました。
71
+
72
+ - **[無限ビンゴモード](#無限ビンゴモード):** そろった列が消えて何度でもビンゴできるモードです。切り替えるとカード全体の演出が出て、🏁 FINISHで終えると結果画面が出ます。
73
+ - **[スタート画面とストップウォッチ](#スタート画面とストップウォッチ):** 3・2・1のカウントダウンで始まるタイムアタックです。
74
+ - **スピードボーナス:** 速くそろえるほど得点が増えます([得点のルール](#得点のルール))。
75
+
76
+ 0.5.0から更新するときは、次の変更に注意してください。
77
+
78
+ - **スタンプは外れなくなりました。** スタンプ済みのマスをもう一度押しても何も起きません。0.5.0と同じように外せるようにするには、`uncheck` を指定します。
79
+ - **最初のポップアップは、スタート画面に変わりました。** `playTitle`・`playStart`・`playSkip` は使われなくなりました。`soundHint`・`vibrationHint` はスタート画面に出ます。スタート画面を出さないときは `ready={false}` です。
80
+ - **カードの下の状況の1行と、パーフェクト画面の本文は、既定で出なくなりました。** 出したいときは `messages` に `statusLines`・`statusDone`・`perfectBody` を渡します。
81
+ - **TypeScriptの型が増えました。** `PackingBingoState` に `time`・`mode`・`cleared`・`finished`・`packing`、`BingoEvent` に `mode`、`PerfectEvent` に `time`・`speedBonus` が増えています。テスト用に自分で作っている値があれば、これらを足してください。`Messages` の新しいキーはすべて省略できます。
82
+
83
+ 0.4.xから更新する場合は、ビンゴ別の振動と花火に同期したフィナーレ(0.5.0)も入ります。0.3.xから更新する場合は、シャッフルと `vibrate` が追加され、フォントの自動読み込みがなくなった点にも注意してください。Google Fontsを使う場合は、クイックスタートの `<link>` をページに追加します。独自の完全な `Messages` オブジェクトには `shuffle` が必要です。詳しくは[変更履歴](CHANGELOG.md)を見てください。
84
+
54
85
  ## クイックスタート
55
86
 
56
87
  コピーして貼り付けるだけで、サイトの好きな場所に置けます。進み具合は各自のブラウザに自動で保存されるので、保存の設定も要りません。
@@ -137,7 +168,7 @@ HTMLの好きな場所に、次をそのまま貼り付けます。インスト
137
168
  ```html
138
169
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Dela+Gothic+One&family=M+PLUS+Rounded+1c:wght@500;800&display=swap" />
139
170
  <div id="packing-bingo"></div>
140
- <script src="https://cdn.jsdelivr.net/npm/packing-bingo@0.5/dist/packing-bingo.global.js"></script>
171
+ <script src="https://cdn.jsdelivr.net/npm/packing-bingo@0.6/dist/packing-bingo.global.js"></script>
141
172
  <script>
142
173
  PackingBingo.createPackingBingo(document.getElementById("packing-bingo"), {
143
174
  items: [
@@ -341,25 +372,41 @@ function PackingPage() {
341
372
  | `shuffle` | `boolean` | `true` | 🔀シャッフルボタンを出します。`false` でボタンを消しても、`ref.shuffle()` は使えます |
342
373
  | `defaultOrder` | `readonly number[]`(持ち物と同じ数) | 元の並び | 最初の並び。`getState().order` と同じ形です。シャッフルした並びが保存されていれば、そちらが優先です。どの持ち物の番号もちょうど1回ずつ入っていないときは使いません |
343
374
  | `vibrate` | `boolean` | `true` | スマホで振動します。[音と振動](#音と振動)を見てください |
375
+ | `ready` | `boolean` | `true` | 最初にスタート画面とカウントダウンを出します。`false` のときは出さず、最初のタップでストップウォッチが動き出します。[スタート画面とストップウォッチ](#スタート画面とストップウォッチ)を見てください |
376
+ | `uncheck` | `boolean` | `false` | 一度スタンプしたマスは、タップやキー操作では外れません。`true` にすると、スタンプ済みのマスをもう一度タップしてスタンプを外せます。リセット・`setChecked`・controlled の `checked` では、どちらの場合も外せます |
377
+ | `mode` | `"normal" \| "infinite"` | `"normal"` | 表示するモード。`"infinite"` で無限ビンゴから始めます。あとで値を変えると、作り直さずに切り替えの演出つきで切り替わります。カードのスイッチでも切り替わります(`onModeChange` で受け取れます)。[無限ビンゴモード](#無限ビンゴモード)を見てください |
378
+ | `modeSwitch` | `boolean` | `true` | 見出しに「通常|∞ 無限」のスイッチを出します。`false` で消しても、`mode` と `ref.setMode()` は使えます |
379
+ | `onModeChange` | `(mode: BingoMode) => void` | なし | スイッチか `setMode` でモードが変わった |
380
+ | `onFinish` | `(event: FinishEvent) => void` | なし | 無限ビンゴを🏁 FINISHボタンか `finish()` で終えた |
344
381
  | `fonts` | `boolean` | なし | 非推奨。0.4.0からは何もしません。フォントはページで読み込みます([フォント](#フォント)) |
345
382
  | `demo` | `boolean` | `false` | デモ用のボタンを出します。保存された進み具合も `defaultChecked` もなければ、一番上の列と、左上からのナナメの途中までが埋まった状態(4×4なら6マス)から始めます。`storageKey` を指定しない限り保存しません |
346
383
  | `className`・`style` | | なし | 外側の `<div>` に付きます |
347
384
  | `ref` | `Ref<PackingBingoHandle>` | なし | [ref](#ref) を見てください |
348
385
 
349
- propsは中身で比べるので、配列やオブジェクトをその場で書いても構いません。`items`・`size`・`free`・`title`・`tag`・`subtitle`・`storageKey`・`sound`・`shuffle`・`vibrate`・`demo`、または `messages` の文字列が変わると、カードを作り直します。保存先のキーが変わらなければ、チェックとシャッフルした並びはそのまま残ります。キーが変わったとき(`items` を変えて自動のキーが変わったときも含む)は、新しいキーに保存された進み具合を読み込みます。得点は[得点のルール](#得点のルール)のとおり計算し直します。`messages` の関数はpropsやstateを使っても構いません。`messages` が変わると、作り直さずに下の1行だけを描き直します。
386
+ propsは中身で比べるので、配列やオブジェクトをその場で書いても構いません。`items`・`size`・`free`・`title`・`tag`・`subtitle`・`storageKey`・`sound`・`shuffle`・`vibrate`・`ready`・`uncheck`・`modeSwitch`・`demo`、または `messages` の文字列が変わると、カードを作り直します。作り直しても、そのとき表示していたモードはそのままです(無限ビンゴは新しいゲームから)。保存先のキーが変わらなければ、チェックとシャッフルした並びはそのまま残ります。キーが変わったとき(`items` を変えて自動のキーが変わったときも含む)は、新しいキーに保存された進み具合を読み込みます。得点は[得点のルール](#得点のルール)のとおり計算し直します。`messages` の関数はpropsやstateを使っても構いません。`messages` が変わると、作り直さずに下の1行だけを描き直します。
350
387
 
351
388
  ### イベント
352
389
 
353
390
  ```ts
354
391
  interface BingoEvent {
355
392
  newLines: number[]; // このタップでそろった列(bingoLines(size, free) の番号。4×4ならLINESと同じ。シャッフルしたあとも、マスの位置の番号)
356
- totalLines: number; // これまでにそろった列の数
393
+ totalLines: number; // これまでにそろった列の数。無限ビンゴでは、そのゲームでそろえた(消えた列も含む)列の数
357
394
  score: number; // このタップのあとの得点
395
+ mode: BingoMode; // "normal" か "infinite"。無限ビンゴのビンゴでも onBingo は呼ばれます
358
396
  }
359
397
 
360
398
  interface PerfectEvent {
361
399
  score: number; // 最終得点。初めて全部そろえたときは1000点のボーナスを含む
362
400
  maxCombo: number;
401
+ time: number | null; // ストップウォッチのクリアタイム(ミリ秒)。計っていないゲームでは null
402
+ speedBonus: number; // score に含まれるスピードボーナス。出なかったときは0
403
+ }
404
+
405
+ interface FinishEvent { // 無限ビンゴを🏁 FINISHで終えたとき
406
+ score: number;
407
+ maxCombo: number;
408
+ lines: number; // そのゲームでそろえた列の数
409
+ time: number; // プレイタイム(ミリ秒)
363
410
  }
364
411
  ```
365
412
 
@@ -367,8 +414,12 @@ interface PerfectEvent {
367
414
 
368
415
  ```ts
369
416
  interface PackingBingoHandle {
370
- /** リセットボタンと同じ。カードと得点を消し、持ち物を元のマスに戻して onChange を呼ぶ。controlled のカードでは、並びを戻して onChange を呼ぶだけ。 */
417
+ /** リセットボタンと同じ。カードと得点を消し、持ち物を元のマスに戻して onChange を呼ぶ。controlled のカードでは、並びを戻して onChange を呼ぶだけ。無限ビンゴの表示中は、無限ビンゴを最初からやり直す(持ち物のチェックは消さない)。 */
371
418
  reset(): void;
419
+ /** スイッチと同じ。"normal" と "infinite" を、切り替えの演出つきで切り替える。 */
420
+ setMode(mode: BingoMode): void;
421
+ /** 🏁 FINISHボタンと同じ。動いている無限ビンゴを終えて結果を出す。通常モードやスタート前は何もしない。 */
422
+ finish(): void;
372
423
  /** 🔀ボタンと同じ。持ち物をランダムなマスに並べ替える。チェックは持ち物といっしょに動き、onChange は呼ばない。 */
373
424
  shuffle(): void;
374
425
  /** 1個だけの変化はタップと同じように演出し、それより大きな変化は静かに反映する。onChange は呼ばない。 */
@@ -378,14 +429,19 @@ interface PackingBingoHandle {
378
429
  }
379
430
 
380
431
  interface PackingBingoState {
381
- checked: boolean[]; // 持ち物ごと、左上から横に1行ずつ(FREEマスは含まない)
432
+ checked: boolean[]; // 持ち物ごと、左上から横に1行ずつ(FREEマスは含まない)。無限ビンゴの表示中は、そのゲームのマス
382
433
  lines: number[]; // そろった列(bingoLines(size, free) の番号)
383
434
  score: number;
384
435
  maxCombo: number;
385
- complete: boolean; // すべての持ち物にチェックが付いている
436
+ complete: boolean; // すべての持ち物にチェックが付いている(無限ビンゴでは常に false)
437
+ mode: BingoMode; // 表示中のモード
438
+ cleared: number; // 無限ビンゴで、このゲームにそろえた列の数(通常モードでは0)
439
+ finished: boolean; // 無限ビンゴを🏁 FINISHで終えたあと(新しいゲームを始めるまで true)
440
+ packing: { checked: boolean[]; order: number[] } | null; // 無限ビンゴの表示中に、後ろで待っている持ち物のチェックと並び(通常に戻したときの状態)。通常モードでは null
386
441
  size: number; // 1辺のマスの数
387
442
  free: boolean; // 真ん中がFREEマスか
388
443
  order: number[]; // マスごとの持ち物の番号(左上から横に1行ずつ、FREEマスは飛ばす)。シャッフルするまでは [0, 1, 2, …]
444
+ time: number; // ストップウォッチ(ミリ秒)。全部そろうと止まる
389
445
  }
390
446
  ```
391
447
 
@@ -396,6 +452,8 @@ interface PackingBingoState {
396
452
  - 初めてチェックした持ち物は、10点 × そのときのコンボ数。コンボは、2.5秒以内に続けたタップの数です。
397
453
  - 1回のタップでn列そろうと、100点 × n²。同じ持ち物の組み合わせの列で得点するのは1回だけです(シャッフルしたあとも同じです)。
398
454
  - 初めてカード全体をそろえると、1000点を足します。
455
+ - ストップウォッチで計ったゲームでは、さらにスピードボーナスを足します。1秒あたりにそろえた持ち物の数 × 1000点(16個を20秒なら800点、10秒なら1,600点)で、上限はありません。速いほど高くなるので、最高記録はいつでも更新できます。`speedBonus(items, ms)` で同じ計算ができます。
456
+ - 無限ビンゴでは、列が消えるとそのマスと列はまた得点できます。パーフェクトのボーナスとスピードボーナスはありません。
399
457
  - チェックを外しても得点は減らず、付け直しても増えません。
400
458
  - シャッフルしても得点は変わりません。並べ替えたことでそろった列は、そろった列として数えますが、得点もお祝いもありません。
401
459
  - シャッフル時に一度でもチェックしたことのある持ち物だけでできる列は、新しい組み合わせでも得点しません。シャッフル前にチェックを外しても同じです。
@@ -406,13 +464,15 @@ interface PackingBingoState {
406
464
  `messages` には、次のキーを好きなだけ指定できます。日本語と英語の一式は、[`src/messages.ts`](src/messages.ts) の `ja` と `en` です。
407
465
 
408
466
  - **文字列:** `title`、`tag`、`subtitle`、`packed`、`bingo`、`score`、`reset`、`shuffle`、`soundOn`、`soundOff`、`stamp`、`free`、`reachBadge`、`reachPop`、`bannerSingle`、`bannerDouble`、`bannerTriple`、`statusDone`、`perfectTag`、`perfectStamp`、`perfectTitle`、`perfectBody`、`statScore`、`statCombo`、`statLines`、`close`、`demoLabel`、`demoBingo`、`demoPerfect`、`demoRestore`。`perfectStamp` と `perfectBody` では、`\n` で改行します。
409
- - **関数(どれも文字列を返します):** `combo(n)`、`comboPop(n)`、`bannerNth(totalLines)`、`bannerMulti(newLines)`、`statusStart(left)`、`statusLines(lines, left)`、`statusReach(labels)`、`perfectBonus(points)`。
410
- - `perfectBody` は、文字列のほかに、持ち物の数を受け取る関数 `perfectBody(total)` にもできます。`ja` と `en` では関数で、「16こ ぜんぶそろった!」の数がカードに合わせて変わります。
411
- - `subtitle`(見出しの下の1行)と `statusStart(left)`(1列もそろう前の下の1行)は、`ja` と `en` のどちらでも空なので、表示しません。出したいときは `subtitle="タップでスタンプ。タテ・ヨコ・ナナメでビンゴ!"` や `` messages={{ statusStart: (left) => `のこり ${left} こ` }} `` のように指定します。
467
+ - **省略できる文字列(スタート画面とストップウォッチ):** `readyTitle`、`readyPrompt`、`soundHint`、`vibrationHint`、`readyTap`、`readyGo`、`time`、`statTime`。省略すると日本語の既定値になり、`soundHint`・`vibrationHint` は空文字にすると表示しません。`readyPrompt`(タイトルの下の1行)は既定では空です。0.5.0の `playTitle`・`playStart`・`playSkip` は使われなくなりました。
468
+ - **省略できる文字列(無限ビンゴモード):** `modeLabel`(スイッチの読み上げ名)、`modeNormal`・`modeInfinite`(スイッチの文字)、`normalTitle`・`normalSub`・`infiniteTitle`・`infiniteSub`(切り替えの演出の大きな文字とその下の1行。`infiniteSub` はスタート画面のバッジにも使います)、`infiniteRule`(無限ビンゴのスタート画面のルール。空文字にすると表示しません)、`finish`(🏁 FINISHボタン)、`finishTitle`・`finishStamp`・`finishTime`(終えたときの結果画面のタイトル、丸いメダルの文字、プレイタイムの見出し)、`again`(結果画面の「もう一度」)。
469
+ - **関数(どれも文字列を返します):** `combo(n)`、`comboPop(n)`、`bannerNth(totalLines)`、`bannerMulti(newLines)`、`statusStart(left)`、`statusLines(lines, left)`、`statusReach(labels)`、`perfectBonus(points)`、`speedBonus(points)`(省略可)。
470
+ - `perfectBody` は、文字列のほかに、持ち物の数を受け取る関数 `perfectBody(total)` にもできます。`ja` と `en` では空なので、結果画面には本文を表示しません。
471
+ - `subtitle`(見出しの下の1行)と、カードの左下の1行(`statusStart(left)`・`statusLines(lines, left)`・`statusReach(labels)`・`statusDone`)は、`ja` と `en` のどちらでも空なので、表示しません。出したいときは `subtitle="タップでスタンプ。タテ・ヨコ・ナナメでビンゴ!"` や `` messages={{ statusStart: (left) => `のこり ${left} こ` }} `` のように指定します。
412
472
 
413
473
  ### export
414
474
 
415
- - `packing-bingo`:`PackingBingo`、`createPackingBingo`、`LINES`、`bingoLines`、`DEFAULT_ITEMS`、`ja`、`en`。型は `PackingBingoProps`、`PackingBingoHandle`、`PackingBingoState`、`BingoItem`、`BingoEvent`、`PerfectEvent`、`Messages`、`PackingBingoOptions`、`PackingBingoInstance`。
475
+ - `packing-bingo`:`PackingBingo`、`createPackingBingo`、`LINES`、`bingoLines`、`speedBonus`、`DEFAULT_ITEMS`、`ja`、`en`。型は `PackingBingoProps`、`PackingBingoHandle`、`PackingBingoState`、`BingoItem`、`BingoEvent`、`BingoMode`、`FinishEvent`、`PerfectEvent`、`Messages`、`PackingBingoOptions`、`PackingBingoInstance`。
416
476
  - `packing-bingo/core`:Reactのコンポーネントとその型(`PackingBingoProps`、`PackingBingoHandle`)を除いた残り。Reactを読み込みません。
417
477
 
418
478
  ## 組み込むときの注意
@@ -441,9 +501,7 @@ interface PackingBingoState {
441
501
 
442
502
  ### 音と振動
443
503
 
444
- ページ内で最初にカードのマスを押したとき、「音と振動の準備はOK?」というポップアップで、音量を上げることと、Androidの「おやすみモード(Do Not Disturb)」をOFFにすることを確認します。「準備OK!はじめる」「このまま遊ぶ」またはEscで閉じると、そのページでは再表示しません。端末の設定は自分で変更してください。`messages.soundHint` と `messages.vibrationHint` で本文を変えられ、両方を空文字にするとポップアップを表示しません。`messages.playTitle`・`playStart`・`playSkip` でタイトルとボタンも変更できます。`vibrate={false}` のときは振動の案内を表示しません。
445
-
446
- 独自の翻訳を使っている場合は、ポップアップ用の上記5キーも翻訳してください。省略したキーには日本語の既定値が使われます。英語の `en` にはこれらの翻訳も含まれています。
504
+ 音量とAndroidの「おやすみモード(Do Not Disturb)」は、[スタート画面](#スタート画面とストップウォッチ)で確認をうながします。端末の設定は自分で変更してください。`vibrate={false}` のときは振動の案内を表示しません。スタート画面のカウントダウンでも、3・2・1で短い音と振動、START!で高い音と長めの振動が出ます。
447
505
 
448
506
  音はWeb Audio APIで合成しています。音を鳴らす仕組みは、ユーザーがページを操作したあとにだけ作るので、ブラウザの自動再生の警告は出ません。🔊ボタンの選択は、localStorageの `packing-bingo-muted` に保存されます。そのあとに作られるカードは、ページを読み込み直したあとも含めて、この設定で始まります。すでに表示中のほかのカードは、それぞれの設定のままです。
449
507
 
@@ -453,9 +511,35 @@ interface PackingBingoState {
453
511
  - **iPhone**(iOS 18以上のSafari):Vibration APIがないので、マスのチェックボックスに `switch` 属性を付けています。指でタップしてチェックが切り替わると、iPhoneの軽い触覚フィードバックが出ます。出るのはタップの瞬間だけで、ビンゴなどの演出に合わせた振動はありません。`role="checkbox"` も付けているので、読み上げは今までどおりチェックボックスです。ほかのブラウザは `switch` 属性を無視します。
454
512
  - 端末の設定で振動や触覚を切っていると、振動しません。
455
513
 
514
+ ### スタート画面とストップウォッチ
515
+
516
+ カードを開くと、カード全体に結果画面と同じ半透明の暗い背景が重なり、真ん中の白いパネルに「Are you ready?」と、「端末の音量をMAXに」「おやすみモードをオフ」をまとめて表示します。暗い背景のどこかをタップするか、フォーカスしてEnterかSpaceを押すと、3・2・1とカウントダウンして、START!で背景が消え、ストップウォッチが動き出します。カウントダウンが終わるまでマスは押せません。最初のタップで音を鳴らすので、ブラウザの自動再生の制限にもかかりません。見出しの🔊ボタンだけは背景の上に出ていて、スタート画面のあいだも切り替えられます。
517
+
518
+ ストップウォッチは見出しの「⏱ 0:00.0」で、0.1秒ごとに進みます。全部そろうと止まって赤くなり、結果画面に「クリアタイム」が100分の1秒まで出ます。`onPerfect` の `time` と `getState().time` でも、ミリ秒で受け取れます。リセットすると、スタート画面とストップウォッチの0に戻ります。2個以上のチェックをまとめて外して空にしたとき(`setChecked` や controlled の `checked`)も同じです。controlledのカードでは、親が空の `checked` を渡したときに戻るので、親がリセットを受け入れなければゲームは続きます。1個ずつタップで外して空にしたときは、ゲームの途中なのでストップウォッチは止まりません。
519
+
520
+ - 全部そろったあとにチェックを外して付け直しても、クリアタイムは最初にそろったときのままです(パーフェクトのボーナスと同じく、最初の1回だけを記録します)。
521
+
522
+ - タイムは保存しません。保存された進み具合から始めたときも、スタート画面から計り直します。全部そろったカードを開いたときは、スタート画面を出しません。
523
+ - デモのボタンは、カウントダウンを飛ばしてすぐに計り始めます。
524
+ - `ready={false}` のときはスタート画面を出さず、最初にチェックを付けたときにストップウォッチが動き出します。
525
+ - 文言は `readyTitle`・`readyPrompt`・`soundHint`・`vibrationHint`・`readyTap`・`readyGo`・`time`・`statTime` で変えられます([文言のキー](#文言のキー))。
526
+
527
+ ### 無限ビンゴモード
528
+
529
+ 見出しの「通常|∞ 無限」スイッチ(`mode` や `ref.setMode()` でも同じ)で、同じマスを使った終わりのないゲームに切り替わります。
530
+
531
+ - **切り替えの演出:** スイッチのつまみが弾んで移り、カード全体に暗い夜空が広がって、回る虹色のオーロラ、∞、INFINITEの文字、光の筋、きらめく星が約1.7秒出ます。専用の上昇音とAndroidの振動もあります。通常に戻すときは、黄色い背景にOKのスタンプが押されて、NORMALの文字が出ます。切り替え中もスイッチは前に出ていて、スタート画面のあいだも押せます。キーボードでは、スイッチにフォーカスして矢印キーで切り替えます。
532
+ - **遊び方:** スタート画面には∞のバッジとルールが出ます。マスを押して列をそろえると、いつもどおりBINGO!のお祝いが出て、約1秒後にその列のマスがくるりと裏返り、スタンプが消えます。続けてほかのマスを押していても大丈夫です(消える直前のマスは、`uncheck` のときも外せません)。BINGOの欄は、そのゲームでそろえた列の数を数え続けます。カードは全部そろうことがないので、パーフェクトはありません。ストップウォッチは、終えるかリセットするまで進み続けます。
533
+ - **🏁 FINISHで終える:** 無限ビンゴのあいだだけ、下の右に虹色の「🏁 FINISH」ボタンが出ます(ゲームが始まるまでは押せません)。押すと、消える直前の列を片づけてからストップウォッチを止め、虹色の枠の結果画面を出します。∞のメダル、「フィニッシュ!」、プレイタイム(100分の1秒まで)、SCORE・MAX COMBO・BINGO(そろえた列の数)のカウントアップに、ファンファーレ・紙吹雪・花火3発・Androidの振動が付きます。結果画面の「もう一度」で、新しい無限ビンゴのスタート画面に戻ります。「とじる」で閉じると、終えたときのカードが少し暗くなって残り、マスとシャッフルは押せません。リセット、モードの切り替え、「もう一度」で新しいゲームになります。`onFinish` で得点・最大コンボ・列の数・プレイタイムを受け取れ、`ref.finish()` でも終えられます。終えたあとのボーナス点はありません。
534
+ - **荷造りのチェックとは別です:** 無限ビンゴのマスは保存せず、`onChange` も呼びません。controlledのカードでも、無限ビンゴは親に聞かずにそのまま進みます。表示中の持ち物のチェックと並びは `getState().packing` で読めます。その間に `setChecked` や controlled の `checked` で届いた状態は、持ち物のチェックとして受け取り(保存先があれば保存し)、通常に戻ったときに表示します。シャッフルした並びも保存せず、通常に戻すと元の並びに戻ります。
535
+ - **切り替えるたびに新しいゲーム:** モードを切り替えると、得点・コンボ・ストップウォッチは0から始まり、スタート画面に戻ります(`ready={false}` のときは最初のタップから計ります)。通常に戻ったときの得点は、チェックした持ち物とそろった列から計算し直します([得点のルール](#得点のルール)の最後の項目と同じです)。全部そろったカードに戻ったときは、スタート画面を出しません。
536
+ - リセットボタンは、無限ビンゴの表示中は無限ビンゴを最初からやり直します。デモの「コンボ → パーフェクト」と「最初に戻す」は、無限ビンゴの表示中は隠れます。
537
+ - 選んだモードは保存しません。ページを開き直すと `mode`(既定は通常)から始まります。
538
+ - `prefers-reduced-motion` のときは、切り替えの演出はフェードだけになり、マスが裏返る動きもなくなります。
539
+
456
540
  ### 保存されるデータ
457
541
 
458
- チェックは、持ち物の数(4×4なら16個。FREEマスは含みません)だけの真偽値のJSON配列として、localStorageに保存されます。キーは `storageKey`、指定しなければ `packing-bingo:` で始まる、持ち物リストから決まるキーです。得点は保存しません。保存された進み具合は `defaultChecked` より優先されます。シャッフルした並びは、`packing-bingo-order:` のあとにそのキーを付けたキー(たとえば `packing-bingo-order:okinawa-2026`)に、マスごとの持ち物の番号のJSON配列として保存します。元の並びに戻したときも保存するので、`defaultOrder` を指定していても、次に開くと元の並びのままです。プライベートブラウズなどで保存できない環境では、保存せずにそのまま動きます。
542
+ チェックは、持ち物の数(4×4なら16個。FREEマスは含みません)だけの真偽値のJSON配列として、localStorageに保存されます。キーは `storageKey`、指定しなければ `packing-bingo:` で始まる、持ち物リストから決まるキーです。得点は保存しません。保存された進み具合は `defaultChecked` より優先されます。シャッフルした並びは、`packing-bingo-order:` のあとにそのキーを付けたキー(たとえば `packing-bingo-order:okinawa-2026`)に、マスごとの持ち物の番号のJSON配列として保存します。元の並びに戻したときも保存するので、`defaultOrder` を指定していても、次に開くと元の並びのままです。無限ビンゴのマスと並びは保存しません。プライベートブラウズなどで保存できない環境では、保存せずにそのまま動きます。
459
543
 
460
544
  ### アクセシビリティ
461
545
 
@@ -524,7 +608,9 @@ npm run typecheck
524
608
 
525
609
  ## English
526
610
 
527
- Packing Bingo is a React component that turns a school-trip packing checklist into a bingo card, with combos, fanfares and fireworks. Cards are 4×4 by default; `size` makes them anything from 2×2 to 10×10, and `free` puts a FREE square in the center of an odd-sized card. Progress is saved in each student's browser automatically. A 🔀 button moves the items to random squares (checks move with their items, and the arrangement is saved too; `shuffle={false}` hides it). Taps buzz on phones: through the Vibration API on Android, and with the system tap haptic of a `switch` checkbox on iPhone (iOS 18+); `vibrate={false}` turns both off. It has no runtime dependencies, makes no network requests, and is safe to render on the server (it ships with `"use client"`). It does not load its web fonts either: add the Google Fonts `<link>` from the quick start to the page's `<head>` to get them.
611
+ Packing Bingo is a React component that turns a school-trip packing checklist into a bingo card, with combos, fanfares and fireworks. Cards are 4×4 by default; `size` makes them anything from 2×2 to 10×10, and `free` puts a FREE square in the center of an odd-sized card. Progress is saved in each student's browser automatically. A 🔀 button moves the items to random squares (checks move with their items, and the arrangement is saved too; `shuffle={false}` hides it). Taps buzz on phones: through the Vibration API on Android, and with the system tap haptic of a `switch` checkbox on iPhone (iOS 18+); `vibrate={false}` turns both off. A stamped square stays stamped when tapped again; `uncheck` lets a second tap take the stamp off. A "Normal | ∞ Infinite" switch in the header (`modeSwitch={false}` hides it; `mode`, `onModeChange` and `ref.setMode()` control it) plays a full-card takeover with an aurora and ∞, and turns the card into an endless game: each completed line clears about a second after its bingo, so it can be filled and scored again. The infinite game is never saved and calls no `onChange`; switching back shows the packing card as it was. A 🏁 FINISH button (or `ref.finish()`) ends an infinite game with a results card (score, max combo, lines, time played; `onFinish`) and a "Play again" button. It has no runtime dependencies, makes no network requests, and is safe to render on the server (it ships with `"use client"`). It does not load its web fonts either: add the Google Fonts `<link>` from the quick start to the page's `<head>` to get them.
612
+
613
+ Version 0.6.0 adds the infinite mode and its FINISH results card, and replaces the 0.5.0 first-play dialog with a start screen: a see-through dark layer over the whole card (the same as the results screen) with an "Are you ready?" panel with the volume and Do Not Disturb reminders. A tap counts down 3, 2, 1 and starts a stopwatch that stops when the card is complete; `ready={false}` skips the screen and the first tap starts the clock, and `onPerfect` and `getState()` report `time` in milliseconds. A timed first clear earns a speed bonus with no ceiling. When upgrading from 0.5.0, note that a stamped square now stays stamped (`uncheck` brings back the old toggle), the status line under the card and the perfect card's body are empty by default, and `PackingBingoState`, `BingoEvent` and `PerfectEvent` have new required fields (update hand-written mocks). Android vibration strength is device-dependent and has not been verified on hardware. See the [v0.6.0 release notes](https://github.com/Jin-hiroo/packing-bingo/releases/tag/v0.6.0) and the [changelog](CHANGELOG.md).
528
614
 
529
615
  ```sh
530
616
  npm install packing-bingo