<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>マイ・リトル・インコ - インコそだてシミュレーション</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- FontAwesome Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Kiwi+Maru:wght@300;400;500&family=Yusei+Magic&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Kiwi Maru', sans-serif;
background: linear-gradient(135deg, #fef3c7 0%, #f9f5f0 100%);
}
.font-magic {
font-family: 'Yusei Magic', sans-serif;
}
/* アニメーション */
@keyframes bobbing {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-6px); }
}
@keyframes eating {
0%, 100% { transform: rotate(0deg) translateY(0); }
30% { transform: rotate(12deg) translateY(3px); }
70% { transform: rotate(-3deg) translateY(1px); }
}
.bird-idle {
animation: bobbing 3s ease-in-out infinite;
}
.bird-eating {
animation: eating 0.4s ease-in-out 3;
}
/* ふきだしの三角 */
.speech-bubble::after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
border-width: 10px 10px 0;
border-style: solid;
border-color: #ffffff transparent;
display: block;
width: 0;
}
/* エフェクトアニメーション */
@keyframes float-up {
0% { transform: translateY(0) scale(0.8); opacity: 1; }
100% { transform: translateY(-40px) scale(1.2); opacity: 0; }
}
.effect-particle {
position: absolute;
animation: float-up 1s ease-out forwards;
pointer-events: none;
}
</style>
</head>
<body class="min-h-screen flex flex-col items-center justify-center p-4">
<div class="w-full max-w-2xl bg-white rounded-3xl shadow-2xl overflow-hidden border-4 border-amber-200">
<!-- ヘッダー -->
<header class="bg-amber-100 p-4 border-b-2 border-amber-200 text-center relative">
<h1 class="text-2xl font-bold text-amber-800 font-magic flex items-center justify-center gap-2">
<i class="fa-solid fa-feather-pointed text-amber-500"></i>
マイ・リトル・インコ
</h1>
<p class="text-xs text-amber-600 mt-1">〜 とまり木から はじまる、小さな ともだちとの 日々 〜</p>
</header>
<!-- メイン -->
<main class="p-6">
<!-- STEP 1: ケージのじゅんび -->
<section id="step-cage" class="space-y-6">
<div class="text-center">
<span class="bg-amber-400 text-amber-950 text-xs font-bold px-3 py-1 rounded-full">ステップ 1</span>
<h2 class="text-xl font-bold text-gray-800 mt-2">インコのおうち(ケージ)を じゅんび しよう!</h2>
<p class="text-sm text-gray-600 mt-1">インコが あんしんして くらせる おうちを つくります。</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- ケージプレビュー -->
<div class="bg-blue-50 rounded-2xl p-4 border-2 border-dashed border-blue-200 flex flex-col items-center justify-center min-h-[250px] relative">
<span class="absolute top-2 left-3 text-xs text-blue-400 font-bold"><i class="fa-solid fa-eye mr-1"></i>おうちの ようす</span>
<!-- ケージ外枠 -->
<div id="preview-cage-frame" class="w-48 h-56 border-4 border-slate-300 rounded-t-full relative flex items-end justify-center pb-6 bg-white shadow-inner">
<div class="absolute top-0 w-1 h-3 bg-slate-400 rounded-t-full -mt-3"></div>
<!-- 縦の格子線 -->
<div class="absolute inset-x-0 top-6 bottom-0 flex justify-around pointer-events-none opacity-20">
<div class="w-0.5 bg-slate-500 h-full"></div>
<div class="w-0.5 bg-slate-500 h-full"></div>
<div class="w-0.5 bg-slate-500 h-full"></div>
<div class="w-0.5 bg-slate-500 h-full"></div>
</div>
<!-- 止まり木 -->
<div id="preview-perch" class="absolute bottom-16 w-36 h-3 bg-amber-700 rounded-full shadow-sm"></div>
<!-- おもちゃ (ベル or ブランコ) -->
<div id="preview-toy" class="absolute top-4 inset-x-0 mx-auto w-20 h-20 flex justify-center items-start hidden">
<!-- ベル -->
<span id="preview-toy-bell" class="text-2xl hidden">🔔</span>
<!-- ブランコ (リアルSVG) -->
<div id="preview-toy-swing" class="w-16 h-16 flex flex-col items-center hidden mt-1">
<svg width="40" height="50" viewBox="0 0 40 50">
<line x1="20" y1="0" x2="20" y2="10" stroke="#94a3b8" stroke-width="2"/>
<path d="M 8 42 A 12 12 0 0 1 32 42" fill="none" stroke="#b45309" stroke-width="2.5"/>
<rect x="4" y="40" width="32" height="4" rx="1" fill="#78350f"/>
</svg>
</div>
</div>
<!-- エサ・水入れ -->
<div class="absolute bottom-4 left-4 w-6 h-4 bg-yellow-500 rounded-b-md border border-yellow-600"></div>
<div class="absolute bottom-4 right-4 w-6 h-4 bg-sky-400 rounded-b-md border border-sky-500"></div>
</div>
</div>
<!-- えらぶ項目 -->
<div class="space-y-4 flex flex-col justify-center">
<div>
<label class="block text-sm font-bold text-gray-700 mb-1"><i class="fa-solid fa-tree text-emerald-600 mr-1"></i>とまり木(そざい)</label>
<div class="grid grid-cols-2 gap-2">
<button onclick="selectPerch('natural')" id="btn-perch-natural" class="px-3 py-2 text-xs border-2 border-amber-500 bg-amber-50 rounded-xl text-amber-800 font-bold">
🌳 てんねんの木
</button>
<button onclick="selectPerch('straight')" id="btn-perch-straight" class="px-3 py-2 text-xs border border-gray-300 rounded-xl text-gray-600 hover:bg-gray-50">
🪵 まっすぐな丸太
</button>
</div>
</div>
<div>
<label class="block text-sm font-bold text-gray-700 mb-1"><i class="fa-solid fa-icons text-purple-600 mr-1"></i>ケージの おもちゃ</label>
<div class="grid grid-cols-3 gap-2">
<button onclick="selectToy('none')" id="btn-toy-none" class="px-2 py-2 text-xs border-2 border-amber-500 bg-amber-50 rounded-xl text-amber-800 font-bold">
なし
</button>
<button onclick="selectToy('bell')" id="btn-toy-bell" class="px-2 py-2 text-xs border border-gray-300 rounded-xl text-gray-600 hover:bg-gray-50">
🔔 ベル
</button>
<button onclick="selectToy('swing')" id="btn-toy-swing" class="px-2 py-2 text-xs border border-gray-300 rounded-xl text-gray-600 hover:bg-gray-50">
🪵 ブランコ
</button>
</div>
</div>
<div>
<label class="block text-sm font-bold text-gray-700 mb-1"><i class="fa-solid fa-palette text-rose-500 mr-1"></i>したに しく 紙の いろ</label>
<div class="flex gap-3">
<button onclick="selectPaper('#ffffff')" class="w-8 h-8 rounded-full border-2 border-gray-300 bg-white focus:ring-2 focus:ring-amber-400" title="白"></button>
<button onclick="selectPaper('#fef3c7')" class="w-8 h-8 rounded-full border-2 border-gray-300 bg-amber-100 focus:ring-2 focus:ring-amber-400" title="きいろ"></button>
<button onclick="selectPaper('#dcfce7')" class="w-8 h-8 rounded-full border-2 border-gray-300 bg-green-100 focus:ring-2 focus:ring-amber-400" title="みどり"></button>
</div>
</div>
</div>
</div>
<div class="text-center pt-4">
<button onclick="finishStepCage()" class="px-8 py-3 bg-gradient-to-r from-amber-500 to-yellow-500 text-white font-bold rounded-full shadow-lg hover:from-amber-600 hover:to-yellow-600 transition-all transform hover:scale-105">
おうち かんせい!インコを えらびに行く <i class="fa-solid fa-arrow-right ml-1"></i>
</button>
</div>
</section>
<!-- STEP 2: インコえらび -->
<section id="step-choose" class="space-y-6 hidden">
<div class="text-center">
<span class="bg-amber-400 text-amber-950 text-xs font-bold px-3 py-1 rounded-full">ステップ 2</span>
<h2 class="text-xl font-bold text-gray-800 mt-2">いっしょに くらす インコを えらぼう!</h2>
<p class="text-sm text-gray-600 mt-1">すきな インコを えらんで、なまえを つけてね。</p>
</div>
<!-- インコえらび -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<!-- セキセイインコ -->
<div onclick="selectBirdType('sekisei')" id="card-sekisei" class="border-2 border-amber-400 bg-amber-50/50 rounded-2xl p-4 cursor-pointer hover:shadow-md transition-all text-center flex flex-col items-center justify-between">
<div class="w-20 h-20 rounded-full bg-emerald-100 flex items-center justify-center text-3xl mb-2 relative">🟢</div>
<div>
<h3 class="font-bold text-gray-800">セキセイインコ</h3>
<p class="text-[10px] text-gray-500 mt-1">おしゃべり大好き。カラフルな模様が特徴。</p>
</div>
</div>
<!-- コザクラインコ -->
<div onclick="selectBirdType('kozakura')" id="card-kozakura" class="border border-gray-200 rounded-2xl p-4 cursor-pointer hover:shadow-md transition-all text-center flex flex-col items-center justify-between">
<div class="w-20 h-20 rounded-full bg-rose-100 flex items-center justify-center text-3xl mb-2 relative">🍑</div>
<div>
<h3 class="font-bold text-gray-800">コザクラインコ</h3>
<p class="text-[10px] text-gray-500 mt-1">愛情深い、ラブバード。顔の赤がチャーム。</p>
</div>
</div>
<!-- オカメインコ -->
<div onclick="selectBirdType('okame')" id="card-okame" class="border border-gray-200 rounded-2xl p-4 cursor-pointer hover:shadow-md transition-all text-center flex flex-col items-center justify-between">
<div class="w-20 h-20 rounded-full bg-yellow-100 flex items-center justify-center text-3xl mb-2 relative">🐤</div>
<div>
<h3 class="font-bold text-gray-800">オカメインコ</h3>
<p class="text-[10px] text-gray-500 mt-1">ほっぺのチークが可愛い、歌うのが大好き。</p>
</div>
</div>
<!-- マメルリハ -->
<div onclick="selectBirdType('mameru')" id="card-mameru" class="border border-gray-200 rounded-2xl p-4 cursor-pointer hover:shadow-md transition-all text-center flex flex-col items-center justify-between">
<div class="w-20 h-20 rounded-full bg-blue-100 flex items-center justify-center text-3xl mb-2 relative">💧</div>
<div>
<h3 class="font-bold text-gray-800">マメルリハ</h3>
<p class="text-[10px] text-gray-500 mt-1">小さな体に秘めたパワー。好奇心旺盛!</p>
</div>
</div>
<!-- ウロコインコ -->
<div onclick="selectBirdType('uroko')" id="card-uroko" class="border border-gray-200 rounded-2xl p-4 cursor-pointer hover:shadow-md transition-all text-center flex flex-col items-center justify-between">
<div class="w-20 h-20 rounded-full bg-amber-100 flex items-center justify-center text-3xl mb-2 relative">☀️</div>
<div>
<h3 class="font-bold text-gray-800">ウロコインコ</h3>
<p class="text-[10px] text-gray-500 mt-1">懐っこい性格。お腹の鱗模様がとっても綺麗。</p>
</div>
</div>
<!-- ボタンインコ -->
<div onclick="selectBirdType('botan')" id="card-botan" class="border border-gray-200 rounded-2xl p-4 cursor-pointer hover:shadow-md transition-all text-center flex flex-col items-center justify-between">
<div class="w-20 h-20 rounded-full bg-red-100 flex items-center justify-center text-3xl mb-2 relative">🏮</div>
<div>
<h3 class="font-bold text-gray-800">ボタンインコ</h3>
<p class="text-[10px] text-gray-500 mt-1">鮮やかな羽色と、目の周りの白いリング。</p>
</div>
</div>
</div>
<!-- 名前入力 -->
<div class="max-w-xs mx-auto text-center space-y-2 pt-4">
<label class="block text-sm font-bold text-gray-700">インコの なまえ</label>
<input type="text" id="input-bird-name" value="ピーちゃん" maxlength="10" class="w-full text-center px-4 py-2 border-2 border-amber-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 text-lg font-bold">
</div>
<div class="text-center">
<button onclick="finishStepChoose()" class="px-8 py-3 bg-gradient-to-r from-emerald-500 to-teal-500 text-white font-bold rounded-full shadow-lg hover:from-emerald-600 hover:to-teal-600 transition-all transform hover:scale-105">
おうちにお迎えする! <i class="fa-solid fa-home ml-1"></i>
</button>
</div>
</section>
<!-- STEP 3: メイン育成ゲーム画面 -->
<section id="step-game" class="space-y-6 hidden">
<!-- セーブロード・ステータスバー -->
<div class="flex flex-col sm:flex-row justify-between items-center bg-amber-50 p-4 rounded-2xl border border-amber-100 gap-3">
<div class="grid grid-cols-2 md:grid-cols-4 gap-2 text-center w-full sm:w-auto flex-1">
<div>
<span class="text-xs text-amber-600 block">なまえ</span>
<span id="stat-name" class="font-bold text-gray-800 text-sm md:text-base">-</span>
</div>
<div>
<span class="text-xs text-amber-600 block">日数</span>
<span id="stat-days" class="font-bold text-gray-800 text-sm md:text-base">1日目</span>
</div>
<div>
<span class="text-xs text-amber-600 block">おもさ(たいじゅう)</span>
<span id="stat-weight" class="font-bold text-gray-800 text-sm md:text-base">-</span>
</div>
<div>
<span class="text-xs text-amber-600 block">おぼえた ことば</span>
<span id="stat-words" class="font-bold text-gray-800 text-sm md:text-base">0語</span>
</div>
</div>
<!-- セーブ・ロードボタン -->
<div class="flex gap-2 w-full sm:w-auto justify-center">
<button onclick="saveGameData()" class="px-4 py-2 bg-emerald-500 hover:bg-emerald-600 text-white font-bold text-xs rounded-xl shadow transition-all flex items-center gap-1">
<i class="fa-solid fa-floppy-disk"></i> セーブ
</button>
<button onclick="loadGameData()" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white font-bold text-xs rounded-xl shadow transition-all flex items-center gap-1">
<i class="fa-solid fa-circle-arrow-down"></i> ロード
</button>
</div>
</div>
<!-- メインケージ・インコ表示エリア -->
<div class="bg-sky-50 rounded-2xl p-6 border-2 border-sky-100 flex flex-col items-center justify-center min-h-[360px] relative overflow-hidden">
<!-- ふきだし -->
<div id="bird-speech" class="absolute top-4 bg-white px-4 py-2 rounded-2xl shadow-md border-2 border-amber-200 text-sm font-bold text-amber-800 speech-bubble transition-all duration-300 opacity-0 transform translate-y-2 z-10">
ピピッ!
</div>
<!-- エフェクト -->
<div id="effect-layer" class="absolute inset-0 pointer-events-none"></div>
<!-- おうち -->
<div id="game-cage-frame" class="w-64 h-80 border-4 border-slate-300 rounded-t-full relative flex items-end justify-center pb-8 bg-white/80 shadow-md">
<!-- 天井フック -->
<div class="absolute top-0 w-1 h-3 bg-slate-400 rounded-t-full -mt-3"></div>
<!-- 格子 -->
<div class="absolute inset-x-0 top-8 bottom-0 flex justify-around pointer-events-none opacity-10">
<div class="w-0.5 bg-slate-500 h-full"></div>
<div class="w-0.5 bg-slate-500 h-full"></div>
<div class="w-0.5 bg-slate-500 h-full"></div>
<div class="w-0.5 bg-slate-500 h-full"></div>
<div class="w-0.5 bg-slate-500 h-full"></div>
</div>
<!-- おもちゃ (ベル or ブランコ) -->
<div id="game-toy-area" class="absolute top-10 inset-x-0 mx-auto w-24 h-24 flex justify-center items-start">
<span id="game-toy-bell" class="text-3xl hidden">🔔</span>
<!-- リアルなブランコ(SVG) -->
<div id="game-toy-swing" class="w-20 h-20 flex flex-col items-center hidden">
<svg width="60" height="70" viewBox="0 0 60 70">
<!-- つり下げチェーン -->
<line x1="30" y1="0" x2="30" y2="15" stroke="#94a3b8" stroke-width="2"/>
<!-- ブランコのアーチ部分 -->
<path d="M 12 55 A 18 18 0 0 1 48 55" fill="none" stroke="#d97706" stroke-width="3"/>
<!-- ビーズの飾り -->
<circle cx="13" cy="45" r="4" fill="#ef4444"/>
<circle cx="47" cy="45" r="4" fill="#3b82f6"/>
<!-- 止まり木部分 -->
<rect x="6" y="52" width="48" height="6" rx="2" fill="#78350f"/>
</svg>
</div>
</div>
<!-- 止まり木 -->
<div id="game-perch" class="absolute bottom-20 w-48 h-4 bg-amber-700 rounded-full shadow-md z-0"></div>
<!-- 食器たち -->
<div class="absolute bottom-6 left-6 flex flex-col items-center">
<div id="food-level" class="w-5 h-2 bg-yellow-600 rounded-full absolute bottom-1 transition-all duration-300"></div>
<div class="w-7 h-5 bg-amber-500 rounded-b-md border border-amber-600 z-10 opacity-80"></div>
</div>
<div class="absolute bottom-6 right-6 flex flex-col items-center">
<div id="water-level" class="w-5 h-2 bg-sky-300 rounded-full absolute bottom-1 transition-all duration-300"></div>
<div class="w-7 h-5 bg-sky-400 rounded-b-md border border-sky-500 z-10 opacity-80"></div>
</div>
<!-- リアルなインコ本体 -->
<div id="bird-character" class="bird-idle absolute bottom-[88px] transition-all duration-500 flex flex-col items-center z-20 cursor-pointer" onclick="tapBird()">
<span id="bird-emotion" class="absolute -top-10 text-xl transition-all duration-300 opacity-0">❤️</span>
<!-- シンプルで可愛いインコSVG -->
<svg id="bird-svg" width="90" height="95" viewBox="0 0 100 100" class="transition-all duration-300">
<!-- 尾羽 -->
<path id="svg-tail" d="M 40 70 L 50 95 L 60 70 Z" fill="#059669" />
<!-- 体 -->
<ellipse id="svg-body" cx="50" cy="65" rx="30" ry="35" fill="#10b981" />
<!-- お腹 -->
<ellipse id="svg-belly" cx="50" cy="70" rx="20" ry="25" fill="#a7f3d0" />
<!-- 頭 -->
<circle id="svg-head" cx="50" cy="35" r="25" fill="#10b981" />
<!-- ほっぺ -->
<circle id="svg-cheek-l" cx="35" cy="40" r="5" fill="#4f46e5" opacity="0.6" />
<circle id="svg-cheek-r" cx="65" cy="40" r="5" fill="#4f46e5" opacity="0.6" />
<!-- 目 -->
<circle cx="40" cy="30" r="4" fill="#111827" />
<circle cx="60" cy="30" r="4" fill="#111827" />
<!-- ろう膜 -->
<ellipse id="svg-cere" cx="50" cy="32" rx="6" ry="3" fill="#3b82f6" />
<!-- くちばし -->
<path d="M 45 35 L 55 35 L 50 45 Z" fill="#fbbf24" />
</svg>
</div>
</div>
<!-- エサ・水のじょうたい -->
<div class="mt-4 flex gap-4 text-xs">
<span id="msg-food-status" class="bg-yellow-100 text-yellow-800 px-3 py-1 rounded-full font-bold">🌾 エサ:満足</span>
<span id="msg-water-status" class="bg-sky-100 text-sky-800 px-3 py-1 rounded-full font-bold">💧 お水:きれい</span>
</div>
</div>
<!-- お世話ボタン -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-2">
<button onclick="feedBird()" class="py-3 px-4 bg-yellow-100 hover:bg-yellow-200 text-yellow-800 font-bold rounded-2xl border-2 border-yellow-300 transition-all flex items-center justify-center gap-1">
🌾 エサをあげる
</button>
<button onclick="giveWater()" class="py-3 px-4 bg-sky-100 hover:bg-sky-200 text-sky-800 font-bold rounded-2xl border-2 border-sky-300 transition-all flex items-center justify-center gap-1">
💧 水をかえる
</button>
<button onclick="playWithBird()" class="py-3 px-4 bg-rose-100 hover:bg-rose-200 text-rose-800 font-bold rounded-2xl border-2 border-rose-300 transition-all flex items-center justify-center gap-1">
🎈 なでて あそぶ
</button>
<button onclick="nextDay()" class="py-3 px-4 bg-emerald-600 hover:bg-emerald-700 text-white font-bold rounded-2xl transition-all flex items-center justify-center gap-1 shadow-md">
🌙 1日 すすめる
</button>
</div>
<!-- おしゃべりおべんきょう -->
<div class="bg-purple-50 p-4 rounded-2xl border border-purple-100 space-y-3">
<h3 class="font-bold text-purple-900 text-sm flex items-center gap-1">
<i class="fa-solid fa-graduation-cap"></i> おしゃべり れんしゅう
</h3>
<p class="text-xs text-purple-700">インコに おしえたい ことばを 入力して、「おしえる」を おしてね!毎日すこしずつ おぼえます。</p>
<div class="flex gap-2">
<input type="text" id="input-teach-word" placeholder="こんにちは!" maxlength="8" class="flex-1 px-3 py-2 border-2 border-purple-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 text-sm">
<button onclick="teachWord()" class="px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white text-sm font-bold rounded-xl transition-all">
おしえる
</button>
</div>
<!-- おぼえた言葉 -->
<div>
<span class="text-xs font-bold text-purple-800 block mb-1">いま おぼえている ことば:</span>
<div id="learned-words-container" class="flex flex-wrap gap-1">
<span class="text-xs bg-white text-purple-600 px-2 py-1 rounded-md border border-purple-100">ピピッ!</span>
</div>
</div>
</div>
<!-- 日記ログ -->
<div class="bg-gray-50 p-3 rounded-xl border border-gray-200">
<span class="text-xs text-gray-400 font-bold block mb-1">そだて日記(にっき)</span>
<div id="game-log" class="text-xs text-gray-600 h-16 overflow-y-auto space-y-1">
<div class="text-emerald-600">おうちへ ようこそ!これから なかよく くらそうね。</div>
</div>
</div>
</section>
</main>
<!-- フッター -->
<footer class="bg-gray-50 p-3 text-center border-t border-gray-100">
<p class="text-[10px] text-gray-400">© 2026 マイ・リトル・インコ シミュレーター</p>
</footer>
</div>
<!-- トースト -->
<div id="toast-message" class="fixed bottom-6 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white px-4 py-2 rounded-xl text-sm font-bold opacity-0 transition-opacity duration-300 pointer-events-none z-50">
メッセージ
</div>
<!-- Firebase SDK Modules -->
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
import { getAuth, signInWithCustomToken, signInAnonymously, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
import { getFirestore, doc, setDoc, getDoc } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
// --- Firebase 初期化 (環境のグローバル変数を使用) ---
const firebaseConfig = JSON.parse(__firebase_config);
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
let currentUser = null;
// 認証プロセス (RULE 3)
const initAuth = async () => {
try {
if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) {
await signInWithCustomToken(auth, __initial_auth_token);
} else {
await signInAnonymously(auth);
}
} catch (error) {
console.error("Authentication failed:", error);
}
};
initAuth();
onAuthStateChanged(auth, (user) => {
currentUser = user;
if (user) {
console.log("Authenticated user:", user.uid);
// 自動ロードの呼び出しも可能
tryAutoLoad();
}
});
// --- ゲームデータ保存 (セーブ) ---
window.saveGameData = async function() {
if (!currentUser) {
window.showToast("セーブするには ネットに せつぞくしてね。");
return;
}
try {
// 保存するパス (RULE 1)
const saveDocRef = doc(db, 'artifacts', appId, 'users', currentUser.uid, 'saves', 'current');
// セーブデータ作成
const saveData = {
cage: window.gameState.cage,
bird: window.gameState.bird,
day: window.gameState.day,
weight: window.gameState.weight,
food: window.gameState.food,
water: window.gameState.water,
love: window.gameState.love,
words: window.gameState.words,
updatedAt: new Date().toISOString()
};
await setDoc(saveDocRef, saveData);
window.showToast("データを クラウドに セーブしました!💾");
window.addLog("セーブしました。いつでも つづきから あそべます。");
} catch (e) {
console.error("Save error: ", e);
window.showToast("セーブに しっぱい しました。");
}
};
// --- ゲームデータ読込 (ロード) ---
window.loadGameData = async function() {
if (!currentUser) {
window.showToast("ロードするには ネットに せつぞくしてね。");
return;
}
try {
const saveDocRef = doc(db, 'artifacts', appId, 'users', currentUser.uid, 'saves', 'current');
const docSnap = await getDoc(saveDocRef);
if (docSnap.exists()) {
const loadedData = docSnap.data();
// gameStateに反映
window.gameState.cage = loadedData.cage;
window.gameState.bird = loadedData.bird;
window.gameState.day = loadedData.day;
window.gameState.weight = loadedData.weight;
window.gameState.food = loadedData.food;
window.gameState.water = loadedData.water;
window.gameState.love = loadedData.love;
window.gameState.words = loadedData.words || ['ピピッ!', 'ピヨ!'];
// UI表示の更新
window.applyLoadedGame();
window.showToast("クラウドから ロードしました!📂");
window.addLog(`セーブデータを ロードして ${window.gameState.day}日目から つづけます!`);
} else {
window.showToast("セーブデータが みつかりませんでした。");
}
} catch (e) {
console.error("Load error: ", e);
window.showToast("ロードに しっぱい しました。");
}
};
// 自動でロードをこころみる
async function tryAutoLoad() {
if (!currentUser) return;
const saveDocRef = doc(db, 'artifacts', appId, 'users', currentUser.uid, 'saves', 'current');
const docSnap = await getDoc(saveDocRef);
if (docSnap.exists()) {
window.addLog("前の そだてデータが クラウドに あります。「ロード」ボタンから つづきを あそべます。");
}
}
</script>
<!-- ゲームロジック用一般スクリプト -->
<script>
// --- ゲームの初期データ/状態管理 ---
window.gameState = {
cage: {
perch: 'natural',
toy: 'none',
paperColor: '#ffffff'
},
bird: {
type: 'sekisei',
name: 'ピーちゃん',
colorTheme: {
tail: '#059669',
body: '#10b981',
belly: '#a7f3d0',
head: '#10b981',
cheek: '#4f46e5',
cere: '#3b82f6' // ろう膜(鼻)の色
}
},
day: 1,
weight: 12.0,
food: 100,
water: 100,
love: 20,
words: ['ピピッ!', 'ピヨ!'],
teachingWord: '',
teachCount: 0,
isSleeping: false
};
// インコのカラーテーマ
const birdColors = {
sekisei: { tail: '#059669', body: '#10b981', belly: '#fde047', head: '#fde047', cheek: '#4f46e5', cere: '#3b82f6' },
kozakura: { tail: '#0284c7', body: '#16a34a', belly: '#a7f3d0', head: '#f43f5e', cheek: '#f43f5e', cere: '#fca5a5' },
okame: { tail: '#475569', body: '#64748b', belly: '#cbd5e1', head: '#fef08a', cheek: '#ea580c', cere: '#94a3b8' },
mameru: { tail: '#3b82f6', body: '#3b82f6', belly: '#bfdbfe', head: '#60a5fa', cheek: '#1e40af', cere: '#3b82f6' },
uroko: { tail: '#991b1b', body: '#15803d', belly: '#f59e0b', head: '#78350f', cheek: '#e11d48', cere: '#78350f' },
botan: { tail: '#2563eb', body: '#dc2626', belly: '#fbbf24', head: '#7f1d1d', cheek: '#b91c1c', cere: '#fecaca' }
};
const stepCage = document.getElementById('step-cage');
const stepChoose = document.getElementById('step-choose');
const stepGame = document.getElementById('step-game');
// --- STEP 1: おうちのじゅんび ---
function selectPerch(type) {
window.gameState.cage.perch = type;
const preview = document.getElementById('preview-perch');
const gamePerch = document.getElementById('game-perch');
document.getElementById('btn-perch-natural').className = type === 'natural'
? 'px-3 py-2 text-xs border-2 border-amber-500 bg-amber-50 rounded-xl text-amber-800 font-bold'
: 'px-3 py-2 text-xs border border-gray-300 rounded-xl text-gray-600 hover:bg-gray-50';
document.getElementById('btn-perch-straight').className = type === 'straight'
? 'px-3 py-2 text-xs border-2 border-amber-500 bg-amber-50 rounded-xl text-amber-800 font-bold'
: 'px-3 py-2 text-xs border border-gray-300 rounded-xl text-gray-600 hover:bg-gray-50';
if (type === 'natural') {
preview.style.height = '14px';
preview.style.borderRadius = '30% 70% 70% 30% / 50% 30% 70% 50%';
if(gamePerch) gamePerch.style.borderRadius = '30% 70% 70% 30% / 50% 30% 70% 50%';
} else {
preview.style.height = '12px';
preview.style.borderRadius = '9999px';
if(gamePerch) gamePerch.style.borderRadius = '9999px';
}
}
function selectToy(type) {
window.gameState.cage.toy = type;
// ボタンデザイン切り替え
['none', 'bell', 'swing'].forEach(t => {
const btn = document.getElementById(`btn-toy-${t}`);
if(t === type) {
btn.className = 'px-2 py-2 text-xs border-2 border-amber-500 bg-amber-50 rounded-xl text-amber-800 font-bold';
} else {
btn.className = 'px-2 py-2 text-xs border border-gray-300 rounded-xl text-gray-600 hover:bg-gray-50';
}
});
// プレビューの切り替え
const previewToy = document.getElementById('preview-toy');
const pBell = document.getElementById('preview-toy-bell');
const pSwing = document.getElementById('preview-toy-swing');
if (type === 'none') {
previewToy.classList.add('hidden');
} else {
previewToy.classList.remove('hidden');
if (type === 'bell') {
pBell.classList.remove('hidden');
pSwing.classList.add('hidden');
} else {
pBell.classList.add('hidden');
pSwing.classList.remove('hidden');
}
}
// ゲーム画面の切り替え
updateGameToyUI();
}
function updateGameToyUI() {
const gBell = document.getElementById('game-toy-bell');
const gSwing = document.getElementById('game-toy-swing');
const type = window.gameState.cage.toy;
if (type === 'none') {
if(gBell) gBell.classList.add('hidden');
if(gSwing) gSwing.classList.add('hidden');
} else if (type === 'bell') {
if(gBell) gBell.classList.remove('hidden');
if(gSwing) gSwing.classList.add('hidden');
} else {
if(gBell) gBell.classList.add('hidden');
if(gSwing) gSwing.classList.remove('hidden');
}
}
function selectPaper(color) {
window.gameState.cage.paperColor = color;
document.getElementById('preview-cage-frame').style.backgroundColor = color;
const gameCage = document.getElementById('game-cage-frame');
if (gameCage) {
gameCage.style.backgroundColor = color + 'cc';
}
}
function finishStepCage() {
showToast('ケージのじゅんび 完了!');
stepCage.classList.add('hidden');
stepChoose.classList.remove('hidden');
}
// --- STEP 2: インコえらび ---
function selectBirdType(type) {
window.gameState.bird.type = type;
window.gameState.bird.colorTheme = birdColors[type];
['sekisei', 'kozakura', 'okame', 'mameru', 'uroko', 'botan'].forEach(t => {
const card = document.getElementById(`card-${t}`);
if (t === type) {
card.classList.add('border-amber-400', 'bg-amber-50/50');
card.classList.remove('border-gray-200');
} else {
card.classList.remove('border-amber-400', 'bg-amber-50/50');
card.classList.add('border-gray-200');
}
});
}
function finishStepChoose() {
const nameInput = document.getElementById('input-bird-name').value.trim();
if (!nameInput) {
showToast('なまえを入力してね!');
return;
}
window.gameState.bird.name = nameInput;
initGameScreen();
stepChoose.classList.add('hidden');
stepGame.classList.remove('hidden');
showToast(`${window.gameState.bird.name}を お迎えしました!`);
addLog(`かわいい ${window.gameState.bird.name}が やってきた!いっぱい なかよくしてね。`);
}
// --- STEP 3: メイン育成ゲーム ---
function initGameScreen() {
document.getElementById('stat-name').innerText = window.gameState.bird.name;
updateStatusUI();
applyBirdColor();
updateGameToyUI();
document.getElementById('game-cage-frame').style.backgroundColor = window.gameState.cage.paperColor + 'd0';
updateLearnedWordsUI();
}
// インコSVGへの着色
function applyBirdColor() {
const colors = window.gameState.bird.colorTheme;
document.getElementById('svg-tail').setAttribute('fill', colors.tail);
document.getElementById('svg-body').setAttribute('fill', colors.body);
document.getElementById('svg-belly').setAttribute('fill', colors.belly);
document.getElementById('svg-head').setAttribute('fill', colors.head);
document.getElementById('svg-cheek-l').setAttribute('fill', colors.cheek);
document.getElementById('svg-cheek-r').setAttribute('fill', colors.cheek);
document.getElementById('svg-cere').setAttribute('fill', colors.cere);
}
// ステータスUIのこうしん
function updateStatusUI() {
document.getElementById('stat-days').innerText = `${window.gameState.day}日目`;
document.getElementById('stat-weight').innerText = `${window.gameState.weight.toFixed(1)}g`;
document.getElementById('stat-words').innerText = `${window.gameState.words.length}語`;
const foodLvl = document.getElementById('food-level');
foodLvl.style.height = `${window.gameState.food / 15}px`;
const foodMsg = document.getElementById('msg-food-status');
if (window.gameState.food > 60) {
foodMsg.innerText = '🌾 エサ:満足';
foodMsg.className = 'bg-yellow-100 text-yellow-800 px-3 py-1 rounded-full font-bold';
} else if (window.gameState.food > 20) {
foodMsg.innerText = '🌾 エサ:普通';
foodMsg.className = 'bg-amber-100 text-amber-800 px-3 py-1 rounded-full font-bold';
} else {
foodMsg.innerText = '🌾 エサ:おなかペコペコ';
foodMsg.className = 'bg-rose-100 text-rose-800 px-3 py-1 rounded-full font-bold animate-pulse';
}
const waterLvl = document.getElementById('water-level');
waterLvl.style.height = `${window.gameState.water / 15}px`;
const waterMsg = document.getElementById('msg-water-status');
if (window.gameState.water > 50) {
waterMsg.innerText = '💧 お水:きれい';
waterMsg.className = 'bg-sky-100 text-sky-800 px-3 py-1 rounded-full font-bold';
} else {
waterMsg.innerText = '💧 お水:よごれている';
waterMsg.className = 'bg-blue-200 text-blue-900 px-3 py-1 rounded-full font-bold animate-pulse';
}
// 成長スケール
const minW = 12.0;
const maxW = 40.0;
let scale = 0.6 + ((window.gameState.weight - minW) / (maxW - minW)) * 0.5;
if (scale > 1.25) scale = 1.25;
document.getElementById('bird-svg').style.transform = `scale(${scale})`;
}
// エサをあげる
function feedBird() {
if (window.gameState.isSleeping) {
showToast('インコは ねむっています💤');
return;
}
if (window.gameState.food >= 100) {
showToast('もう おなかいっぱいです!');
return;
}
window.gameState.food = Math.min(100, window.gameState.food + 40);
window.gameState.love = Math.min(100, window.gameState.love + 5);
triggerBirdAnimation('bird-eating');
spawnEffect('🌾');
spawnEffect('❤️');
addLog(`${window.gameState.bird.name}は うれしそうに エサをつついた。`);
updateStatusUI();
setTimeout(() => {
saySomething('モグモグ…おいしい!');
}, 1000);
}
// 水をかえる
function giveWater() {
if (window.gameState.isSleeping) {
showToast('インコは ねむっています💤');
return;
}
if (window.gameState.water >= 100) {
showToast('お水は すでに きれいです。');
return;
}
window.gameState.water = 100;
window.gameState.love = Math.min(100, window.gameState.love + 3);
spawnEffect('💧');
addLog('お水を あたらしく とりかえました。');
updateStatusUI();
setTimeout(() => {
saySomething('コクコク…ぷはぁ!');
}, 500);
}
// なでる・遊ぶ
function playWithBird() {
if (window.gameState.isSleeping) {
showToast('インコは ねむっています💤');
return;
}
window.gameState.love = Math.min(100, window.gameState.love + 10);
spawnEffect('❤️');
spawnEffect('🎶');
addLog(`${window.gameState.bird.name}を なでてあげた。うれしそうに している。`);
showEmotion('❤️');
setTimeout(() => {
const randWord = window.gameState.words[Math.floor(Math.random() * window.gameState.words.length)];
saySomething(randWord);
}, 600);
}
function tapBird() {
if (window.gameState.isSleeping) {
showEmotion('💤');
saySomething('スースー…');
return;
}
playWithBird();
}
// ことばを教える
function teachWord() {
if (window.gameState.isSleeping) {
showToast('インコは ねむっています💤');
return;
}
const input = document.getElementById('input-teach-word');
const word = input.value.trim();
if (!word) {
showToast('おしえたい ことばを 入力してね!');
return;
}
if (window.gameState.words.includes(word)) {
showToast('その ことばは もう おぼえているよ!');
return;
}
if (window.gameState.teachingWord !== word) {
window.gameState.teachingWord = word;
window.gameState.teachCount = 0;
}
window.gameState.teachCount++;
spawnEffect('🎓');
addLog(`${window.gameState.bird.name}に「${word}」と はなしかけた。`);
if (window.gameState.teachCount >= 3) {
window.gameState.words.push(word);
window.gameState.teachingWord = '';
window.gameState.teachCount = 0;
spawnEffect('🎉');
addLog(`【おしゃべり大成功】なんと!新しく「${word}」を おぼえた!`);
showToast(`「${word}」を おぼえました!`);
updateLearnedWordsUI();
updateStatusUI();
setTimeout(() => {
saySomething(`${word}!!`);
}, 800);
} else {
setTimeout(() => {
const progressWord = word.substring(0, Math.ceil(word.length / 2)) + '…?';
saySomething(progressWord);
}, 800);
}
input.value = '';
}
function updateLearnedWordsUI() {
const container = document.getElementById('learned-words-container');
container.innerHTML = '';
window.gameState.words.forEach(word => {
const span = document.createElement('span');
span.className = 'text-xs bg-white text-purple-600 px-2 py-1 rounded-md border border-purple-100 font-semibold shadow-sm';
span.innerText = word;
container.appendChild(span);
});
}
// 1日進める
function nextDay() {
if (window.gameState.isSleeping) return;
window.gameState.isSleeping = true;
addLog(`よるに なりました。お部屋を くらくして 寝かせます…🌙`);
const cage = document.getElementById('game-cage-frame');
cage.style.filter = 'brightness(0.4)';
showEmotion('💤');
setTimeout(() => {
window.gameState.day++;
let weightGain = 0;
if (window.gameState.food > 40) {
if (window.gameState.weight < 35.0) {
weightGain = 2.5 + Math.random() * 1.5; // ひな鳥の成長
} else {
weightGain = 0.1 + (Math.random() - 0.5) * 0.3; // 成鳥の安定
}
} else {
weightGain = -1.0;
addLog('ちゅうい:ごはんが 足りなくて、少しやせてしまったようです…');
}
window.gameState.weight = Math.max(10.0, Math.min(45.0, window.gameState.weight + weightGain));
window.gameState.food = Math.max(10, window.gameState.food - 35);
window.gameState.water = Math.max(10, window.gameState.water - 40);
window.gameState.isSleeping = false;
cage.style.filter = 'brightness(1)';
addLog(`あさになりました!${window.gameState.day}日目が はじまります。☀️`);
if (window.gameState.weight >= 35.0 && window.gameState.day > 5) {
addLog(`${window.gameState.bird.name}は すっかり おとなの インコになりました!`);
}
updateStatusUI();
showEmotion('☀️');
setTimeout(() => {
const randWord = window.gameState.words[Math.floor(Math.random() * window.gameState.words.length)];
saySomething(randWord + ' おはよ!');
}, 1000);
}, 3000);
}
// ロードしたデータを画面に適用する
window.applyLoadedGame = function() {
// 画面切り替え(ケージ準備、お迎え画面だったらゲーム画面へ)
stepCage.classList.add('hidden');
stepChoose.classList.add('hidden');
stepGame.classList.remove('hidden');
initGameScreen();
};
// --- エフェクト・表示ヘルパー ---
let speechTimeout;
function saySomething(text) {
const bubble = document.getElementById('bird-speech');
bubble.innerText = text;
bubble.classList.remove('opacity-0', 'translate-y-2');
bubble.classList.add('opacity-100', 'translate-y-0');
clearTimeout(speechTimeout);
speechTimeout = setTimeout(() => {
bubble.classList.remove('opacity-100', 'translate-y-0');
bubble.classList.add('opacity-0', 'translate-y-2');
}, 3000);
}
function triggerBirdAnimation(className) {
const bird = document.getElementById('bird-character');
bird.classList.remove('bird-idle');
bird.classList.add(className);
setTimeout(() => {
bird.classList.remove(className);
bird.classList.add('bird-idle');
}, 1500);
}
let emotionTimeout;
function showEmotion(emoji) {
const emo = document.getElementById('bird-emotion');
emo.innerText = emoji;
emo.classList.remove('opacity-0');
emo.classList.add('opacity-100');
clearTimeout(emotionTimeout);
emotionTimeout = setTimeout(() => {
emo.classList.remove('opacity-100');
emo.classList.add('opacity-0');
}, 2000);
}
function spawnEffect(emoji) {
const layer = document.getElementById('effect-layer');
const particle = document.createElement('div');
particle.className = 'effect-particle text-xl font-bold';
const randomX = 40 + (Math.random() * 20);
const randomY = 60 + (Math.random() * 10);
particle.style.left = `${randomX}%`;
particle.style.top = `${randomY}%`;
particle.innerText = emoji;
layer.appendChild(particle);
setTimeout(() => {
particle.remove();
}, 1000);
}
window.addLog = function(text) {
const log = document.getElementById('game-log');
const div = document.createElement('div');
div.innerText = `● ${text}`;
log.appendChild(div);
log.scrollTop = log.scrollHeight;
}
window.showToast = function(msg) {
const toast = document.getElementById('toast-message');
toast.innerText = msg;
toast.classList.remove('opacity-0');
toast.classList.add('opacity-100');
setTimeout(() => {
toast.classList.remove('opacity-100');
toast.classList.add('opacity-0');
}, 2500);
}
</script>
</body>
</html>
インコを育てるゲーム