# 画面設計書 10-ブランドページ

## 概要

本ドキュメントは、OpenCodeウェブサイトのブランドページの設計仕様を定義する。

### 本画面の処理概要

ブランドページは、OpenCodeのブランドアセット（ロゴ、ワードマーク）をダウンロードできるページである。ライト/ダークモード、通常/正方形の各バリエーションをPNG/SVG形式で提供する。

**業務上の目的・背景**：メディア、パートナー企業、コミュニティがOpenCodeのブランドアセットを正しく使用できるよう、公式のロゴ・ワードマークを提供する。ブランドの一貫性を保つためのリソースページ。

**画面へのアクセス方法**：フッターのブランドリンク、またはURL `/brand` に直接アクセス。

**主要な操作・処理内容**：
1. ブランドアセット一覧の表示
2. 個別アセットのダウンロード（PNG/SVG）
3. 全アセット一括ダウンロード（ZIP）

**画面遷移**：
- 遷移元：フッター、各種リンク
- 遷移先：なし（ダウンロード処理のみ）

**権限による表示制御**：特になし（公開ページ）

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 56 | 国際化 | 主機能 | 多言語対応（i18n.t関数による翻訳） |

## 画面種別

公開ページ / ブランドリソース

## URL/ルーティング

`/brand` - ブランドページ

## 入出力項目

| 項目名 | 入力/出力 | 型 | 必須 | 説明 |
|--------|----------|------|------|------|
| - | - | - | - | この画面では入力項目なし |

## 表示項目

| 項目名 | 型 | 説明 |
|--------|------|------|
| ヘッダー | コンポーネント | サイトナビゲーション |
| ページ見出し | テキスト | ブランドページタイトル |
| サブタイトル | テキスト | ページ説明 |
| 全ダウンロードボタン | ボタン | 全アセット一括ダウンロード |
| アセットグリッド | グリッド | ブランドアセット一覧 |
| ロゴ（ライト） | 画像 | OpenCodeロゴ ライトモード版 |
| ロゴ（ダーク） | 画像 | OpenCodeロゴ ダークモード版 |
| ロゴ正方形（ライト） | 画像 | OpenCodeロゴ正方形 ライトモード版 |
| ロゴ正方形（ダーク） | 画像 | OpenCodeロゴ正方形 ダークモード版 |
| ワードマーク（ライト） | 画像 | OpenCodeワードマーク ライトモード版 |
| ワードマーク（ダーク） | 画像 | OpenCodeワードマーク ダークモード版 |
| ワードマークシンプル（ライト） | 画像 | OpenCodeシンプルワードマーク ライトモード版 |
| ワードマークシンプル（ダーク） | 画像 | OpenCodeシンプルワードマーク ダークモード版 |
| PNGダウンロードボタン | ボタン | PNG形式ダウンロード |
| SVGダウンロードボタン | ボタン | SVG形式ダウンロード |
| フッター | コンポーネント | サイトフッター |
| 法的表示 | コンポーネント | 利用規約等へのリンク |

## イベント仕様

### 1-個別アセットダウンロード

ユーザーがPNG/SVGボタンをクリックした際の処理。

処理フロー：
1. `downloadFile(url, filename)` が呼び出される
2. `fetch(url)` でアセットを取得
3. `response.blob()` でBlobに変換
4. `window.URL.createObjectURL(blob)` でBlobURLを生成
5. 動的に `<a>` 要素を作成し `download` 属性を設定
6. `link.click()` でダウンロードを開始
7. `window.URL.revokeObjectURL(blobUrl)` でメモリ解放

### 2-全アセット一括ダウンロード

ユーザーが「全ダウンロード」ボタンをクリックした際の処理。

処理フロー：
1. `downloadFile(brandAssets, "opencode-brand-assets.zip")` が呼び出される
2. `/opencode-brand-assets.zip` からZIPファイルを取得
3. 上記「個別アセットダウンロード」と同様の処理でダウンロード

### 3-ダウンロードフォールバック

fetch失敗時のフォールバック処理。

処理フロー：
1. `catch` ブロックでエラーをキャッチ
2. `console.error("Download failed:", error)` でエラーログ
3. 代替として新しいタブでアセットURLを開く

## データベース更新仕様

### 操作別データベース影響一覧

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| - | - | - | この画面ではデータベース更新は行わない |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|--------------|----------|
| brand.title | タイトル | ページタイトル | 常時 |
| brand.meta.description | メタ | ページ説明 | 常時 |
| brand.heading | 見出し | ページ見出し | 常時 |
| brand.subtitle | 説明 | サブタイトル | 常時 |
| brand.downloadAll | ボタン | 全ダウンロードボタンテキスト | 常時 |

## 例外処理

| 状況 | 処理内容 |
|------|---------|
| アセット取得失敗 | 新しいタブでアセットURLを開く（フォールバック） |

## 備考

- 提供されるブランドアセット（8種類 x 2形式 = 16ファイル）:
  - ロゴ（ライト/ダーク）
  - ロゴ正方形（ライト/ダーク）
  - ワードマーク（ライト/ダーク）
  - ワードマークシンプル（ライト/ダーク）
- 各アセットはPNGとSVGの2形式で提供
- 一括ダウンロードはZIP形式（/opencode-brand-assets.zip）
- プレビュー画像とダウンロード用画像は別ファイル
- data-page属性は "enterprise" を使用（スタイル共有のため）

---

## コードリーディングガイド

本画面を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

アセットファイルのインポートと定数定義を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | index.tsx | `packages/console/app/src/routes/brand/index.tsx` | プレビュー画像インポート（8-15行目） |
| 1-2 | index.tsx | `packages/console/app/src/routes/brand/index.tsx` | PNG/SVGアセットインポート（16-31行目） |
| 1-3 | index.tsx | `packages/console/app/src/routes/brand/index.tsx` | brandAssets定数（32行目） |

**読解のコツ**: プレビュー用画像（preview-*）とダウンロード用画像は別ファイル。PNG/SVG両形式をインポート。

#### Step 2: エントリーポイントを理解する

メインのBrandコンポーネントを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.tsx | `packages/console/app/src/routes/brand/index.tsx` | Brandコンポーネント（34-314行目） |

**主要処理フロー**:
1. **35行目**: useI18nの取得
2. **36-60行目**: downloadFile関数定義
3. **62-66行目**: メタ情報設定
4. **71-87行目**: ヒーローセクション（タイトル、サブタイトル、全ダウンロードボタン）
5. **89-306行目**: アセットグリッド（8つのアセット x PNG/SVGボタン）

#### Step 3: ダウンロード処理を理解する

ファイルダウンロードの実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.tsx | `packages/console/app/src/routes/brand/index.tsx` | downloadFile関数（36-60行目） |
| 3-2 | index.tsx | `packages/console/app/src/routes/brand/index.tsx` | fetchとBlob変換（37-40行目） |
| 3-3 | index.tsx | `packages/console/app/src/routes/brand/index.tsx` | 動的リンク生成（42-48行目） |
| 3-4 | index.tsx | `packages/console/app/src/routes/brand/index.tsx` | フォールバック処理（50-59行目） |

### プログラム呼び出し階層図

```
Brand (index.tsx)
    │
    ├─ useI18n() ──────────────────> I18nContext
    │      └─ i18n.t() (翻訳)
    │
    ├─ downloadFile() ────────────> ダウンロード処理
    │      ├─ fetch() (アセット取得)
    │      ├─ response.blob() (Blob変換)
    │      ├─ URL.createObjectURL() (BlobURL生成)
    │      ├─ document.createElement("a") (リンク生成)
    │      ├─ link.click() (ダウンロード開始)
    │      ├─ URL.revokeObjectURL() (メモリ解放)
    │      └─ [catch] 新しいタブでURL開く (フォールバック)
    │
    ├─ Header ────────────────────> header.tsx
    │
    ├─ Footer ────────────────────> footer.tsx
    │
    └─ Legal ─────────────────────> legal.tsx
```

### データフロー図

```
[入力]                      [処理]                          [出力]

ダウンロードボタン          downloadFile()                  ファイルダウンロード
(クリック) ────────────────▶ fetch() ─────────────────────▶ ブラウザダウンロード
                               │
                               ├─ response.blob()
                               ├─ URL.createObjectURL()
                               └─ link.click()

フォールバック             新しいタブで開く               ブラウザで表示
(fetchエラー) ─────────────▶ window.open() ───────────────▶ アセット直接表示
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.tsx | `packages/console/app/src/routes/brand/index.tsx` | ソース | ブランドページコンポーネント（315行） |
| index.css | `packages/console/app/src/routes/brand/index.css` | スタイル | ブランドページ専用スタイル |
| header.tsx | `packages/console/app/src/component/header.tsx` | ソース | ヘッダーコンポーネント |
| footer.tsx | `packages/console/app/src/component/footer.tsx` | ソース | フッターコンポーネント |
| legal.tsx | `packages/console/app/src/component/legal.tsx` | ソース | 法的表示 |
| locale-links.tsx | `packages/console/app/src/component/locale-links.tsx` | ソース | 多言語リンク |
| i18n.tsx | `packages/console/app/src/context/i18n.tsx` | ソース | 国際化コンテキスト |
| preview-opencode-logo-light.png | `packages/console/app/src/asset/brand/preview-opencode-logo-light.png` | アセット | ロゴプレビュー（ライト） |
| preview-opencode-logo-dark.png | `packages/console/app/src/asset/brand/preview-opencode-logo-dark.png` | アセット | ロゴプレビュー（ダーク） |
| preview-opencode-logo-light-square.png | `packages/console/app/src/asset/brand/preview-opencode-logo-light-square.png` | アセット | ロゴ正方形プレビュー（ライト） |
| preview-opencode-logo-dark-square.png | `packages/console/app/src/asset/brand/preview-opencode-logo-dark-square.png` | アセット | ロゴ正方形プレビュー（ダーク） |
| preview-opencode-wordmark-light.png | `packages/console/app/src/asset/brand/preview-opencode-wordmark-light.png` | アセット | ワードマークプレビュー（ライト） |
| preview-opencode-wordmark-dark.png | `packages/console/app/src/asset/brand/preview-opencode-wordmark-dark.png` | アセット | ワードマークプレビュー（ダーク） |
| preview-opencode-wordmark-simple-light.png | `packages/console/app/src/asset/brand/preview-opencode-wordmark-simple-light.png` | アセット | シンプルワードマークプレビュー（ライト） |
| preview-opencode-wordmark-simple-dark.png | `packages/console/app/src/asset/brand/preview-opencode-wordmark-simple-dark.png` | アセット | シンプルワードマークプレビュー（ダーク） |
| opencode-logo-light.png | `packages/console/app/src/asset/brand/opencode-logo-light.png` | アセット | ロゴ（ライト）PNG |
| opencode-logo-dark.png | `packages/console/app/src/asset/brand/opencode-logo-dark.png` | アセット | ロゴ（ダーク）PNG |
| opencode-logo-light.svg | `packages/console/app/src/asset/brand/opencode-logo-light.svg` | アセット | ロゴ（ライト）SVG |
| opencode-logo-dark.svg | `packages/console/app/src/asset/brand/opencode-logo-dark.svg` | アセット | ロゴ（ダーク）SVG |
| opencode-brand-assets.zip | `/opencode-brand-assets.zip` | アセット | 全アセット一括ZIP |
