# SNSアプリ テンプレート 要件定義書
## プロダクト名：Souzoh SNS

> 本書は「Claude CodeでSNSアプリを開発する方法と費用｜そのまま使えるテンプレート配布【2026年】」記事に添付するデモアプリの要件定義書です。
> 同時に、後続4本（マッチング／フリマ／デリバリー／学習）の**母艦テンプレート**を兼ねます。

---

## 1. 目的とゴール

| 項目 | 内容 |
|---|---|
| 一次目的 | 記事読者が**URLを開いた瞬間に触れる**SNSデモ。スクショが記事のビジュアル素材になる |
| 二次目的 | 後続4アプリの土台。`theme.css` / `app.config.js` / `data/` の3点差し替えでリスキンできる構造にする |
| 成功条件 | 「これ無料で配ってるの？」と思われる見た目 ＋ 3クリック以内に"動く"実感 |
| 非ゴール | 実運用できるSNS。ユーザー登録もデータ永続化もしない |

---

## 2. スコープ

### 2.1 作るもの

| # | 画面 | 優先度 | 概要 |
|---|---|---|---|
| S1 | タイムライン | A | おすすめ／フォロー中タブ、投稿カード一覧、無限スクロール（mock追加） |
| S2 | 投稿詳細 | A | 本文＋返信スレッド、返信フォーム |
| S3 | 探す | A | 人気タグ、ユーザー検索、画像グリッド |
| S4 | 通知 | B | いいね／返信／フォロー／メンションの4種 |
| S5 | プロフィール | A | ヘッダー、フォローボタン、投稿／いいねタブ |
| S6 | 投稿作成 | A | ボトムシート型モーダル。本文・画像・タグ |
| S7 | DM | B | スレッド一覧 → 会話ビュー（1画面内で切替） |

### 2.2 作らないもの（明示的に除外）

- ❌ バックエンド・DB・認証（Supabase等は**一切使わない**）
- ❌ 画像アップロードの実保存
- ❌ 状態の永続化（localStorage不使用。リロードで初期状態に戻る＝デモとして正しい挙動）
- ❌ SSR / SEO対応 / OGP動的生成
- ❌ ダークモード、多言語、テストコード、CI
- ❌ 本格的なブレークポイント設計（後述の2段階のみ）

> **除外理由**：この4行が「重くしない」の実体です。ここを守れば1アプリあたりの生成量が読めます。

---

## 3. 技術構成

```
Vite + React 18（JSX / TypeScriptは使わない）
Tailwind CSS v4（@theme でCSS変数を一元管理）
lucide-react（アイコン）
Framer Motion は使わない → CSS transition + Web Animations API で足りる
デプロイ：Cloudflare Pages
```

**TypeScriptを外す理由**：型エラーの往復がClaude Codeの消費で最も無駄が出る箇所です。デモアプリに型安全性のリターンはほぼありません。

**Framer Motionを外す理由**：バンドルサイズと、アニメ指定の試行錯誤が往復を生むため。必要な動きは10行のCSSで出ます。

---

## 4. デザイン仕様

### 4.1 方向性

Xの黒でもInstagramの白でもない、**「余白の広い明るいUI ＋ インディゴ1色の強い差し色」**。
カードは枠線を使わず、**背景色の段差と大きめの角丸**だけで階層を作ります。SNSにありがちな情報密度を意図的に下げ、1投稿を大きく見せる。スクショ映えを最優先した設計です。

**シグネチャー要素**：いいねボタン。押した瞬間にインディゴの円が弾けて数字がスライドカウントアップする。ここだけに手を掛け、他は静かに保ちます。

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

```css
@theme {
  /* ---- Color ---- */
  --color-bg:        #F4F4F1;  /* ページ背景（ごく淡いウォームグレー） */
  --color-surface:   #FFFFFF;  /* カード面 */
  --color-surface-2: #EDEDE8;  /* 入力欄・非活性面 */
  --color-ink:       #17181C;  /* 本文 */
  --color-ink-sub:   #74757C;  /* メタ情報・タイムスタンプ */
  --color-line:      #E2E2DC;  /* 区切り線（極力使わない） */
  --color-accent:    #4A3AFF;  /* エレクトリックインディゴ：CTA・アクティブ */
  --color-accent-sub:#00BFA5;  /* ミント：通知バッジ・オンライン表示 */
  --color-danger:    #E5484D;

  /* ---- Type ---- */
  --font-display: "Outfit", "Zen Kaku Gothic New", sans-serif;  /* 700/600 */
  --font-body:    "Noto Sans JP", sans-serif;                   /* 400/500 */
  --font-num:     "DM Mono", monospace;                         /* カウント類 */

  /* ---- Scale ---- */
  --text-display: 28px/1.3;
  --text-title:   20px/1.4;
  --text-body:    15px/1.75;   /* 日本語なので行間広め */
  --text-meta:    12px/1.4;

  /* ---- Space（4の倍数で統一） ---- */
  --space-1: 4px;  --space-2: 8px;  --space-3: 12px;
  --space-4: 16px; --space-6: 24px; --space-8: 32px;

  /* ---- Shape ---- */
  --radius-card:  20px;
  --radius-input: 14px;
  --radius-pill:  999px;
  --shadow-card:  0 1px 2px rgba(23,24,28,.04), 0 8px 24px rgba(23,24,28,.06);
  --shadow-pop:   0 12px 40px rgba(23,24,28,.14);
}
```

> **リスキン時はこのブロックの色とフォントだけ書き換えます。**
> 例）フリマ＝`--color-accent: #0A8F3C` ＋ `--color-bg: #FFFFFF`、デリバリー＝`--color-accent: #FF6A13` ＋ `--color-bg: #101828`。

### 4.3 レイアウト

| | 仕様 |
|---|---|
| モバイル（〜768px） | 全幅。下部固定タブバー（5アイコン）＋中央の投稿FAB |
| PC（769px〜） | 中央カラム **600px固定**、左に幅240pxの縦ナビ。右カラムは作らない |
| 画面遷移 | 200ms のフェード＋4px上方向スライド |
| `prefers-reduced-motion` | 全アニメーションを無効化（必須） |

### 4.4 画像の扱い（アセット0で成立させる）

投稿画像・アバターは**実ファイルを用意しません**。

- **アバター**：ユーザーIDから決定的に生成したグラデーション円 ＋ 名前の頭1文字（白抜き、`--font-display`）
- **投稿画像**：`image.seed` 文字列をハッシュ化して2色を決め、45°グラデ ＋ 微細ノイズのSVGを描画。指定アスペクト比で表示
- **差し替え**：`public/images/{seed}.jpg` を置くと自動で実写に切り替わる

```jsx
<img src={`/images/${seed}.jpg`} alt=""
     onError={(e) => { e.currentTarget.style.display = 'none' }} />
{/* 下層にグラデーションのフォールバックが常時敷かれている */}
```

> あとから実写を10枚放り込むだけで記事のスクショが一段良くなります。最初はやらなくて構いません。

---

## 5. 画面仕様

### S1. タイムライン

- ヘッダー：ロゴ（左）／検索アイコン・DMアイコン（右）。スクロールで影が付く
- タブ：`おすすめ` / `フォロー中`（下線インジケータがスライド）
- 投稿カード：アバター・表示名・@ID・相対時刻／本文／画像（あれば）／タグチップ／アクション行
- アクション行：💬 返信数／🔁 リポスト数／♡ いいね数／🔖 保存
- 無限スクロール：末尾400pxで既存mockをシャッフル追加（最大3回、以降は「これで全部です」）
- 初回ロード：スケルトン600ms

### S2. 投稿詳細

- 上部に元投稿を大きく（本文18px、絶対時刻表示）
- 統計行：`リポスト 12 ・ いいね 84`
- 返信スレッド：左に2pxのインデントライン
- 下部固定の返信入力 → 送信で**即座に自分の返信が先頭に挿入**（楽観更新）＋トースト「返信しました」

### S3. 探す

- 検索バー：入力に応じてユーザー・タグをリアルタイム絞り込み（前方一致・部分一致）
- 人気タグ：チップ12個。投稿数を`--font-num`で併記
- 画像グリッド：3列・正方形クロップ。タップで S2 へ
- 空状態：「"◯◯" に一致する投稿はありません」＋人気タグへの導線

### S4. 通知

- タブ：`すべて` / `メンション`
- 4種類（いいね／返信／フォロー／メンション）でアイコンと色を変える
- 未読は左端に3pxのミントバー。画面を開いて1.5秒後にフェードで既読化

### S5. プロフィール

- カバー：グラデーション（高さ140px）／アバターは56px、カバーに-28pxで重ねる
- 表示名・@ID・bio・フォロー中/フォロワー数
- 自分＝`プロフィールを編集`（押すとトースト「デモでは編集できません」）、他人＝`フォローする`トグル
- タブ：`投稿` / `返信` / `いいね`

### S6. 投稿作成

- 下からせり上がるボトムシート（PCは中央モーダル）
- 本文（280字カウンター、260字超でオレンジ、280字超で赤＋送信不可）
- 画像追加ボタン → ダミー画像を3択から選ぶピッカー
- タグ入力：スペース区切りでチップ化
- 投稿 → シート下降 ＋ タイムライン先頭に挿入 ＋ トースト

### S7. DM

- 一覧：アバター・名前・最新メッセージ1行・時刻・未読ドット
- 会話：自分＝右・インディゴ地に白文字、相手＝左・白地。角丸は連続時に片側を潰す
- 送信すると**1.2秒後に相手からの定型返信が入る**（`autoReply`配列から順に）。デモの体験がここで一段良くなります

---

## 6. データモデル

```js
// user
{ id:"u01", name:"桐生 あかり", handle:"akari_kir", bio:"...",
  following:182, followers:2431, isFollowing:false }

// post
{ id:"p01", userId:"u01", body:"...", createdAt:"2026-07-21T08:12:00+09:00",
  image:{ seed:"morning-desk", ratio:"4:5" } | null,
  tags:["朝活"], likes:84, reposts:12, liked:false, saved:false }

// comment
{ id:"c01", postId:"p01", userId:"u04", body:"...", createdAt:"...", likes:3 }

// notification
{ id:"n01", type:"like"|"reply"|"follow"|"mention",
  userId:"u03", postId:"p02"|null, createdAt:"...", read:false }

// dmThread
{ id:"t01", userId:"u02", unread:2,
  messages:[ { from:"me"|"them", body:"...", at:"14:02" } ],
  autoReply:["了解です！","じゃあその時間で〜"] }
```

---

## 7. デモデータ

### 7.1 ユーザー（11名）

| id | 表示名 | handle | bio | フォロワー |
|---|---|---|---|---|
| u00 | あなた | you_demo | このデモを触っている人。何か投稿してみてください | 128 |
| u01 | 桐生 あかり | akari_kir | イラストと珈琲。締切前はいなくなります | 2,431 |
| u02 | 三上 湊 | minato_m | 商店街の端で小さな喫茶店をやっています | 1,208 |
| u03 | 白石 ひなた | hinata_shi | 大学3年／フィルムで撮ってます | 864 |
| u04 | 藤堂 玲 | rei_todo | Webエンジニア。個人開発の話が多め | 3,902 |
| u05 | 中村 そら | sora_nkmr | 年6回は飛行機に乗る会社員 | 1,675 |
| u06 | 岸本 大河 | taiga_ks | ベース弾き。夜になると音源を上げます | 941 |
| u07 | 森 ひかる | hikaru_mori | 作った料理をひたすら記録する場所 | 5,120 |
| u08 | 高梨 千夏 | chinatsu_t | 走る人。フル3回・サブ4狙い | 612 |
| u09 | 佐久間 陽 | yo_sakuma | 古着屋の店番。80sが好き | 1,340 |
| u10 | 東 みなみ | minami_azm | 植物と暮らす。今32鉢 | 2,087 |

### 7.2 投稿（18件）

| id | 投稿者 | 本文 | 画像seed / 比率 | タグ | ♡ | 🔁 |
|---|---|---|---|---|---|---|
| p01 | u01 | 朝5時に起きると1日が2回ある。まだ誰も起きてない時間に線を引くのがいちばん進む | morning-desk / 4:5 | 朝活 イラスト | 284 | 41 |
| p02 | u04 | 個人開発で一番大事なのは技術選定じゃなくて「今日15分だけやる」を切らさないことだった | — | 個人開発 | 1,204 | 312 |
| p03 | u07 | 冷蔵庫の残りだけで作ったのに今週いちばん美味しい。名前がないので「木曜の鍋」と呼ぶ | thursday-pot / 1:1 | 料理 | 892 | 77 |
| p04 | u03 | フィルム現像上がった。24枚のうち使えるのは3枚くらいで、その3枚のために撮ってる | film-roll / 4:5 | カメラ フィルム | 431 | 28 |
| p05 | u02 | 常連さんが「いつもの」と言ってくれるまで4年かかりました。今日その4年目でした | — | 喫茶店 | 2,180 | 402 |
| p06 | u10 | 枯らしたと思ってたモンステラから新芽。植物は諦めるのが早すぎるとよく怒られる | new-leaf / 1:1 | 植物 | 660 | 51 |
| p07 | u08 | 20km走ってきた。走ってる間は何も考えないと言うけど、実際は晩ごはんのことを考えている | — | ランニング | 318 | 12 |
| p08 | u06 | 夜中に録ったベースライン。近所迷惑にならない音量の限界を攻めている | bass-night / 16:9 | 音楽 DTM | 754 | 88 |
| p09 | u05 | 早朝の空港は世界でいちばん好きな場所かもしれない。誰もがどこかへ行く途中の顔をしている | airport-dawn / 16:9 | 旅 | 1,542 | 233 |
| p10 | u09 | 入荷したデッドストック。40年前の服が新品で出てくるの、毎回ちょっと怖い | vintage-rack / 4:5 | 古着 | 587 | 44 |
| p11 | u01 | 描けない日は描けない日として記録しておくと、あとで見たとき波があるだけだとわかる | — | イラスト | 1,033 | 176 |
| p12 | u04 | Claude Codeに要件定義書を渡すようになってから、やり取りの往復が体感で1/3になった | — | 開発 AI | 2,847 | 691 |
| p13 | u07 | 出汁だけは市販に頼らないと決めてる。他は全部頼っている | dashi-pot / 1:1 | 料理 | 445 | 19 |
| p14 | u03 | 逆光で撮ると全部それっぽくなるので、ここ最近ずっと逆光で撮っている | backlight / 4:5 | カメラ | 762 | 64 |
| p15 | u10 | 水やりの日をカレンダーに入れたら植物が枯れなくなった。原因は愛情ではなく管理だった | — | 植物 | 1,890 | 358 |
| p16 | u02 | 雨の日は客足が落ちるかわりに、長居する人が増える。それはそれで良い日だと思う | rainy-cafe / 16:9 | 喫茶店 | 1,266 | 142 |
| p17 | u05 | 現地の人しか行かない食堂に入るのが好き。メニューが読めないので隣の人と同じものを頼む | — | 旅 | 903 | 97 |
| p18 | u06 | ライブ終わり。音を出す仕事は疲れるけど、疲れ方が気持ちいい種類の疲れ方をしている | live-end / 16:9 | 音楽 | 528 | 33 |

`createdAt` は 2026-07-21 06:00 〜 2026-07-22 21:00 の範囲でばらけさせる（相対時刻表示が「3分前」「2時間前」「昨日」と混ざるように）。

### 7.3 返信（p01・p02・p05・p12 に付与）

| postId | 投稿者 | 本文 |
|---|---|---|
| p01 | u07 | わかる。朝の1時間は夜の3時間くらいある |
| p01 | u04 | 何時に寝てるんですか本当に |
| p01 | u01 | 22時です。夜は完全に諦めました |
| p02 | u08 | ランニングとまったく同じことを言っている |
| p02 | u10 | 「やる気が出たらやる」を捨てた瞬間から続くようになりました |
| p05 | u02 | みなさんありがとうございます、明日も開けます |
| p05 | u09 | 4年って言うほど短くないですよ。おめでとうございます |
| p12 | u01 | 要件定義書ってどのくらい細かく書いてます？ |
| p12 | u04 | 画面ごとの状態遷移まで書くと一発で通ります |

### 7.4 通知（8件・上3件が未読）

| type | 相手 | 内容 |
|---|---|---|
| like | u04 | あなたの投稿にいいねしました |
| follow | u07 | あなたをフォローしました |
| reply | u01 | あなたの投稿に返信しました：「それ気になります」 |
| mention | u05 | あなたをメンションしました |
| like | u03 | あなたの投稿にいいねしました |
| like | u10 | あなたの投稿にいいねしました |
| follow | u09 | あなたをフォローしました |
| reply | u08 | あなたの投稿に返信しました：「参考にします！」 |

### 7.5 DM（4スレッド）

| 相手 | 最新メッセージ | 未読 | autoReply |
|---|---|---|---|
| u02 | 今週の金曜、席とっておきます？ | 2 | 「了解です！19時で押さえておきます」／「お待ちしてます」 |
| u04 | あのリポジトリ、READMEだけ先に読んでみて | 0 | 「見ました？わりと雑ですが動きます」／「詰まったら聞いてください」 |
| u07 | レシピ送りました〜 | 1 | 「分量は適当で大丈夫です」／「作ったら見せてください！」 |
| u09 | 取り置きしてます、いつでもどうぞ | 0 | 「今週いっぱいは置いておきますね」／「お待ちしてます」 |

---

## 8. ディレクトリ構成（母艦テンプレ兼用）

```
souzoh-sns/
├── public/images/          ← 空。実写を入れると自動で反映
├── src/
│   ├── theme.css           ★リスキン対象①：色・フォント
│   ├── app.config.js       ★リスキン対象②：アプリ名・タブ構成・ラベル辞書
│   ├── data/               ★リスキン対象③：mockデータ
│   │   ├── users.js  posts.js  comments.js  notifications.js  dms.js
│   ├── components/ui/      ← 全アプリ共通。触らない
│   │   ├── Avatar.jsx  Button.jsx  Chip.jsx  Tabs.jsx
│   │   ├── Sheet.jsx   Toast.jsx   Skeleton.jsx  EmptyState.jsx
│   │   ├── GradientImage.jsx        ← seed→グラデ生成
│   │   └── CountUp.jsx              ← いいね数のスライドカウント
│   ├── components/shell/   ← 共通。ヘッダー／サイドナビ／タブバー／FAB
│   ├── features/
│   │   ├── timeline/  post-detail/  explore/
│   │   ├── notifications/  profile/  composer/  dm/
│   ├── store.js            ← useReducer 1本。Contextで配る
│   └── main.jsx
└── README.md
```

**`app.config.js` の例**（ここがリスキンの心臓部）：

```js
export default {
  appName: "Souzoh SNS",
  tagline: "ことばと写真の、小さなタイムライン",
  nav: [
    { key:"timeline", label:"ホーム",     icon:"Home" },
    { key:"explore",  label:"探す",       icon:"Search" },
    { key:"notify",   label:"通知",       icon:"Bell" },
    { key:"dm",       label:"メッセージ", icon:"Mail" },
    { key:"profile",  label:"プロフィール", icon:"User" },
  ],
  labels: {
    listTitle: "タイムライン",
    itemNoun: "投稿",          // フリマなら "商品"、学習なら "講座"
    createCta: "投稿する",
    primaryAction: "いいね",   // フリマなら "お気に入り"
  },
  tabs: { list: ["おすすめ", "フォロー中"] },
};
```

---

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

**セッションを4分割**します。1セッションにつき生成は1〜2機能まで。コンテキストを膨らませないのが費用対効果に直結します。

| Session | やること | 冒頭で読ませるファイル |
|---|---|---|
| 1 | 土台：Vite初期化 / theme.css / app.config.js / data配下5ファイル / store.js / ui配下8コンポーネント | 本書 §3・§4・§6・§7・§8 |
| 2 | S1タイムライン ＋ S2投稿詳細 | §5(S1,S2) ＋ `ui/` `store.js` |
| 3 | S3探す ＋ S4通知 ＋ S5プロフィール | §5(S3〜S5) ＋ `ui/` `store.js` |
| 4 | S6投稿作成 ＋ S7DM ＋ 仕上げ（アニメーション・空状態・reduced-motion） | §5(S6,S7) ＋ §4.3 |

**各セッション共通の指示文テンプレ**：

```
添付の要件定義書 §{該当節} を実装してください。
既存の src/components/ui/ と src/store.js は完成済みです。新規作成せず import して使ってください。
色・余白・角丸は src/theme.css の変数のみを使い、生の値をハードコードしないでください。
実装前に、変更・作成するファイルの一覧だけ先に提示してください。
```

最後の1行が効きます。着手前にファイル一覧を出させると、想定外の探索と作り直しが減ります。

---

## 10. 受け入れ基準

- [ ] 7画面すべてが遷移でき、行き止まりがない
- [ ] いいね／フォロー／保存が即座にトグルし、数字が連動する
- [ ] 投稿・返信・DM送信が、リロードするまで残る
- [ ] DM送信の1.2秒後に相手の返信が入る
- [ ] 空状態・検索0件・スケルトンが用意されている
- [ ] 375px幅と1440px幅で崩れない
- [ ] 生の色コードがJSX内に1つも無い（全て`theme.css`経由）
- [ ] `prefers-reduced-motion: reduce` で全アニメーションが止まる
- [ ] キーボードのTab移動でフォーカスリングが見える
- [ ] `npm run build` が警告なく通り、Cloudflare Pagesで表示される

---

## 11. リスキン手順（2本目以降の参照用）

### 11.0 命名ルール（5本共通）

全アプリを `Souzoh {ドメイン}` で統一します。ロゴは `Souzoh` を `--font-display` の700、ドメイン名を500の薄色で続ける2トーン組み。全記事のスクショに社名が入り、テンプレ配布物としても出所が一目でわかります。

| 記事 | appName | accent |
|---|---|---|
| SNS | Souzoh SNS | `#4A3AFF` インディゴ |
| マッチング | Souzoh Match | コーラル→ピンクのグラデ |
| フリマ | Souzoh Market | `#0A8F3C` ビビッドグリーン |
| デリバリー | Souzoh Delivery | `#FF6A13` オレンジ ＋ 濃紺背景 |
| 学習 | Souzoh Learn | `#0E6B70` ディープティール |

### 11.1 手順

1. `theme.css` の色6つとフォント3つを差し替え
2. `app.config.js` の `appName` / `nav` / `labels` を差し替え
3. `data/` の5ファイルを新ドメインの内容に差し替え
4. そのアプリ固有の1〜2画面のみ新規実装
   - マッチング → スワイプカード
   - フリマ → 出品フォーム・取引ステータス
   - デリバリー → カート・配達トラッキング
   - 学習 → 動画プレイヤー・進捗リング
5. `components/ui/` と `components/shell/` は**触らない**

---

## 12. 補足：概算コスト感

Session 1が最も重く（土台＋コンポーネント一式）、Session 2〜4は逓減します。2本目以降は §11 の手順1〜3が中心になるため、体感で初回の3〜4割程度に収まる想定です。ただし実際の消費は往復回数に強く依存するので、**「デザインの微調整はまとめて最後に1回」**を徹底してください。ここが崩れると初回と同じだけ掛かります。


---

## 付録　本番運用に向けて（ソウゾウ合同会社より）

> **この章は実装の対象ではありません。Claude Code は、この章の内容を実装しないでください。** 要件定義書を読んでいる方へのご案内です。

この要件定義書は、画面と動きを確かめるための**デモ**として書かれています。社内やお客さまに実際に使ってもらうには、次の4つが別に必要です。

| 項目 | デモでは | 本番では |
|---|---|---|
| データの保存 | データはその端末のブラウザの中にしか残りません。ほかの端末やほかの人とは共有されず、消えることもあります。 | サーバーのデータベースに保存し、バックアップと復元ができるようにします。 |
| セキュリティ | ログインがなく、誰でもすべての画面とデータを見られます。 | ログイン、権限（誰が何を見られるか）、通信とデータの暗号化、不正なアクセスへの対策を入れます。 |
| ガバナンス（運用ルール） | 決めていません。 | 誰がいつ何をしたかの記録、個人情報の扱い（プライバシーポリシー・同意）、アカウントの発行と削除、障害のときの連絡体制を決めます。 |
| 公開・デプロイ | 手元で動かすか、デモとして公開するだけです。 | 独自ドメイン、本番とテストの環境分け、更新の手順、監視と障害対応、月々の費用の管理を整えます。 |

本番運用をお考えの方は、ぜひ一度ご相談ください。最適なプランのご紹介と、進め方をお伝えします。

- 日程を選んで相談する（代表 西澤）：https://vszh6zudz50.jp.larksuite.com/scheduler/e7fecdad1a756dbe
- 公式LINE：https://lin.ee/UNwvWjp
- お問い合わせフォーム：https://service.souzoh-official.com/contact/
