2026年2月27日、ポケモンは誕生30周年を迎えました。
おめでとう。そしてありがとう、PokéAPI。この記事はその感謝を込めて書いています。
はじめに
みなさん、月次定例ってどうやって発表してますか?
PowerPoint? Googleスライド? Notion?
僕もそうでした。毎月、淡々とスライドを作って、淡々と発表して、淡々と終わる。
内容はちゃんとしてるはずなのに、なぜか聞いてる側の目が死んでいる。
「いや、俺の2ヶ月の成果、もっと盛り上がってよくない?」
そんなワガママな欲求から、月次定例の発表スライドをやめて、Spotify Wrapped風のWebアプリを作りました。
BGMが流れ、ポケモンが鳴き、紙吹雪が舞う。
月次定例が、ライブになりました。
この記事でわかること
- Spotify Wrapped風プレゼンアプリの設計思想と技術構成
- Web Audio APIで効果音をコードだけで生成する方法
- PokéAPIでポケモンの鳴き声を再生する実装
- Claude Codeとの並行開発で爆速でアプリを完成させた話
完成したもの
Monthly Wrapped ─ Spotify Wrappedのストーリー形式で、月次の成果を発表するWebアプリです。
機能一覧
| 機能 | 説明 |
|---|---|
| 📱 ストーリーUI | Instagram Stories風プログレスバーで進行表示 |
| 🎨 10種のカラーテーマ | スライドごとにグラデーションが変わる |
| ✨ パーティクル&紙吹雪 | 35個のキラキラ + 最後に紙吹雪が降る |
| 🎵 BGM | ループ再生で会場の空気を支配 |
| 🔊 スライド遷移SE | Web Audio APIでリアルタイム生成した効果音 |
| 🐾 ポケモン | 各スライドに伝説ポケモンが登場し、鳴く |
| 📊 カウントアップ | 数字が0からイージング付きで増加 |
| 📸 メディア埋め込み | 画像・動画をスライド内に表示 |
全部入り。 やりすぎました。
全体アーキテクチャ
なぜ作ったのか
きっかけは単純で、Spotify Wrappedが好きすぎた。
年末に「あなたの今年のトップアーティストは〜」ってやるあれです。
あのワクワク感、テンポの良さ、次のスライドが気になる感じ。
「これ、月次定例でやったら最高では?」
と思ったのが運の尽きでした。
Before / After
| Before 😐 | After 🎉 | |
|---|---|---|
| 形式 | PowerPoint | Spotify Wrapped風Webアプリ |
| 音 | 無音 | BGM + 効果音 + ポケモンの鳴き声 |
| 視覚 | 静的なスライド | パーティクル + カウントアップ + 紙吹雪 |
| 反応 | 「ふーん」 | 拍手 |
| 更新コスト | 毎月スライド作り直し | JSON差し替え 5分 |
| ポケモン | いない | 鳴く |
技術スタック
React 18.3 + Vite 5.4
以上。
フレームワークはReactだけ。状態管理ライブラリなし。CSSフレームワークなし。 バニラに近い構成です。
| 技術 | 用途 | 一言 |
|---|---|---|
| React 18 | UIコンポーネント | これだけで十分 |
| Vite 5 | ビルド&開発サーバー | 爆速HMR |
| Web Audio API | 効果音のリアルタイム生成 | 音をコードで作る狂気 |
| PokéAPI | ポケモンのスプライト&鳴き声 | 神API。感謝 |
| CSS Keyframes | パーティクル・紙吹雪・アニメーション | GPU加速で60fps |
| JSON | スライドデータ管理 | 差し替えるだけで内容変更 |
ソースファイルはたった5つ。合計 約600行。 これで全機能入りのプレゼンアプリが動きます。
src/
├── App.jsx # 110行 - ナビゲーション & プログレスバー
├── Slide.jsx # 330行 - 6種類のスライド描画エンジン
├── Confetti.jsx # 紙吹雪パーティクル(物理演算付き)
├── useAudio.js # 107行 - BGM + SE + ポケモン鳴き声
└── useParallax.js # パララックス効果
おもしろ実装ポイント 6選
1. 会議でBGMが流れる ─ 正気か?
まず一番ヤバいのはこれ。月次定例にBGMを付けた。
実装自体はシンプルで、Web Audio APIの AudioContext でループ再生しているだけ:
const startBGM = () => {
const ctx = getCtx();
fetch('/assets/bgm.mp3')
.then(r => r.arrayBuffer())
.then(buf => ctx.decodeAudioData(buf))
.then(audioBuffer => {
const source = ctx.createBufferSource();
source.buffer = audioBuffer;
source.loop = true; // ← これだけでずっと流れる
const gain = ctx.createGain();
gain.gain.value = 0.08; // 主張しすぎない音量
source.connect(gain).connect(ctx.destination);
source.start();
});
};
技術的には大したことない。でも「会議でBGMを流す」という発想がそもそもおかしい。
しかも曲はダイヤモンド・パールのシロナ戦。
ポケモン史上最強のチャンピオン戦BGMが、月次定例の会議で流れている。冷静に考えてほしい。報告資料の発表中にシロナ戦のイントロが鳴ってる。 なぜか緊張感が出る。なぜか本気になる。
gain.gain.value = 0.08 ─ この音量調整に全てが詰まっている。大きすぎると発表の邪魔(シロナ戦は主張が強い)、小さすぎると存在感がない。0.08、これがベスト。会議のBGMの最適音量を追い求めた男がここにいる。
2. 効果音はダウンロードしない、その場で合成する
スライド遷移時の「シュッ」という効果音、音声ファイルを使っていません。
Web Audio APIでホワイトノイズを生成→バンドパスフィルター→250msで減衰。すべてリアルタイムで合成しています。
const playSlideSE = () => {
const ctx = getCtx();
const duration = 0.25;
const buffer = ctx.createBuffer(1, ctx.sampleRate * duration, ctx.sampleRate);
const data = buffer.getChannelData(0);
// ホワイトノイズ × エンベロープ
for (let i = 0; i < data.length; i++) {
const t = i / data.length;
const envelope = Math.sin(t * Math.PI) * (1 - t * 0.5);
data[i] = (Math.random() * 2 - 1) * envelope * 0.15;
}
const source = ctx.createBufferSource();
source.buffer = buffer;
// バンドパスフィルターで「シュッ」感を演出
const filter = ctx.createBiquadFilter();
filter.type = 'bandpass';
filter.frequency.setValueAtTime(2000, ctx.currentTime);
filter.frequency.exponentialRampToValueAtTime(800, ctx.currentTime + duration);
filter.Q.value = 1.5;
// ゲイン減衰
const gain = ctx.createGain();
gain.gain.setValueAtTime(0.3, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + duration);
source.connect(filter).connect(gain).connect(ctx.destination);
source.start();
source.stop(ctx.currentTime + duration);
};
音をコードで作る。 冷静に考えるとヤバいですが、これが Web Audio APIの本気です。
音声ファイルの管理が不要になるし、パラメータを変えるだけで音色を調整できる。月次定例のためにここまでやる必要があったかは…まぁ、あった。
Web Audio API のポイント
ブラウザの自動再生ポリシーにより、ユーザーインタラクション後にしかAudioContextを初期化できません。今回は「画面をクリックして開始」のオーバーレイで解決しています。
3. ポケモンが毎スライド鳴く 🎉 祝30周年
2026年2月27日、ポケモンは30周年を迎えました。
このアプリで盛大にお祝いさせてもらいました。
各スライドにはポケモンIDが設定されており、PokéAPIからスプライト画像と鳴き声をリアルタイムで取得しています。
const playPokemonCry = (pokemonId) => {
const ctx = getCtx();
const url = `https://raw.githubusercontent.com/PokeAPI/cries/main/cries/pokemon/latest/${pokemonId}.ogg`;
fetch(url)
.then(r => r.arrayBuffer())
.then(buf => ctx.decodeAudioData(buf))
.then(audioBuffer => {
const source = ctx.createBufferSource();
source.buffer = audioBuffer;
const gain = ctx.createGain();
gain.gain.value = 0.1; // 控えめに鳴く(会議中なので)
source.connect(gain).connect(ctx.destination);
source.start();
});
};
スライド遷移時、400msの間を置いてからポケモンの鳴き声を再生しています。この「間」が大事で、遷移SEとポケモンの鳴き声が被らないようにしている。
こだわり:全員が歴代パッケージの伝説ポケモン
このアプリに登場するポケモン、実は全員が歴代シリーズのパッケージを飾った伝説ポケモンです。
最初のスライドはディアルガ。ダイヤモンド・パールの顔で、時間を司るポケモン。「月次」の始まりにこれ以上の選択肢はない。
| スライド | ポケモン | パッケージ | 選んだ理由 |
|---|---|---|---|
| intro | ディアルガ | 💎 ダイヤモンド | 時間を司る → 「月次」の始まり |
| 成長 | ホウオウ | 🥇 金 | 復活と再生 → チームの成長 |
| 横断 | カイオーガ | 🔵 サファイア | 海の王 → oceanテーマ |
| 開発① | レックウザ | 💚 エメラルド | 天空の支配者 → emeraldテーマ |
| 開発② | グラードン | 🔴 ルビー | 大地の創造 → sunsetテーマ |
| AI分析 | レシラム | ⬜ ホワイト | 真実の白炎 → 情熱 |
| 広告 | パルキア | 💜 パール | 空間を司る → violetテーマ |
| 管理 | ゼルネアス | 🦌 X | 生命の力 → forestテーマ |
| 文化 | ルギア | 🥈 銀 | 海の守り神 → チームの結束 |
| 発見 | ゼクロム | ⬛ ブラック | 理想の黒雷 → 課題発見 |
| 視点 | ソルガレオ | ☀️ サン | 太陽の使者 → 新しい視点 |
| 計画 | ギラティナ | 🌀 プラチナ | 反転世界 → 未来への布石 |
| まとめ | アルセウス | ⭐ LEGENDS | 創造神 → すべてを締めくくる |
ディアルガで始まり、アルセウスで終わる。
パッケージの伝説ポケモンだけで13枚のスライドを構成する。やりすぎ? いいえ、愛です。 30周年だし。
4. 紙吹雪は物理演算している
最後のサマリースライドで降る紙吹雪、ちゃんと重力加速度と回転を計算しています。
// 毎フレーム物理演算
p.y += p.vy;
p.vy += 0.15; // 重力加速度
p.x += p.vx;
p.rotation += p.rotationSpeed; // 回転
// 画面外に出たら上から再投入(オブジェクトプール)
if (p.y > height + 20) {
p.y = -20;
p.vy = Math.random() * 2 + 1;
}
月次定例に物理演算は要るのか?
要る。
紙吹雪がヒラヒラと自然に落ちてくるだけで、会場の「おお〜」が全然違います。
5. カウントアップが気持ちいい
数字の表示は0からスタートして、ease-out cubicのイージングでカウントアップ。
const eased = 1 - Math.pow(1 - progress, 3); // ease-out cubic
setText(String(Math.round(eased * target)));
「7プロジェクト」→「0...1...3...5...7プロジェクト!」と加速しながら増える。
最初にグッと増えて、目標値に近づくにつれてゆっくりになる。「到達した感」が気持ちいい。
ただの数字なのにテンションが上がる。これがWrapped力です。
6. プログレスバーがInstagram Stories
画面上部にInstagram Storiesと同じプログレスバーを実装しました。
[████████][████░░░░][░░░░░░░░][░░░░░░░░][░░░░░░░░]
完了 現在 未読 未読 未読
現在のスライドがアニメーションで埋まっていき、次に進むと次のバーが動き始める。
「あと何枚あるんだろう」がわかるのが地味に大事。 聞いてる人の集中力が持続します。
PowerPointの 12/30 とは情報量が違う。
Claude Codeとの並行開発がヤバかった
今回の開発で一番語りたいのは、実はClaude Codeとの共同開発体験です。
開発フロー
直列 vs 並行:開発のガントチャート
普通、一人で開発すると直列でしか進まない:
Claude Codeとの並行開発だと:
圧倒的に速い。
しかも、仕様書を先に書いておいたことで、Claude Codeが仕様に沿って自律的に実装してくれた。
「ここどうしますか?」じゃなくて「仕様通りに作りました、確認してください」が返ってくる。
Claude Codeに任せたこと
| タスク | 詳細 | 感想 |
|---|---|---|
| 🎨 10種のカラーテーマ | グラデーション・アクセント・グロウを全パターン一発生成 | 色彩感覚がある |
| 🔊 Web Audio API | ノイズ生成→フィルター→減衰のパイプラインを一発実装 | 音響工学もいける |
| 🐾 PokéAPI連携 | fetch→decode→再生の非同期処理、エラーハンドリング込み | 非同期処理完璧 |
| ✨ CSSアニメーション | パーティクル35個分のランダムパラメータ、8種のkeyframes | 職人芸 |
| 📝 発表原稿 | 8分間のタイムスケジュール付きセリフを生成 | ライターもできる |
| 🐾 ポケモン選定 | 各スライドの内容に合わせた伝説ポケモンを提案 | ポケモンの解像度が高い |
Claude Code活用のコツ
一番のポイントは「仕様書を先に書く」こと。
Claude Codeに丸投げするんじゃなくて、設計と仕様は自分で決める。実装を任せる。
自分 = プロデューサー、Claude Code = エンジニアチーム。この役割分担がハマった。
仕様書に書いた内容はこんな感じ:
仕様書の抜粋(クリックで展開)
## スライドタイプ一覧
- intro: タイトル画面(bigText, subText)
- stat: 数字を大きく見せる(bigText, unit, subText)
- project: 成果紹介(title, description, tags, media)
- highlight: ハイライトエピソード(bigText, subText)
- summary: 締めくくり(stats配列, subText)
## ルール
- 1スライド1メッセージ。情報を詰め込みすぎない
- 連続するスライドで同じthemeを使わない
- bigTextは短く。長い文章はsubTextに
## カラーテーマ
midnight, coral, emerald, violet, ocean, flame, forest, sunset, rose, gold
スライドのデータ構造
JSONを書き換えるだけで誰でも自分の月次発表が作れます。
{
"meta": {
"name": "あなたの名前",
"team": "チーム名",
"month": "2026年3月",
"monthShort": "Mar 2026"
},
"slides": [
{
"type": "intro",
"theme": "midnight",
"topLabel": "MONTHLY WRAPPED",
"bigText": "2026年3月",
"subText": "あなたの1ヶ月を振り返ろう",
"pokemon": 483
},
{
"type": "stat",
"theme": "coral",
"topLabel": "PROJECTS",
"bigText": "5",
"unit": "プロジェクト",
"subText": "今月関わったプロジェクト数",
"pokemon": 250
}
]
}
来月の発表? slides.json を差し替えるだけ。5分で終わる。
毎月の運用も自動化
仕様書にヒアリングフローを組み込んであるので、Claude Codeに以下を送るだけ:
あなたは「Monthly Wrapped」のスライドデータを作成するアシスタントです。
Spotify Wrapped風のストーリー形式で、私の今月の成果を魅力的に伝えるスライドを作ってください。
Claude Codeが対話形式で今月の成果をヒアリングして、slides.json を自動生成してくれます。
つまり毎月の運用コストがほぼゼロ。最高の仕組みができてしまった。
10種のカラーテーマ
スライドごとに雰囲気がガラッと変わるのが、Wrapped体験の肝です。
| テーマ | 色味 | おすすめ用途 |
|---|---|---|
| 🌙 midnight | 紺→紫→暗灰 | intro、締めくくり |
| 🪸 coral | 暗赤→赤→オレンジ | 数字インパクト |
| 🌿 emerald | 暗緑→エメラルド→ミント | プロジェクト紹介 |
| 💜 violet | 暗紫→紫→薄紫 | インパクト数値 |
| 🌊 ocean | 暗青→青→スカイブルー | プロジェクト紹介 |
| 🔥 flame | 暗赤→赤→オレンジ | 熱いトピック |
| 🌲 forest | 暗緑→緑→ライトグリーン | プロジェクト紹介 |
| 🌅 sunset | 暗橙→オレンジ→黄 | 数字・成果 |
| 🌹 rose | 暗ピンク→ローズ→桃色 | エモいハイライト |
| ✨ gold | 暗金→ゴールド→黄 | summary(ラスト) |
ルール: 連続するスライドで同じテーマを使わない。 色の変化で「次に進んだ」感を出す。
発表当日 ─ 会議がライブになった
会議室でプロジェクターに映して、npm run dev で起動。
画面に「MONTHLY WRAPPED — 画面をクリックして開始」のオーバーレイが表示される。
参加者が全員着席したのを確認して、クリック。
BGMが流れ始める。
一瞬の沈黙。そして笑い。
「え、音楽流れるの?」
そう、月次定例で音楽が流れるんです。 普通に考えたらおかしい。会議ですよ。アジェンダがあって、議事録があって、次のアクションを決める場で、BGMがループ再生されてる。
でもこれが効く。
BGMが流れた瞬間、聞く姿勢が「会議モード」から「コンテンツを楽しむモード」に切り替わる。Spotify Wrappedを見るときの、あのリラックスした集中力。月次定例なのに、みんなの目がちゃんと画面を見てる。
スライドが切り替わるたびに「シュッ」と効果音。
数字がカウントアップして、ポケモンが鳴く。
パーティクルがキラキラ舞う。
BGMはずっとループしてる。
いつもの月次定例が、完全にライブになった。
最後のサマリースライドでアルセウスが鳴き、紙吹雪が降った時、
普通に拍手が起きました。
月次定例で拍手って何?
BGMの威力、マジで侮れない。 同じ内容を無音で発表するのと、BGM付きで発表するのでは聞いてる人の反応が全然違う。エンタメの基本はやっぱり音楽。映画もゲームもそう。月次定例だって同じだった。
操作方法
| 操作 | アクション |
|---|---|
| 画面右側クリック / →キー / Space | 次のスライド |
| 画面左側クリック / ←キー | 前のスライド |
| スワイプ左右(モバイル) | 前後移動 |
おまけ:技術の学びメモ
Web Audio API — ブラウザで音を"作る"
ブラウザ上で音をプログラムで生成できるのは本当に面白い。効果音を外部ファイルに依存せず、コードだけで作れるのはWebアプリならではの強み。
注意点:
-
AudioContextはユーザーインタラクション後にしか初期化できない(ブラウザの自動再生ポリシー) -
audioCtx.state === 'suspended'のチェックを忘れずに - 今回は「クリックして始める」オーバーレイでこれを解決
PokéAPI — 30年分のポケモンデータが無料
- Official Artworkのスプライトが高解像度でおすすめ
- 鳴き声は GitHub上の
.oggファイルから直接取得可能 - レスポンスが速い。個人開発の遊び心を加えるのに最高のAPI
- 完全無料。感謝しかない
CSS clamp() — メディアクエリ不要のレスポンシブ
font-size: clamp(2.5rem, 8vw, 6rem);
min, preferred, max を一行で指定できるので、メディアクエリなしでレスポンシブな文字サイズが実現できます。プロジェクター(大画面)でもスマホでも読みやすいサイズになるので、プレゼンアプリに特に効きました。
Reactカスタムフック — ロジックの分離
useAudio フックで音声ロジックを完全に分離したことで、UIコンポーネント側は関数を呼ぶだけ:
const { playSlideSE, playPokemonCry, startBGM } = useAudio();
// スライド遷移時
playSlideSE();
setTimeout(() => playPokemonCry(slide.pokemon), 400);
音の実装を知らなくていい。フックによるロジック分離は、コンポーネントの見通しを良くするだけでなく、テストや差し替えも容易にしてくれます。
まとめ
月次定例は、もっと楽しくていい。
技術の無駄遣いだと思いますか?
僕は技術の正しい使い方だと思っています。
だって、自分たちの成果を伝える場なんだから、
最高の体験で届けたいじゃないですか。
ポケモン30周年、Spotify Wrapped、Claude Code。
2026年のいいとこ取りをして、月次定例をライブにした話でした。
来月はどのポケモンを鳴かせようかな。
この記事が面白かったらLGTMお願いします!
過去、Chromeの新規タブをドラクエ5風に改造した記事を書いたので、そちらもぜひ。




