# 口コミ・レビューアプリ テンプレート 要件定義書
## プロダクト名：Souzoh Reviews

> シリーズ5本目。**Portalで作った共通資産（画像取得スクリプト・SmartImage・motion.js）を初めて全面的に受け取る1本**です。
> 前作で追加した4条件（デザイン／画像自動取得／動き／第一印象）は本書でも必達要件として引き継ぎます。

---

## 1. 目的とゴール

| 項目 | 内容 |
|---|---|
| コンセプト | 地域のお店（飲食・美容・整体・ジムなど）の口コミを、**評価の中身が見える形で**集めるレビューアプリ |
| 一次目的 | 星の数だけでなく「なぜその評価なのか」が伝わるUIを見せる |
| 二次目的 | 店舗業の読者に「自社の口コミ基盤が欲しい」と思わせる。**Souzohの既存クライアント層（整体・ジム・ヨガ）と直結する題材** |
| 成功条件 | 評価分布バーと項目別スコアが動くところまで読者が到達する |
| 非ゴール | 実運用可能な口コミプラットフォーム。不正検知・通報対応・店舗審査 |

**なぜこの題材か**：レビューは**データの可視化と文章コンテンツが同居する**唯一の題材です。Portalが数字と写真の同居だったのに対し、こちらは「分布グラフ」「項目別スコア」「写真付きレビュー」が1画面に載る。しかも店舗業は読者の母数が最も大きい。

---

## 2. 引き継ぐ4条件

| 条件 | 対応章 | 本作での実現方法 |
|---|---|---|
| ① デザインにこだわる | §5 | **エディトリアル（雑誌）路線**。クリーム地＋セリフ見出し。前4本と完全に別方向 |
| ② ネットから画像を自動取得 | §6 | Portalの `fetch-images.mjs` を**そのまま流用**。マニフェストだけ差し替え |
| ③ 動きをつける | §7 | Portalの `motion.js` を継承 ＋ **本作固有の6種**を追加 |
| ④ 一目ですごい | §8 | 「星の部分塗り」と「分布バーの積み上がり」に集中投下 |

> ★ここが**シリーズ設計の効果が最も出る1本**です。②はスクリプトを持ってくるだけ、③は定数ファイルを持ってくるだけで条件を満たします。実質的な作り込みは①④に全振りできます。

---

## 3. スコープ

### 3.1 作るもの

| # | 画面 | 優先度 | 母艦／Portalとの関係 |
|---|---|---|---|
| R1 | 発見（ホーム） | A | 母艦S1を改修 |
| R2 | 店舗詳細 | **S** | **新規**。評価サマリーが本体 |
| R3 | レビュー投稿 | **S** | **新規**。星＋項目別＋写真 |
| R4 | 検索・絞り込み | A | Market K3を流用 |
| R5 | レビュー一覧・詳細 | A | 母艦S1/S2を改修 |
| R6 | レビュアー（投稿者）ページ | B | 母艦S5を改修 |
| R7 | マイページ | B | Market K6を改修 |
| R8 | 保存したお店 | C | 母艦の一覧を流用 |

### 3.2 作らないもの

- ❌ バックエンド・DB・認証・データ永続化
- ❌ 地図表示（**理由は§3.3**）
- ❌ 予約・決済・クーポン
- ❌ 通報・削除申請・店舗からの返信管理
- ❌ ダークモード、多言語、テストコード

### 3.3 地図を入れない判断

口コミアプリで地図は自然な発想ですが、外します。

- 地図ライブラリ（Leaflet / MapLibre）はバンドルが重く、タイル配信という**新しい外部依存**を1つ増やします。§6で外部依存を潰す設計にしているのと矛盾します
- タイルサービスは商用利用の条件が提供者ごとに違い、配布テンプレートに載せると利用者に判断を押し付けることになります
- デモとしての見せ場が薄い。地図は「動いて当たり前」で、驚きに繋がりません

代わりに**エリアチップ＋徒歩分数の表記**で位置情報を扱います。実装ゼロで体験は成立します。

### 3.4 デモデータの注意（Marketより厳しく扱う）

**実在の店舗名・チェーン名を一切使いません。** Marketでブランド名を排除したのと同じ理由に加えて、こちらは**低評価レビューを載せる**という事情があります。架空店舗に星2のレビューが付いているのは何の問題もありませんが、実在店舗名が入った瞬間に風評の話になります。

店名はすべて架空のものを使い、`README` と画面フッターに「掲載されている店舗・レビューはすべて架空のものです」を明記してください。

---

## 4. 技術構成

```
Vite + React 18（JSX）
Tailwind CSS v4
lucide-react
motion（motion.dev）… Portalと同じく使用範囲を限定（§7.4）
────────── Portalから持ち込む ──────────
scripts/fetch-images.mjs
src/components/ui/SmartImage.jsx
src/motion.js
────────── 本作で追加なし ──────────
recharts は使いません
```

**recharts を外す理由**：本作のグラフは「横バー5本の積み上がり」と「項目別スコアのバー」だけです。どちらもdivとCSSで15行、軸も目盛りもツールチップも要りません。**Portalとは逆の判断ですが、逆にすべき理由があります**——ここで自前実装にしておくと、バーの動きを`motion.js`の定数で完全に制御できます。ライブラリのアニメーションと自前の演出が混ざるほうが、調整の往復が増えます。

---

## 5. デザイン仕様 ★条件①

### 5.1 方向性

シリーズ5本の住み分けです。

| アプリ | 基調 | 書体 | 質感 |
|---|---|---|---|
| SNS | 明るいウォームグレー | サンセリフ | 余白 |
| Match | ディーププラム | サンセリフ | 舞台 |
| Market | 白 | サンセリフ（極太数字） | 陳列 |
| Portal | ディープインク | サンセリフ | ガラス／管制室 |
| **Reviews** | **クリーム（紙）** | **セリフ見出し** | **雑誌／エディトリアル** |

**5本目にして初めてセリフ体を使います。** レビューは文章が主役のコンテンツなので、書評誌や食のムック本のような佇まいが最も合います。加えて、4本がサンセリフで揃っている中に1本だけ紙質のセリフを入れると、**5枚並べたときの記憶に残る1枚**になります。

具体的には、クリーム地に**細い罫線と大きな見出し**。写真は角丸を極小（4px）にして紙面に貼った印象を作ります。差し色は墨に近い濃紺と、星のためのテラコッタ。

### 5.2 シグネチャー要素：星の部分塗り

評価 `3.7` を星4つ目まで塗るのではなく、**4つ目の星を70%だけ塗ります**。

```
★★★☆☆   ← やらない（3.7が3.0に見える）
★★★◐☆   ← これ。SVGのlinearGradientで70%の位置に色境界を置く
```

実装は `<linearGradient>` の `offset` を評価値から計算するだけ（約12行）。しかし**この1点でアプリの精度の印象が変わります**。星が並ぶ画面で部分塗りができているかどうかは、作り込みの水準がそのまま出る箇所です。ここを最初に作り切ってください。

### 5.3 デザイントークン（`src/theme.css`）

```css
@theme {
  /* ---- Base ---- */
  --color-bg:        #FBF8F1;  /* クリーム：わずかに黄み。紙の地 */
  --color-surface:   #FFFFFF;  /* 記事面・カード */
  --color-surface-2: #F3EEE3;  /* 入れ子・入力欄 */
  --color-ink:       #1A1712;  /* 墨に近い黒 */
  --color-ink-sub:   #6E675C;
  --color-ink-dim:   #A39B8D;
  --color-line:      #E2DACB;  /* 罫線。多用してよい */
  --color-line-bold: #1A1712;  /* 見出し下の太罫（2px） */

  /* ---- Accent ---- */
  --color-star:      #E0703A;  /* テラコッタ：星のみに使う */
  --color-accent:    #1F3A5F;  /* 濃紺：CTA・リンク */
  --color-good:      #4A7C59;  /* 高評価の分布バー */
  --color-mid:       #C4A24C;  /* 中評価 */
  --color-low:       #A85A50;  /* 低評価。赤にしない */

  /* ---- Type ---- */
  --font-display: "Shippori Mincho B1", "Playfair Display", serif;  /* 600 */
  --font-body:    "Noto Sans JP", sans-serif;                       /* 400/500 */
  --font-num:     "Bodoni Moda", "Noto Serif JP", serif;            /* ★評点はセリフ数字 */

  --text-hero:   clamp(30px, 3.6vw, 46px)/1.25;
  --text-score:  clamp(40px, 5vw, 64px)/1.0;   /* 店舗の総合評点 */
  --text-title:  21px/1.5;
  --text-body:   15px/1.9;   /* ★行間を全アプリ中いちばん広く。読み物なので */
  --text-label:  11px/1.3;

  /* ---- Space ---- */
  --space-1: 4px;  --space-2: 8px;  --space-3: 12px;
  --space-4: 16px; --space-6: 24px; --space-8: 32px; --space-12: 48px;

  /* ---- Shape ---- */
  --radius-card:  8px;    /* 全アプリ中いちばん小さい。紙に貼る印象 */
  --radius-photo: 4px;
  --radius-pill:  999px;
  --shadow-card:  none;             /* ★影は使わない。罫線で構造を作る */
  --shadow-pop:   0 16px 48px rgba(26,23,18,.18);  /* モーダルのみ */

  /* ---- Motion（Portalから継承） ---- */
  --ease-out:   cubic-bezier(.16,1,.3,1);
  --ease-inout: cubic-bezier(.65,0,.35,1);
  --dur-fast:   140ms;
  --dur-base:   240ms;
  --dur-slow:   520ms;
}
```

### 5.4 タイポグラフィの決め事

- **評点だけはセリフ数字**（`--font-num` = Bodoni系）。`4.3` が大きくセリフで組まれているだけで、雑誌の評価記事の顔になります
- 見出しは `--font-display`（明朝）。**本文は明朝にしない**——日本語の長文明朝は画面で読みにくく、レビュー本文はサンセリフのほうが機能します
- セクション見出しの下に **2pxの太罫**（`--color-line-bold`）。エディトリアル感の最大の供給源で、実装コストはゼロです
- 本文行間1.9。5本中で最も広く取ります

### 5.5 やらないこと（NGリスト）

- ❌ 星を絵文字（⭐）で描く。**必ずSVG**（部分塗りができないため）
- ❌ 低評価に赤（`#FF0000`系）を使う。`--color-low` の落ち着いた赤茶に留める
- ❌ カードへの影（`--shadow-card: none`）
- ❌ 本文の明朝組み
- ❌ 角丸の混在（3種のみ）
- ❌ 「★4.3」のようにテキストで星を書く箇所を残す

---

## 6. 画像の自動取得 ★条件②

### 6.1 方針

**Portalの `scripts/fetch-images.mjs` をそのままコピーします。** 差し替えるのは `imageManifest.js` だけです。仕様（ビルド時取得・4本並列・失敗は握りつぶす・`--force`・`public/images/` は `.gitignore`）はPortal §6.3と完全に同一なので、本書では繰り返しません。

表示側も `SmartImage.jsx` の3層フォールバック（ローカル → 外部seed URL → グラデーション）をそのまま使います。

### 6.2 本作固有の事情：写真がコンテンツそのもの

前4本では写真は装飾でしたが、**レビューアプリでは写真が投稿内容の一部**です。「写真付きレビュー」というラベルが機能するので、取得枚数を増やします。

| 用途 | 枚数 |
|---|---|
| 店舗のカバー写真 | 12 |
| 店舗の内観・商品写真（詳細のギャラリー） | 24（1店舗あたり2枚） |
| レビュー添付写真 | 18 |
| 特集バナー | 3 |
| **合計** | **57** |

Portalの18枚から3倍ですが、取得は並列4本で30秒程度です。ここは惜しまないでください。**写真の枚数がそのまま「賑わっている感」になります。**

### 6.3 レビュアーのアバター

Portal §6.2と同じ判断で、**実在人物の写真は使いません**。ID由来のグラデーション＋頭文字です。

本作では特に重要で、**架空の低評価レビューに実在の人物の顔が添えられる**のは避けるべき状態です。

### 6.4 マニフェスト抜粋（`src/data/imageManifest.js`）

```js
export default [
  // 店舗カバー（12）
  { seed:"shop-cafe-hazama",   w:1400, h:900, use:"喫茶 はざま" },
  { seed:"shop-ramen-kiyo",    w:1400, h:900, use:"らーめん 清雅" },
  { seed:"shop-seitai-nagi",   w:1400, h:900, use:"整体院 なぎ" },
  { seed:"shop-gym-forge",     w:1400, h:900, use:"FORGE 24" },
  { seed:"shop-yoga-tsumugi",  w:1400, h:900, use:"ヨガスタジオ つむぎ" },
  { seed:"shop-bakery-kona",   w:1400, h:900, use:"パン工房 こな" },
  { seed:"shop-izakaya-tobi",  w:1400, h:900, use:"炭火酒場 とび" },
  { seed:"shop-salon-lume",    w:1400, h:900, use:"hair salon LUME" },
  { seed:"shop-curry-mame",    w:1400, h:900, use:"スパイスカレー まめ" },
  { seed:"shop-sento-yumoto",  w:1400, h:900, use:"銭湯 湯もと" },
  { seed:"shop-book-yoruba",   w:1400, h:900, use:"夜間書店 よるば" },
  { seed:"shop-sushi-isana",   w:1400, h:900, use:"鮨 いさな" },

  // 店舗ギャラリー（24）… {seed}-a / {seed}-b を各店に
  // レビュー添付（18）… rv-01 〜 rv-18、比率は 4:3 と 1:1 を混在
  // 特集バナー（3）
  { seed:"feature-morning", w:1600, h:600, use:"特集：朝から開いている店" },
  { seed:"feature-solo",    w:1600, h:600, use:"特集：ひとりで入りやすい店" },
  { seed:"feature-rain",    w:1600, h:600, use:"特集：雨の日に行きたい店" },
];
```

---

## 7. 動き ★条件③

### 7.1 Portalからの継承

`src/motion.js` をコピーし、以下をそのまま使います。

| # | 名称 | 本作での適用先 |
|---|---|---|
| 1 | 段差入場 | R1の店舗カード（60ms差） |
| 4 | スクロール連動リビール | R2のレビュー一覧、R5 |
| 5 | ホバー浮上 | 店舗カード（影がないので**枠色と写真の微拡大**で表現） |
| 6 | View Transitions 画面遷移 | 全ページ |
| 7 | 共有要素モーフ | 店舗カード → R2（カバー写真と店名が連続移動） |
| 9 | リスト入れ替え | R4の絞り込み結果 |
| 14 | トースト | 全体 |

### 7.2 本作固有の動き（6種）

| # | 名称 | 対象 | 仕様 | reduced時 |
|---|---|---|---|---|
| A | **星の充填** | R2の総合評価 | 5つの星が左から順に塗られる。1つあたり110ms、4つ目は70%で止まる | 完成形を即表示 |
| B | **分布バーの積み上がり** | R2の評価分布 | 星5〜1の横バーが**下から上の順に**幅0→実値、各180ms／80ms差 | 完成形 |
| C | 評点カウントアップ | R2の `4.3` | 0.0→4.3、小数第1位まで、900ms。`--font-num` | 即表示 |
| D | 項目別スコアバー | R2の項目別 | 4項目のバーが左から伸びる、各400ms／60ms差 | 完成形 |
| E | **星の入力** | R3 | ホバー／ドラッグで星が追従。確定時に選んだ星が1度だけ12%拡大→戻る（180ms） | 拡大なし |
| F | 写真ライトボックス | R2ギャラリー・R5 | サムネから全画面へ**共有要素モーフ**で拡大（`--dur-base`） | フェード |

> **BとCを同時に走らせます。** 評点が0.0から4.3へ数えあがる横で、分布バーが下から積み上がる。この0.9秒が本作の"すごい"の中心です（Portalのカウントアップ＋スパークラインと同じ設計思想）。

### 7.3 R2 到達時のシーケンス

```
0ms     カバー写真がモーフ着地
120ms   店名・エリア・営業状態が fade-in
200ms   評点カウントアップ開始         ← C
        同時に星の充填開始             ← A
        同時に分布バーが下から積み上がり ← B
1100ms  上記3つが着地
1200ms  項目別スコアバーが伸びる        ← D
1600ms  完了。以降は静止
```

### 7.4 motion ライブラリの使用範囲（限定）

- ✅ #7 共有要素モーフ（店舗カード → R2）
- ✅ F 写真ライトボックス
- ✅ #9 リスト入れ替え
- ❌ 上記以外はCSS / WAAPI / View Transitions

**A〜Dはすべて自前**です（§4の通り）。CSS変数 `--w` を JS から書き換えて `transition: width` させるだけで、`motion.js` の定数と完全に同期します。

---

## 8. 「一目ですごい」の設計 ★条件④

### 8.1 勝負するのはR2（店舗詳細）

Portalはホームが勝負でしたが、本作は**店舗詳細**です。レビューアプリの本質はホームの一覧ではなく「1店舗を深く見る」体験にあり、記事のスクショもここから撮ります。

R2のファーストビューに以下が同時に載ります。

1. 大きなセリフの**評点**（`4.3`）が数えあがっている
2. **部分塗りの星**（4つ目が70%）
3. **評価分布**の横バー5本が積み上がっている
4. **項目別スコア**（味・接客・雰囲気・価格）
5. カバー写真が発色している
6. `営業中 / 22:00まで` のライブ表示

> ★**「分布」が載っていることが決定的です。** 平均点だけのレビューUIは無数にありますが、分布が見えると「星4.3の中身は5が多数＋2がわずか」という情報が伝わります。ここが本作の主張であり、そのまま見た目の情報量になります。

### 8.2 R2 のレイアウト（PC 1120px時）

```
┌──────────────────────────────────────────────┐
│ カバー写真（1400×900をトリミング、高さ320px）    │
│  ← 下端に --color-bg へのグラデーション         │
├────────────────────┬─────────────────────────┤
│ 店名（--font-display）│  評点 4.3（--text-score）  │
│ ジャンル・エリア・徒歩5分│  ★★★★◐               │
│ 営業中 / 22:00まで    │  レビュー 128件            │
│                     ├─────────────────────────┤
│                     │  ★5 ████████████ 78      │
│                     │  ★4 ██████ 31            │
│                     │  ★3 ███ 12               │
│                     │  ★2 █ 5                  │
│                     │  ★1 █ 2                  │
├────────────────────┴─────────────────────────┤
│ 項目別　味 4.5 / 接客 4.2 / 雰囲気 4.6 / 価格 3.8 │
├──────────────────────────────────────────────┤
│ ギャラリー（横スクロール・2枚 + レビュー写真）      │
├──────────────────────────────────────────────┤
│ レビュー一覧（並び替え・絞り込み付き）             │
└──────────────────────────────────────────────┘
```

モバイルは縦積み。評点ブロックは**店名の直下に横並び**（評点｜星｜件数）。

### 8.3 「参考になった」の扱い

各レビューに `参考になった 12` ボタンを置きます。**押すと数字がスライドカウントアップし、そのレビューが並び替え「参考になった順」で1つ上がるところまで動かします。**

小さい実装ですが、**押した結果が並び順に反映される**のはデモとして効きます。押して終わりのボタンとは印象が違います。

---

## 9. 画面仕様

### R1. 発見（ホーム）

- ヘッダー：検索バー ＋ エリアチップ（`蔵前` `浅草橋` `両国` `門前仲町` `清澄白河`）
- 特集バナー3枚（横スクロール、`feature-*` 画像）
- `いま評価が伸びている店` … 6件のカード
- `写真が多い店` … 6件
- ジャンルチップ：`カフェ` `ラーメン` `居酒屋` `整体` `ジム` `ヨガ` `美容室` `パン` `銭湯` `書店`
- 店舗カード：カバー写真／店名／評点＋星／件数／ジャンル・エリア・徒歩分数／保存アイコン

### R2. 店舗詳細 ★本体

§8.2の構成。加えて：

- **営業状態の判定**：`data` の営業時間と現在時刻を比較して `営業中 / 22:00まで` `本日休業` `まもなく閉店` を出し分け。実装は10行、体験の解像度が上がります
- 基本情報：住所（架空）・電話（`03-0000-0000` 形式のダミー）・定休日・支払い方法・席数
- ギャラリー：店舗写真2枚 ＋ レビュー添付写真。タップでライトボックス（F）
- レビュー一覧：並び替え `新しい順` / `参考になった順` / `評価が高い順` / `評価が低い順`、絞り込み `写真あり` / `星で絞る`
- 下部固定：`保存` ／ `レビューを書く`（濃紺）

### R3. レビュー投稿 ★固有画面

全画面フォーム。上から：

| # | 項目 | 仕様 |
|---|---|---|
| 1 | 総合評価 | **大きな星5つ**。タップ／ドラッグで0.5刻み（動きE）。未選択では投稿不可 |
| 2 | 項目別 | `味` `接客` `雰囲気` `価格` の4つを小さい星で。**任意**（未入力可） |
| 3 | 訪問時期 | 3択チップ：`1か月以内` `3か月以内` `半年以上前` |
| 4 | 訪問シーン | 4択チップ：`ひとり` `友人と` `家族と` `仕事で` |
| 5 | 本文 | 20〜1000字。**20字未満は投稿不可**＋「もう少し詳しく教えてください」 |
| 6 | 写真 | 4枠。ダミー画像ピッカー（6枚から選択） |

**リアルタイムのプレビュー**：フォーム下部に、投稿後の見え方をそのまま表示します。星・本文・写真が入力に追従して変化する。**これがR3の見せ場**で、実装は同じコンポーネントを再利用するだけです。

投稿 → R2のレビュー一覧先頭に挿入 ＋ **評点・分布バー・件数が再計算されてアニメーションで更新**（B・Cが再実行）＋ トースト。

> ★**自分の投稿で分布バーが動くのが、このアプリで最も気持ちいい瞬間です。** 必ず実装してください。

### R4. 検索・絞り込み

- 検索：店名・ジャンル・レビュー本文を部分一致
- 絞り込みパネル：`ジャンル` `エリア` `評点（3.0以上／3.5以上／4.0以上）` `写真あり` `営業中のみ`
- 並び替え：`評点が高い順` / `レビューが多い順` / `新着順`
- 適用中の条件をチップ表示、×で個別解除
- 空状態：「条件に合うお店がありません」＋ `評点の条件を外す`

### R5. レビュー一覧・詳細

- 全店舗横断のレビューフィード（`みんなの口コミ` タブ）
- レビューカード：レビュアー・星・訪問シーン・本文（3行で省略）・写真サムネ・`参考になった`
- 詳細：本文全文 ＋ 写真ギャラリー ＋ 店舗への導線

### R6. レビュアーページ

- アバター・表示名・投稿数・獲得した「参考になった」総数
- **称号バッジ**：投稿数に応じて `はじめて` `常連` `達人`（3段階のみ。細分化しない）
- 投稿レビュー一覧

### R7. マイページ

- タブ：`書いたレビュー` / `保存したお店` / `下書き`
- サマリー：`レビュー 8件` `参考になった 47` `訪問した店 12`

---

## 10. データモデル

```js
// shop
{ id:"sh01", name:"喫茶 はざま", genre:"カフェ",
  area:"蔵前", walkMin:5,
  cover:{ seed:"shop-cafe-hazama", w:1400, h:900 },
  gallery:[{seed:"shop-cafe-hazama-a"},{seed:"shop-cafe-hazama-b"}],
  hours:{ open:"08:00", close:"22:00", closedOn:["火"] },
  tel:"03-0000-0001", address:"東京都台東区蔵前0-0-0",
  payment:["現金","クレジットカード","QR決済"], seats:18,
  score:4.3, reviewCount:128,
  distribution:{ 5:78, 4:31, 3:12, 2:5, 1:2 },
  aspects:{ 味:4.5, 接客:4.2, 雰囲気:4.6, 価格:3.8 },
  saved:false }

// review
{ id:"rv01", shopId:"sh01", reviewerId:"u03",
  score:5, aspects:{味:5,接客:4,雰囲気:5,価格:4},
  visited:"1か月以内", scene:"ひとり",
  body:"...", photos:[{seed:"rv-01"}],
  helpful:24, marked:false, createdAt:"..." }

// reviewer
{ id:"u03", name:"あさひ", reviewCount:31, helpfulTotal:412,
  badge:"達人", avatarSeed:"u03" }

// 評点の再計算（投稿時にこれを走らせる）
score = Σ(星 × 件数) / 総件数  → 小数第1位で四捨五入
distribution[n] += 1
aspects[k] = 未入力を除外した平均
```

**`score` をデータに持ちつつ再計算もする**理由：初期表示は静的な値で即座に出し、投稿時のみ再計算します。全レビューから毎回集計すると初期表示が遅くなり、§7.3のシーケンスが崩れます。

---

## 11. デモデータ

### 11.1 店舗（12件）

| id | 店名 | ジャンル | エリア | 徒歩 | 評点 | 件数 | 分布 5/4/3/2/1 |
|---|---|---|---|---|---|---|---|
| sh01 | 喫茶 はざま | カフェ | 蔵前 | 5分 | 4.3 | 128 | 78/31/12/5/2 |
| sh02 | らーめん 清雅 | ラーメン | 浅草橋 | 3分 | 4.6 | 341 | 264/52/18/5/2 |
| sh03 | 整体院 なぎ | 整体 | 蔵前 | 7分 | 4.8 | 86 | 76/8/2/0/0 |
| sh04 | FORGE 24 | ジム | 両国 | 4分 | 3.9 | 212 | 92/64/33/15/8 |
| sh05 | ヨガスタジオ つむぎ | ヨガ | 清澄白河 | 6分 | 4.7 | 94 | 78/12/3/1/0 |
| sh06 | パン工房 こな | パン | 蔵前 | 2分 | 4.4 | 156 | 96/40/14/4/2 |
| sh07 | 炭火酒場 とび | 居酒屋 | 門前仲町 | 5分 | 4.1 | 267 | 142/72/33/13/7 |
| sh08 | hair salon LUME | 美容室 | 両国 | 8分 | 4.5 | 73 | 51/16/4/1/1 |
| sh09 | スパイスカレー まめ | カレー | 浅草橋 | 6分 | 4.2 | 189 | 108/48/22/8/3 |
| sh10 | 銭湯 湯もと | 銭湯 | 蔵前 | 9分 | 4.0 | 132 | 62/40/20/7/3 |
| sh11 | 夜間書店 よるば | 書店 | 清澄白河 | 11分 | 4.6 | 58 | 44/10/3/1/0 |
| sh12 | 鮨 いさな | 寿司 | 門前仲町 | 3分 | 3.6 | 97 | 34/26/21/10/6 |

> ★**評点は意図的にばらけさせています。** 全店が4.5以上のデモは嘘くさく、分布バーの見せ場も消えます。`sh12`（3.6）と `sh04`（3.9）が入っていることで、分布バーが「低評価も含めて可視化する」機能だと伝わります。

### 11.2 レビュアー（10名）

| id | 表示名 | 投稿数 | 参考になった総数 | 称号 |
|---|---|---|---|---|
| u00 | あなた | 8 | 47 | 常連 |
| u01 | くらた | 142 | 2,380 | 達人 |
| u02 | 三日月 | 87 | 1,104 | 達人 |
| u03 | あさひ | 31 | 412 | 常連 |
| u04 | ノブ | 19 | 233 | 常連 |
| u05 | まめだ | 64 | 891 | 達人 |
| u06 | 陽子 | 12 | 96 | 常連 |
| u07 | りく | 4 | 21 | はじめて |
| u08 | 佐伯 | 55 | 702 | 達人 |
| u09 | ぽち | 2 | 8 | はじめて |

### 11.3 レビュー（32件・抜粋）

**高評価の例**

| 店舗 | 投稿者 | 星 | シーン | 本文 |
|---|---|---|---|---|
| sh01 | u03 | 5 | ひとり | 平日の昼過ぎに行くと、だいたい席が空いています。豆の説明を聞くと長くなるので、時間があるときに行くのがおすすめです |
| sh02 | u01 | 5 | ひとり | スープが変わりましたね。前より塩味が落ち着いて、最後まで飲めるようになりました |
| sh03 | u06 | 5 | ひとり | 3回通って、朝起きたときの首の痛みがなくなりました。その日だけ楽になる施術とは違うと思います |
| sh05 | u02 | 5 | ひとり | 初心者クラスでも、できないポーズを無理にやらせないのが良いです |
| sh11 | u08 | 5 | ひとり | 22時以降の書店という時点で価値があります。品揃えは偏っていますが、その偏りが良い |

**中〜低評価の例**（これがあることでデモの説得力が変わります）

| 店舗 | 投稿者 | 星 | シーン | 本文 |
|---|---|---|---|---|
| sh04 | u04 | 3 | ひとり | マシンは充実していますが、19〜21時は待ちが発生します。時間をずらせる人向けです |
| sh12 | u05 | 3 | 仕事で | 味は良かったです。ただこの価格帯なら、もう少し落ち着いて食べられる席の作りだと嬉しい |
| sh07 | u09 | 2 | 友人と | 金曜の夜に予約なしで行ったのが悪かったのですが、注文が通るまで30分かかりました |
| sh10 | u07 | 3 | ひとり | お湯は良いです。ロッカーが古いのと、ドライヤーが有料なのは知っておくと良いかも |
| sh04 | u06 | 4 | ひとり | 24時間なのが一番の価値。設備は値段なりですが、そこに文句を言う施設ではないと思います |

> **低評価レビューの書き方に注意してください。** 攻撃的な文面ではなく「事実 ＋ 誰に向くか」の形にしています。配布テンプレートのデモデータとして適切であると同時に、実際に参考になる口コミの見本にもなります。

### 11.4 特集（3件）

| タイトル | 説明 | 掲載店 |
|---|---|---|
| 朝から開いている店 | 8時前に灯りがついている5軒 | sh01 sh06 sh10 |
| ひとりで入りやすい店 | カウンターがある、静かに過ごせる | sh01 sh02 sh11 sh09 |
| 雨の日に行きたい店 | 駅から近い、長居できる | sh06 sh08 sh11 |

---

## 12. ディレクトリ構成

```
souzoh-reviews/
├── scripts/
│   └── fetch-images.mjs        ← Portalからコピー（無変更）
├── public/images/              ← .gitignore対象
├── src/
│   ├── theme.css               ★差し替え（§5.3）
│   ├── app.config.js           ★差し替え（下記）
│   ├── motion.js               ← Portalからコピー ＋ A〜Fを追記
│   ├── data/
│   │   ├── shops.js  reviews.js  reviewers.js
│   │   ├── features.js  imageManifest.js
│   ├── components/ui/          ← 母艦＋Portalからコピー
│   │   ├── SmartImage.jsx      ← Portalから（無変更）
│   │   ├── Stars.jsx           ★新規：部分塗り（§5.2）。最初に作る
│   │   ├── StarInput.jsx       ★新規：0.5刻み入力（動きE）
│   │   ├── ScoreBig.jsx        ★新規：セリフ数字＋カウントアップ
│   │   ├── Distribution.jsx    ★新規：分布バー5本（動きB）
│   │   ├── AspectBars.jsx      ★新規：項目別（動きD）
│   │   └── Lightbox.jsx        ★新規：写真拡大（動きF）
│   ├── components/shell/       ← 母艦からコピー
│   ├── features/
│   │   ├── discover/  shop/  review-form/  search/
│   │   ├── reviews/  reviewer/  my-page/
│   ├── store.js
│   └── main.jsx
└── README.md
```

```js
// app.config.js
export default {
  appName: "Souzoh Reviews",
  tagline: "星の数より、その中身を",
  layout: { maxWidth: 1120 },
  nav: [
    { key:"discover", label:"さがす",     icon:"Compass" },
    { key:"reviews",  label:"口コミ",     icon:"MessageSquareQuote" },
    { key:"write",    label:"投稿",       icon:"PenLine" },
    { key:"saved",    label:"保存",       icon:"Bookmark" },
    { key:"mypage",   label:"マイページ", icon:"User" },
  ],
  labels: {
    listTitle: "近くのお店",
    itemNoun: "お店",
    createCta: "レビューを書く",
    primaryAction: "保存",
  },
};
```

> Marketと同じく、**タブバーの中央を「投稿」**にしています。口コミアプリで最も価値の高い行動は投稿です。

---

## 13. 実装手順（Claude Codeへの投げ方）

**4分割。** Portal・Marketより1つ少ないのは、§2の通り条件②③を資産の持ち込みで満たせるためです。

| Session | やること | 冒頭で読ませるもの |
|---|---|---|
| 0 | 母艦を複製 → Portalから `fetch-images.mjs` `SmartImage.jsx` `motion.js` をコピー → `theme.css` `app.config.js` `data/` を差し替え → **`npm run fetch:images` で57枚取得** → 画面は触らずビルドを通す | §5.3・§6・§11・§12 |
| 1 | `ui/` の6点（Stars / StarInput / ScoreBig / Distribution / AspectBars / Lightbox）＋ `motion.js` にA〜F追記。**画面はまだ作らない** | §5.2・§7.2・§10 |
| 2 | R2 店舗詳細（§7.3のシーケンスまで完成させる） | §7.3・§8.2・§9(R2) |
| 3 | R3 レビュー投稿（プレビュー＋分布の再計算）＋ R1 発見 | §9(R1,R3)・§10 |
| 4 | R4検索 ＋ R5一覧 ＋ R6レビュアー ＋ R7マイページ ＋ 仕上げ | §9(R4〜R7)・§14 |

**Session 1で `ui/` を先に作り切ることが本作の要点です。** 星コンポーネントは全画面に出てきます。ここが後回しになると、画面ごとに微妙に違う星が量産され、統一の手戻りが発生します（Marketの `Price` と同じ構図です）。

**Session 2 の指示文テンプレ**：

```
添付の要件定義書 §7.3・§8.2・§9(R2) を実装してください。
src/components/ui/ の Stars / ScoreBig / Distribution / AspectBars / SmartImage と
src/motion.js は完成済みです。新規作成せず import して使ってください。
§7.3 のシーケンス（0ms〜1600ms）をそのままの順序・時間で再現してください。
アニメーションの時間・遅延は src/motion.js の定数のみを参照し、
JSX に数値を直接書かないでください。
色・余白・角丸は src/theme.css の変数のみを使ってください。
実装前に、作成・変更するファイルの一覧だけ先に提示してください。
```

---

## 14. 受け入れ基準

**条件①（デザイン）**
- [ ] 星がSVGで描かれ、**小数評価が部分塗りで表現されている**（4.3なら4つ目が30%）
- [ ] 評点がセリフ体（`--font-num`）で組まれている
- [ ] セクション見出しの下に2pxの太罫がある
- [ ] カードに影が使われていない
- [ ] 生の色コード・生のpx値がJSXに1つも無い
- [ ] §5.5のNGリストに1つも該当しない

**条件②（画像）**
- [ ] `npm run fetch:images` で57枚が取得される
- [ ] `public/images/` を空にしても壊れた画像アイコンが出ない
- [ ] レビュアーのアバターに実在人物の写真が使われていない
- [ ] `public/images/` が `.gitignore` に入っている

**条件③（動き）**
- [ ] §7.2のA〜F、§7.1の継承分がすべて動作する
- [ ] §7.3のシーケンスが順序・時間通りに走る
- [ ] `prefers-reduced-motion: reduce` で「停止」列がすべて止まる
- [ ] View Transitions 非対応でも遷移が壊れない

**条件④（第一印象）**
- [ ] R2到達時に、評点カウントアップ・星の充填・分布バーの積み上がりが**同時に走る**
- [ ] R2ファーストビューに §8.1 の6要素が同時に見える

**機能**
- [ ] R3で総合評価未選択・本文20字未満のとき投稿できない
- [ ] R3のプレビューが入力にリアルタイム追従する
- [ ] **投稿すると評点・件数・分布バーが再計算され、アニメーションで更新される**
- [ ] `参考になった` を押すと数字が増え、「参考になった順」で順位が上がる
- [ ] 営業時間と現在時刻から `営業中 / 本日休業` が出し分けられる
- [ ] 絞り込みの条件チップが個別に解除できる
- [ ] 実在の店舗名・チェーン名がデータに1つも含まれていない
- [ ] フッターに「掲載内容はすべて架空」の明記がある

**共通**
- [ ] 390px / 1024px / 1120px の3幅で崩れない
- [ ] `npm run build` が警告なく通り、Cloudflare Pagesで表示される

---

## 15. シリーズ資産の状況（5本目終了時点）

| 資産 | 出所 | 本作での扱い |
|---|---|---|
| `components/ui/` 8点 | 母艦SNS | そのまま |
| `components/shell/` | 母艦SNS（+Market の maxWidth） | そのまま |
| `Price.jsx` | Market | 未使用 |
| `Carousel.jsx` | Market | ギャラリーに流用 |
| `Stepper.jsx` | Market | 未使用 |
| `SmartImage.jsx` | **Portal** | そのまま |
| `fetch-images.mjs` | **Portal** | そのまま |
| `motion.js` | **Portal** | コピー＋追記 |
| `Stars` / `Distribution` 系6点 | **本作** | → 学習アプリの評価・進捗表示に転用可 |

**5本目にして、新規実装が `ui/` 6点と画面7枚まで縮みました。** 母艦方式の投資回収がはっきり出ている状態です。

---

## 16. 補足：コスト感

シリーズ中で**最も軽い1本**になる見込みです。理由は3つ。

1. 条件②③が資産の持ち込みで完結する（Session 0とSession 1の一部）
2. 固有画面が実質2枚（R2・R3）で、残りは既存構造の流用
3. グラフをライブラリではなく自前にしたことで、調整が`motion.js`の定数編集で済む

かかるのは **Session 1（`ui/` 6点）とSession 2（R2のシーケンス）に集中**します。特に `Stars.jsx` の部分塗りは、SVGの `linearGradient` を使う指定を最初に渡してください。ここを曖昧にすると「星を4つ塗って5つ目を薄く」といった近似実装が出てきて、作り直しになります。§5.2に実装方針まで書いてあるので、そのまま渡せば1回で通ります。
