口コミ・レビューアプリ テンプレート 要件定義書
プロダクト名: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)
@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)
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のファーストビューに以下が同時に載ります。
- 大きなセリフの評点(
4.3)が数えあがっている - 部分塗りの星(4つ目が70%)
- 評価分布の横バー5本が積み上がっている
- 項目別スコア(味・接客・雰囲気・価格)
- カバー写真が発色している
営業中 / 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. データモデル
// 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
// 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つ。
- 条件②③が資産の持ち込みで完結する(Session 0とSession 1の一部)
- 固有画面が実質2枚(R2・R3)で、残りは既存構造の流用
- グラフをライブラリではなく自前にしたことで、調整が
motion.jsの定数編集で済む
かかるのは Session 1(ui/ 6点)とSession 2(R2のシーケンス)に集中します。特に Stars.jsx の部分塗りは、SVGの linearGradient を使う指定を最初に渡してください。ここを曖昧にすると「星を4つ塗って5つ目を薄く」といった近似実装が出てきて、作り直しになります。§5.2に実装方針まで書いてあるので、そのまま渡せば1回で通ります。

