はじめに
潔癖症・嘔吐恐怖症の友人のために、映画に不潔な要素が含まれるかを事前にチェックできるレビューサイトを作りました。海外サイト「Does the Dog Die?」の潔癖症版です。
完成したサイト: https://keppekimovierev.blogspot.com/
当初の想定構成
潔癖症・嘔吐恐怖症の人が映画ポスターをタップすれば、どのような汚い表現が含まれているのか絵文字アイコンで一目瞭然に理解できるようにする。汚い表現が一切無い映画である場合も明確に分かるようにする。
- レスポンシブデザイン
- ヘッダに記事名、ハンバーグメニューは固定
- スクロールでTOPへのジャンプリンク出現
- 記事一覧はグリッド表示、クリックで記事がモーダル表示
- モーダル記事内には9つの衛生面評価項目を🚽などの絵文字アイコンで表示。ネタバレを含むより詳細な解説を別ボタンでアコーディオン展開
- 不潔要素がない清潔な映画の場合、紙吹雪でお祝いする演出🎉
- 自動スコア計算機能(YESの数から★を算出)
- GA4導入可能(モーダル記事ページ、アイコンクリック、ネタバレ展開をトラッキング)
- ブログの利用方法を解説するページを別途表示可能
- サイト内検索機能
なぜClaudeを使ったか
このプロジェクトでは、AI(Claude)を開発パートナーとして積極的に活用しました。他社AIとの比較検討の結果、Claude(Maxプラン)を選択しました。
※慣れてやり取り回数を減らせればClaude(Proプラン)でも回せるかもしれません。
他社AI比較
AI | 料金 | 回数制限 | 指示遵守 | 1チャット内一貫性 | 制約・問題点 | 評価 |
---|---|---|---|---|---|---|
Claude(Max) | $100/月 | 普通 | ✅ 指示に忠実 | ✅ 高い | チャット移行で完全リセット | ⭐⭐⭐⭐⭐ |
Claude(Pro) | $20/月 | やや少ない | ✅ 指示に忠実 | ✅ 高い | チャット移行で完全リセット | ⭐⭐⭐⭐ |
ChatGPT(Plus) | $20/月 | 多い | ❌ 余計な修正多発 | ❌ 先祖返り頻発 | 指示無視、コンプラ過敏 | ⭐⭐ |
Gemini | 無料~ | 普通 | 普通 | 普通 | コード生成が不安定 | ⭐⭐ |
Copilot | $10/月 | - | 普通 | - | 単発補完メイン | ⭐⭐⭐ |
ChatGPTからの移行理由
当初はChatGPTを使用していましたが、以下の問題が開発効率を著しく阻害したため移行を決断しました。
- 勝手な改変: 「頼まれた事以外をしないで」と明確に指示しても、デザインを変更したり関係ない機能を追加
- 制約無視: カスタム指示に制約を書いても、「一般的なベストプラクティス」を優先して無視。カスタム指示の文字数制限により、詳細な制約条件を設定できない
- 先祖返り現象: 1チャット内でも過去の修正内容を忘れ、以前の古いコードを再提案
- コンプライアンス過敏: 厳しいフィードバックに対して「違反の可能性」として回答中断
Claude選択のメリット・デメリット
メリット
- 指示遵守の精度: 制約条件を明確に守り、指定範囲内での最適解を一貫して提案
- 技術的正確性: できることとできないことを正確に判断し、実装可能な代替案を提示
- 建設的な対話: フィードバックに感情的にならず、技術的な議論を継続
- 1チャット内の継続性: 長時間のやり取りでも過去の文脈と制約を適切に保持
デメリット
- 回数制限の厳しさ: Pro版でも比較的少ない回数制限で頻繁にチャット移行が必要(ChatGPTは月額3000円プランでもClaudeの倍以上やり取りできる)
- 完全リセット問題: 新しいチャットでは過去の文脈が完全に失われ、毎回経緯を説明し直す必要あり
- 長文生成エラー: 大きなファイルの生成時にエラーが発生しやすいため、分割回答を依頼する必要あり
Claude Codeではなく通常のClaudeを選択した理由
Claude Codeも検討しましたが、以下の理由から通常のClaudeを選択しました。
- 学習コスト: /init、/review等の多数のスラッシュコマンドを覚える必要があるが、シンプルなWebサイト開発には過剰
- コスト面: 1日5~10ドル(場合によっては1時間100ドル超)で小規模開発には高額
- プロジェクトの特性: HTMLテンプレート全体を俯瞰する必要があり、Webインターフェースでの視覚的確認とファイルアップロード機能が有効
Claudeで制作した時の工夫
AIを効果的に活用するため、以下の工夫を行いました。
1. 厳密な品質管理の徹底
AIが生成したコードはまるまる信用せず必ず検証し、事実と異なる回答には即座にフィードバックを行いました。「実装しました」という回答に対しても「該当する記述が見当たりませんが、具体的にどの部分ですか?」と確認し、正確な成果物を作ることを心がけました。
2. 回数制限対策としてのコンテキスト管理
Claudeの最大の弱点である回数制限に対する対策をしました。
- プロジェクトの全容説明: 新しいチャットの冒頭で、サイトの目的・技術構成・制約条件を簡潔にまとめて提示
- 現在の状況の明確化: 「現在はここまで実装済み、今回はこの機能を追加したい」という現状認識の共有
- 制約条件の再設定: 削除禁止、コメントアウト必須、指定拡張子にて3ファイル分割でのアーティファクト生成等のルールを毎回明記
3. 保守性を重視した開発指示
- 全体設計への配慮: 「その場しのぎの解決ではなく、コード全体を見て、一方の修正がもう一方に悪影響を与えないように」という横断的な視点を要求
- 変更履歴の明確化: 削除ではなくコメントアウトを指示し、コードの移動時は元の場所と移動先の両方に経緯を記録
- 曖昧さの排除: 行数指定ではなく「具体的に何が書かれたコードか」を明記させ、「最終的に何をすればいいか」を明確化
4. 責任範囲の明確化
AIに対して「頼まれた事以外をしない」「既存コードを参考に実装し、センスでアレンジしない」と制約を設けつつ、「プログラマとしての懸念点は必ず報告せよ」と技術的な気づきは積極的に求めました。
5. エラー対策とワークフロー最適化
- 3ファイル分割: 長文生成時のエラーを避けるため、HTML/CSS/JavaScriptを分割して生成
- 段階的検証: 「全部を生成し直す必要がない場合は、修正前・修正後の形式で回答」として効率化
- 重複削除の慎重な判断: 変数/関数の使用箇所とスコープを明記させ、削除による影響を事前評価
なぜBloggerを選んだか
他プラットフォーム比較表
プラットフォーム | カスタムHTML/CSS/JS | 無料枠 | 広告 | 評価 |
---|---|---|---|---|
Blogger | ✅ 完全自由 | ✅ 無制限 | ✅ なし | ⭐⭐⭐⭐⭐ |
GitHub Pages | ✅ 完全自由 | ✅ 無制限 | ✅ なし | ⭐⭐⭐ |
はてなブログ | △ 一部のみ | △ 制限あり | ❌ あり | ⭐⭐ |
WordPress.com | ❌ 有料版のみ | △ 制限あり | ❌ あり | ⭐⭐ |
note | ❌ 不可 | ✅ 無制限 | ✅ なし | ⭐ |
Notion | ❌ 不可 | △ 制限あり | ✅ なし | ⭐ |
- 完全無料でHTML/CSS/JavaScript を自由に編集可能
- Google製でGA4との相性が良い
- 記事の更新が簡単(GitHub Pagesは毎回コミットが必要)
実装した機能
1. アイコンベースの評価システム
「便器」「ゲロ」といった直接的な文字を見るだけで不快感を覚える人もいるため、🚽や🤢などの絵文字をアイコンに使用し、便器が映るか、嘔吐描写があるか、などを判別するデザインに。
絵文字の横に✓を書けば「YES(その描写が含まれる)」、✗を書けば「NO(その描写は含まれない)」の表示になるように設計。
<!-- 記事投稿時のHTML -->
<span class="icon-wrapper">
🚽✓ <!-- ✓でYES(含まれる) -->
<span class="icon-tooltip">便器の映り込みがある</span>
</span>
<span class="icon-wrapper">
🤢✗ <!-- ✗でNO(含まれない) -->
<span class="icon-tooltip">嘔吐シーンなし</span>
</span>
JavaScriptで絵文字と記号を分離し、YES/NO表示に変換しました
// アイコンのテキストを取得(絵文字と記号を分離)
const iconText = wrapper.childNodes[0].textContent.trim();
const hasCheck = iconText.includes('✓');
const hasCross = iconText.includes('✗');
const emoji = iconText.replace(/[✓✗]/g, '').trim();
// YES/NO表示を作成
if (hasCheck) {
noSpan.style.display = 'none'; // YESのみ表示
} else if (hasCross) {
yesSpan.style.display = 'none'; // NOのみ表示
}
▽完成形
2. GA4によるユーザー行動分析
// アイコンクリック時のトラッキング
function trackIconClick(iconType, hasContent) {
if (typeof gtag !== 'undefined') {
gtag('event', 'icon_click', {
'event_category': 'interaction',
'icon_type': iconType, // 例:'🚽'
'has_content': hasContent ? 'yes' : 'no'
});
}
}
// ネタバレ展開時のトラッキング
details.addEventListener('toggle', function() {
if (details.open) {
const movieTitle = titleElement.textContent;
gtag('event', 'spoiler_open', {
'event_category': 'engagement',
'movie_title': movieTitle
});
}
});
3. 不潔要素なし映画への特別演出
安全な映画であることを即座に分かるよう、紙吹雪でお祝いしつつ評価項目を無効化。
// 全てNOの場合、紙吹雪でお祝い
if (yesCount === 0 && totalCount > 0) {
const noDirtyDiv = document.createElement('div');
noDirtyDiv.className = 'no-dirty-elements sparkle';
noDirtyDiv.textContent = '不潔要素なし';
// 紙吹雪演出(canvas-confettiライブラリ使用)
confetti({
particleCount: 100,
spread: 70,
origin: { y: 0.3 },
colors: ['#FFD700', '#FF69B4', '#00CED1']
});
}
▽完成形
4. BloggerのAPI活用
<!-- Bloggerタグで動的にコンテンツ取得 -->
<Blogger>
<div class="post-data-temp" data-post-url="<$BlogItemPermalinkURL$>">
<$BlogItemBody$>
</div>
</Blogger>
// JavaScriptで記事を整形してグリッド表示
filteredElements.reverse().forEach(function(tempElement, index) {
const postUrl = tempElement.getAttribute('data-post-url');
// グリッドに追加
mainOrArchivePage.insertBefore(postDiv, mainOrArchivePage.firstChild);
});
5. レスポンシブデザインの工夫
/* CSS変数でカラム数管理 */
:root {
--mobile-columns: 3;
--desktop-columns: 4;
}
/* clamp関数で画面幅に応じたフォントサイズ */
#site-title {
font-size: clamp(12px, 7vw, 48px);
}
/* iPhoneノッチ対応 */
body {
padding-top: env(safe-area-inset-top, 0px);
}
6. パフォーマンス最適化
/* GPU活用アニメーション */
.bounce-once {
animation: bounce 0.5s ease 2;
will-change: transform;
transform: translateZ(0); /* GPU強制 */
backface-visibility: hidden;
}
// スクロールイベントの最適化
let isScrolling = false;
window.addEventListener('scroll', function() {
if (!isScrolling) {
window.requestAnimationFrame(function() {
// 処理
isScrolling = false;
});
isScrolling = true;
}
});
実装方針の転換:モーダルから個別ページへ
当初の実装(モーダル方式)
最初は全ての記事を/
ページ内でモーダル表示する設計にしていました。
// 当初:クリックでモーダル表示
imgClone.addEventListener('click', function(e) {
e.preventDefault();
showModal(postData); // モーダルで記事内容を表示
});
▽当時の様子
発覚した問題点
運用を考えると致命的な問題が複数あることに気づきました。
1. SEOの完全な無効化
- 記事ごとに設定したSEOタグが機能しない
- 全てのアクセスが
/
に集約され、個別記事がインデックスされない - 「映画名 潔癖症」で検索してもヒットしない
2. ユーザー体験の問題
- 記事への直接リンクが不可能
- ブックマークができない
- SNSでの記事シェアが不可能
3. GA4分析の限界
- ページビューが全て
/
として記録 - どの記事が人気か、標準レポートでは判別不可
解決策:個別記事ページへの移行
// 改善後:個別記事URLへ遷移
imgClone.addEventListener('click', function(e) {
e.preventDefault();
const postUrl = postDiv.getAttribute('data-post-url');
if (postUrl) {
window.location.href = postUrl; // 個別ページへ遷移
}
});
この変更により以下を実現。
- ✅ 記事ごとのSEO最適化が有効に
- ✅ 個別記事への直接アクセス可能
- ✅ GA4で正確なページ分析が可能
学んだ教訓
「技術的に可能」と「適切な実装」は違う
JavaScriptで制御すれば確かに記事ごとの分析は可能でしたが、SEOやUXを考慮すると個別ページ方式が圧倒的に優れていました。
効果的なAI活用のプロトコル例
1. 要件は具体的に箇条書き
❌ 曖昧な指示:「いい感じのレビューサイト作って」
✅ 明確な指示:
「以下の機能を実装したい
- 9つの衛生面評価項目を絵文字アイコンで表示
- アイコンクリックでツールチップ表示
- 不潔要素がない場合は紙吹雪演出
- GA4でユーザー行動をトラッキング」
2. 制約条件を最初に伝える
前提条件:
- 環境:Blogger(クラシックテーマ)
- 修正は最小限に:その場しのぎではなく、全体への影響を考慮
- 削除は禁止:削除する場合は必ずコメントアウト
- 既存コメントは保持:すでにあるコメントは消さない
- 移動時は明記:コードを移動する場合は元の場所をコメントアウト
- 変更内容を明記:追記・上書き・削除(コメントアウト)を明確に
- デザイン変更禁止:頼まれたこと以外はしない
- コードを横断的に見て、必要最小限の修正に留める
生成方法:
3ファイル(.txtファイル)に分割してアーティファクト生成
・ファイル1: HTMLの開始から </style> タグまで
・ファイル2: <script> タグから </head> タグまで
・ファイル3: <body> タグから HTMLの終了まで
3. 段階的な実装とフィードバック
1. 基本構造の実装 → 動作確認
2. 個別機能(アイコン、ツールチップ) → 動作確認
3. GA4統合 → 動作確認
4. 最適化(GPU活用、スクロール処理)
4. コードで事実確認
AI:「〇〇を実装しました」
私:「該当する記述が無いように見えますが、どこですか?」
AI:「失礼しました、実装していませんでした」
生成されたコードは必ず検証し、事実と異なる回答には即座にフィードバック。コードを証拠として提示することで、正確な議論ができました。
つまずいたポイント
1. Bloggerの制約
- クラシックテーマでないと自由度が低い
- 日本語の技術情報がほぼない
- 運営が実質停滞している
2. SEO問題
6月に公開したが、未だに検索結果に表示されない。
実装済:
- ✅ サイトマップ送信(feeds/posts/default)
- ✅ Search Console登録
- ✅ 個別記事のインデックスリクエスト
- ✅ 構造化データ実装(記事ページのReview schema)
- ✅ OGPタグ設定
できていないこと:
- ❌ 記事の文字数増加(現在は短文レビュー)
- ❌ 内部リンクの構築
- ❌ 被リンク獲得(このQiita記事が初めて)
- ❌ SNSでの拡散
- ❌ ページ表示速度の最適化
- ❌ 完全なアクセシビリティ対応
3. コードの複雑化
記事投稿時の絵文字に✓✗を付けるだけでYES/NO判定する仕組みは便利だが、JavaScriptでの処理が複雑になった。
今後の改善点
- SEO対策(被リンク獲得、コンテンツ充実)
- ラベル機能で「嘔吐あり/なし」のフィルタリング
- PWA化でオフライン対応
- 画像の遅延読み込み実装
- その他UI改善
まとめ
Bloggerは制約もありますが、無料で自由度の高いカスタマイズが可能です。特にGA4との連携や、JavaScriptを使った動的な機能実装には最適でした。
また、AIを効果的に活用することで、一人でも複雑な機能を実装できました。重要なのは、AIに丸投げするのではなく、明確な要件定義と段階的な実装、そして必ずコードを検証することだと感じました。
潔癖症や嘔吐恐怖症の方が安心して映画を楽しめるよう、今後もコンテンツを充実させていきます!
★この記事もClaudeを使って執筆しました。