<!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 CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Google Fonts for Cute Japanese Typography -->
<link href="https://fonts.googleapis.com/css2?family=Kiwi+Maru:wght@400;500;700&display=swap" rel="stylesheet">
<!-- FontAwesome Icons for UI -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
body {
font-family: 'Kiwi Maru', 'Murecho', sans-serif;
user-select: none;
-webkit-user-select: none;
}
/* なめらかなイージングアニメーション */
.smooth-transition {
transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275), filter 0.3s ease, background 0.8s ease;
}
/* 吹き出しの三角 */
.speech-bubble::after {
content: '';
position: absolute;
bottom: -12px;
left: 50%;
transform: translateX(-50%);
border-width: 12px 12px 0;
border-style: solid;
border-color: #ffffff transparent;
display: block;
width: 0;
}
/* 背景デコレーション関連のCSS */
.bg-dots {
background-image: radial-gradient(rgba(255, 255, 255, 0.75) 18%, transparent 18%),
radial-gradient(rgba(255, 255, 255, 0.75) 18%, transparent 18%);
background-size: 40px 40px;
background-position: 0 0, 20px 20px;
}
.bg-stars-pattern {
background-image: radial-gradient(rgba(254, 240, 138, 0.6) 12%, transparent 12%),
radial-gradient(rgba(254, 243, 199, 0.5) 18%, transparent 18%);
background-size: 50px 50px;
background-position: 0 0, 25px 25px;
}
/* シャボン玉の上昇アニメーション */
@keyframes float-bubble {
0% { transform: translateY(110vh) translateX(0) scale(0.6); opacity: 0; }
10% { opacity: 0.85; }
90% { opacity: 0.85; }
100% { transform: translateY(-10vh) translateX(50px) scale(1.2); opacity: 0; }
}
.bubble-decor {
position: absolute;
border-radius: 50%;
background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.95) 0%, rgba(255, 170, 190, 0.7) 45%, rgba(100, 190, 255, 0.6) 100%);
box-shadow: inset -3px -3px 8px rgba(255, 255, 255, 0.8), 0 6px 12px rgba(0, 0, 0, 0.08);
pointer-events: none;
z-index: 1;
}
/* おひさま・おつきさまの浮遊モーション */
@keyframes sky-friend-float {
0%, 100% { transform: translateY(0px) rotate(0deg); }
50% { transform: translateY(-8px) rotate(3deg); }
}
.sky-friend {
animation: sky-friend-float 4s infinite ease-in-out;
}
/* しっぽ通常フリフリアニメーション */
@keyframes wag {
0%, 100% { transform: rotate(-5deg); }
50% { transform: rotate(5deg); }
}
.wagging {
animation: wag 0.15s infinite alternate ease-in-out;
}
@keyframes tail-wag {
0%, 100% { transform: rotate(-10deg) translate(0px, 0px); }
50% { transform: rotate(20deg) translate(-5px, 2px); }
}
.tail-wagging {
transform-origin: 290px 320px;
animation: tail-wag 0.2s infinite alternate ease-in-out;
}
/* ちょこちょこ自律歩行用のアニメーション */
@keyframes walk-bounce {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-6px) rotate(1deg); }
}
@keyframes walk-leg-l {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-12px) rotate(-15deg); }
}
@keyframes walk-leg-r {
0%, 100% { transform: translateY(-12px) rotate(15deg); }
50% { transform: translateY(0) rotate(0deg); }
}
@keyframes walk-tail {
0%, 100% { transform: rotate(-5deg); }
50% { transform: rotate(18deg); }
}
.walking-shiba {
animation: walk-bounce 0.4s infinite ease-in-out;
}
.walking-leg-left {
transform-origin: 164px 325px;
animation: walk-leg-l 0.4s infinite ease-in-out !important;
}
.walking-leg-right {
transform-origin: 236px 325px;
animation: walk-leg-r 0.4s infinite ease-in-out !important;
}
.walking-tail {
transform-origin: 290px 320px;
animation: walk-tail 0.4s infinite ease-in-out !important;
}
/* ふわふわ浮遊エフェクト */
@keyframes float-up {
0% { transform: translateY(20px) scale(0.8); opacity: 0; }
20% { opacity: 1; }
100% { transform: translateY(-120px) scale(1.5); opacity: 0; }
}
.effect-particle {
position: absolute;
animation: float-up 1.2s forwards cubic-bezier(0.1, 0.8, 0.3, 1);
pointer-events: none;
z-index: 50;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.15));
}
/* ぷにぷに振動 */
@keyframes purupuru {
0%, 100% { transform: scale(1); }
20% { transform: scale(1.08, 0.92); }
40% { transform: scale(0.95, 1.05); }
60% { transform: scale(1.03, 0.97); }
80% { transform: scale(0.98, 1.01); }
}
.purupuru-anim {
animation: purupuru 0.6s ease-out;
}
/* ボタンホバー */
.cute-btn {
transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.cute-btn:hover {
transform: scale(1.1) rotate(2deg);
}
.cute-btn:active {
transform: scale(0.95);
}
</style>
</head>
<body class="bg-gradient-to-b from-sky-200 via-pink-100 to-amber-100 min-h-screen flex flex-col justify-between overflow-hidden relative smooth-transition">
<!-- パターン装飾背景 -->
<div id="bg-pattern" class="absolute inset-0 pointer-events-none bg-dots opacity-65 transition-all duration-700"></div>
<!-- シャボン玉上昇のコンテナ -->
<div id="bg-bubbles" class="absolute inset-0 pointer-events-none overflow-hidden z-0"></div>
<!-- 背景演出 (星・光など) -->
<div id="bg-stars" class="absolute inset-0 pointer-events-none opacity-60 z-0"></div>
<!-- 空のともだち(おひさま・おつきさま) -->
<div class="absolute top-20 right-10 md:right-20 pointer-events-none z-0 w-24 h-24 sky-friend">
<!-- 可愛い太陽 (おひる用) -->
<svg id="sky-sun" viewBox="0 0 100 100" class="w-full h-full drop-shadow-md transition-all duration-700 transform scale-100">
<defs>
<radialGradient id="sun-grad" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fef08a" />
<stop offset="100%" stop-color="#f97316" />
</radialGradient>
</defs>
<g stroke="#f97316" stroke-width="4" stroke-linecap="round">
<line x1="50" y1="10" x2="50" y2="2" />
<line x1="50" y1="90" x2="50" y2="98" />
<line x1="10" y1="50" x2="2" y2="50" />
<line x1="90" y1="50" x2="98" y2="50" />
<line x1="22" y1="22" x2="16" y2="16" />
<line x1="78" y1="78" x2="84" y2="84" />
<line x1="22" y1="78" x2="16" y2="84" />
<line x1="78" y1="22" x2="84" y2="16" />
</g>
<circle cx="50" cy="50" r="28" fill="url(#sun-grad)" />
<circle cx="42" cy="46" r="3.5" fill="#431407" />
<circle cx="58" cy="46" r="3.5" fill="#431407" />
<ellipse cx="38" cy="51" rx="4" ry="2.5" fill="#f43f5e" opacity="0.6" />
<ellipse cx="62" cy="51" rx="4" ry="2.5" fill="#f43f5e" opacity="0.6" />
<path d="M46 54 Q 50 58 54 54" stroke="#431407" stroke-width="3" stroke-linecap="round" fill="none" />
</svg>
<!-- 可愛い月 (よなか用) -->
<svg id="sky-moon" viewBox="0 0 100 100" class="w-full h-full drop-shadow-md transition-all duration-700 transform scale-0 absolute top-0 left-0">
<defs>
<radialGradient id="moon-grad" cx="40%" cy="40%" r="60%">
<stop offset="0%" stop-color="#fef08a" />
<stop offset="100%" stop-color="#eab308" />
</radialGradient>
</defs>
<path d="M35 15 A35 35 0 1 0 85 65 A28 28 0 1 1 35 15 Z" fill="url(#moon-grad)" />
<path d="M42 48 Q46 52 50 48" stroke="#713f12" stroke-width="2.5" stroke-linecap="round" fill="none" />
<path d="M56 54 Q60 58 64 54" stroke="#713f12" stroke-width="2.5" stroke-linecap="round" fill="none" />
<circle cx="40" cy="53" r="4" fill="#fda4af" opacity="0.7" />
<path d="M34 15 Q24 5 15 12 Q20 20 28 22 Z" fill="#6366f1" />
<circle cx="13" cy="11" r="3.5" fill="#ffffff" />
</svg>
</div>
<!-- ヘッダー / タイトル -->
<header class="w-full text-center py-4 px-6 z-10 flex justify-between items-center bg-white/75 backdrop-blur-md shadow-sm border-b border-amber-100">
<div class="flex items-center gap-2">
<div class="w-10 h-10 rounded-full bg-amber-500 flex items-center justify-center text-white shadow-md">
<i class="fa-solid fa-dog text-lg"></i>
</div>
<h1 class="text-xl md:text-2xl font-bold text-amber-900 tracking-wider">ぷにぷに黒柴マスコット</h1>
</div>
<div class="flex items-center gap-3">
<button id="btn-sound" class="w-10 h-10 rounded-full bg-amber-100 hover:bg-amber-200 text-amber-800 flex items-center justify-center shadow-sm transition-all" title="音声を切り替え">
<i id="sound-icon" class="fa-solid fa-volume-high"></i>
</button>
<button id="btn-help" class="w-10 h-10 rounded-full bg-amber-100 hover:bg-amber-200 text-amber-800 flex items-center justify-center shadow-sm transition-all" title="あそびかた">
<i class="fa-solid fa-question"></i>
</button>
</div>
</header>
<!-- メイン表示エリア -->
<main class="flex-1 flex flex-col lg:flex-row items-center justify-center p-4 md:p-8 w-full gap-6 max-w-7xl mx-auto z-10">
<!-- 左側:アクション -->
<div class="flex lg:flex-col flex-row gap-3 justify-center w-full lg:w-auto overflow-x-auto py-2 px-1">
<div class="bg-white/80 backdrop-blur-md p-3 rounded-2xl shadow-md border border-amber-100 flex lg:flex-col gap-3 min-w-max">
<div class="text-xs text-amber-800 font-bold text-center border-b border-amber-100 pb-1 mb-1 hidden lg:block">
アクション
</div>
<button id="btn-trick-hand" class="cute-btn bg-amber-400 hover:bg-amber-500 text-amber-950 font-bold py-2.5 px-4 rounded-xl flex items-center gap-2 text-sm shadow-sm">
🐾 お手!
</button>
<button id="btn-trick-sing" class="cute-btn bg-emerald-400 hover:bg-emerald-500 text-emerald-950 font-bold py-2.5 px-4 rounded-xl flex items-center gap-2 text-sm shadow-sm">
🎵 歌って!
</button>
<button id="btn-trick-sleep" class="cute-btn bg-blue-400 hover:bg-blue-500 text-blue-950 font-bold py-2.5 px-4 rounded-xl flex items-center gap-2 text-sm shadow-sm">
💤 おねむ
</button>
</div>
</div>
<!-- 中央:黒柴本体 (ステージ) -->
<div id="main-stage" class="relative flex-1 flex items-center justify-center w-full max-w-lg aspect-square" style="perspective: 1000px;">
<!-- 黒柴 SVG 構成 (このコンテナごとちょこちょこ自律的に歩くようになります) -->
<div id="shiba-container" class="w-5/6 h-5/6 relative flex items-center justify-center select-none cursor-grab active:cursor-grabbing transition-transform duration-200" style="pointer-events: auto;">
<!-- なでなで判定オーバーレイ & 骨ドラッグ受け皿 (歩いてもズレないよう中に内包) -->
<div id="interaction-overlay" class="absolute inset-0 z-30"></div>
<!-- 吹き出し (歩く黒柴に連動して付いていきます) -->
<div id="speech-bubble" class="speech-bubble absolute -top-10 bg-white border-2 border-amber-300 text-amber-900 px-6 py-3 rounded-2xl shadow-lg text-sm md:text-base font-bold scale-0 transition-transform duration-300 origin-bottom z-40 max-w-xs text-center">
クゥ〜ン🐾 なでてほしいワン!
</div>
<svg id="shiba-svg" viewBox="0 0 400 400" class="w-full h-full drop-shadow-xl overflow-visible transition-transform duration-300" style="transform-origin: center;">
<defs>
<!-- 唐草模様パターンの定義 -->
<pattern id="karakusa" width="40" height="40" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<rect width="40" height="40" fill="#2e7d32" />
<path d="M20 10 A10 10 0 1 1 10 20 A5 5 0 1 0 15 25" fill="none" stroke="#ffffff" stroke-width="3" stroke-linecap="round"/>
<path d="M0 30 A10 10 0 1 1 30 40" fill="none" stroke="#ffffff" stroke-width="3" stroke-linecap="round"/>
<circle cx="10" cy="10" r="2.5" fill="#ffffff" />
<circle cx="30" cy="30" r="2.5" fill="#ffffff" />
</pattern>
</defs>
<!-- しっぽ (後ろ側) -->
<g id="shiba-tail" class="tail-wagging">
<path d="M270 330 C 310 320, 340 280, 330 250 C 320 220, 280 230, 260 260 C 250 280, 260 310, 270 330 Z" fill="#2c2a29" />
<path d="M330 250 C 327 235, 305 235, 290 248 C 285 253, 305 265, 320 260 C 325 258, 329 254, 330 250 Z" fill="#faf6f0" />
<path d="M295 280 C 315 270, 325 255, 310 245 C 300 240, 285 260, 285 270 Z" fill="#d27c3c" />
</g>
<!-- 体と前足 -->
<g id="shiba-body">
<ellipse cx="200" cy="340" rx="95" ry="55" fill="#2c2a29" />
<ellipse cx="200" cy="335" rx="65" ry="40" fill="#faf6f0" />
<path d="M135 320 C 135 340, 150 360, 160 365 C 150 345, 140 330, 135 320 Z" fill="#d27c3c" />
<path d="M265 320 C 265 340, 250 360, 240 365 C 250 345, 260 330, 265 320 Z" fill="#d27c3c" />
<!-- 前足 左 -->
<g id="leg-left">
<rect x="150" y="325" width="28" height="60" rx="14" fill="#2c2a29" />
<ellipse cx="164" cy="382" rx="15" ry="10" fill="#faf6f0" />
</g>
<!-- 前足 右 -->
<g id="leg-right">
<rect x="222" y="325" width="28" height="60" rx="14" fill="#2c2a29" />
<ellipse cx="236" cy="382" rx="15" ry="10" fill="#faf6f0" />
</g>
</g>
<!-- バンダナ / リボン (首元) -->
<g id="shiba-neckwear" class="smooth-transition">
<path id="item-bandana" d="M135 295 Q 200 345 265 295 Q 200 305 135 295 Z" fill="url(#karakusa)" />
<path id="item-bandana-knot" d="M135 295 Q 125 300 120 290 Q 130 285 135 295 Z M265 295 Q 275 300 280 290 Q 270 285 265 295 Z" fill="url(#karakusa)" />
<g id="item-ribbon" class="hidden">
<path d="M170 305 Q 200 320 230 305 Q 215 285 200 300 Q 185 285 170 305 Z" fill="#e11d48" />
<circle cx="200" cy="298" r="8" fill="#facc15" />
<path d="M170 305 Q 150 315 155 330 Q 175 320 175 310 Z" fill="#e11d48" />
<path d="M230 305 Q 250 315 245 330 Q 225 320 225 310 Z" fill="#e11d48" />
</g>
</g>
<!-- 頭部全体グループ -->
<g id="shiba-head" class="smooth-transition" style="transform-origin: 200px 240px;">
<!-- 耳 左 -->
<g id="ear-left">
<path d="M110 170 Q 115 105 145 115 Q 175 125 170 175 Z" fill="#2c2a29" />
<path d="M123 163 Q 127 122 143 128 Q 158 135 155 165 Z" fill="#fca5a5" />
<path d="M110 170 Q 130 170 140 160 Q 120 150 115 170 Z" fill="#d27c3c" />
</g>
<!-- 耳 右 -->
<g id="ear-right">
<path d="M290 170 Q 285 105 255 115 Q 225 125 230 175 Z" fill="#2c2a29" />
<path d="M277 163 Q 273 122 257 128 Q 242 135 245 165 Z" fill="#fca5a5" />
<path d="M290 170 Q 270 170 260 160 Q 280 150 285 170 Z" fill="#d27c3c" />
</g>
<!-- 輪郭ベース -->
<ellipse cx="200" cy="215" rx="95" ry="85" fill="#2c2a29" />
<!-- まろ眉 (お顔の中心寄りに調整済み:168 & 232) -->
<ellipse id="maro-left" cx="168" cy="165" rx="12" ry="9" fill="#d27c3c" class="smooth-transition" style="transform-origin: 168px 165px;" />
<ellipse id="maro-right" cx="232" cy="165" rx="12" ry="9" fill="#d27c3c" class="smooth-transition" style="transform-origin: 232px 165px;" />
<!-- ほっぺ裏白 -->
<path id="cheek-bg-left" d="M110 220 Q 95 240 120 270 Q 160 290 200 275 Q 165 210 110 220 Z" fill="#faf6f0" />
<path id="cheek-bg-right" d="M290 220 Q 305 240 280 270 Q 240 290 200 275 Q 235 210 290 220 Z" fill="#faf6f0" />
<ellipse cx="200" cy="250" rx="42" ry="32" fill="#faf6f0" />
<!-- 左ほっぺ (もちもち引っ張れる) -->
<g id="cheek-left-group" class="smooth-transition" style="transform-origin: 120px 245px;">
<ellipse cx="125" cy="245" rx="28" ry="24" fill="#faf6f0" />
<ellipse id="blush-left" cx="120" cy="245" rx="15" ry="10" fill="#fca5a5" opacity="0.75" class="smooth-transition" />
</g>
<!-- 右ほっぺ -->
<g id="cheek-right-group" class="smooth-transition" style="transform-origin: 280px 245px;">
<ellipse cx="275" cy="245" rx="28" ry="24" fill="#faf6f0" />
<ellipse id="blush-right" cx="280" cy="245" rx="15" ry="10" fill="#fca5a5" opacity="0.75" class="smooth-transition" />
</g>
<!-- おめめ (真ん中に寄せた超キュートな黄金バランス:168 & 232) -->
<g id="eye-left-group" class="smooth-transition">
<circle cx="168" cy="195" r="15" fill="#2c2a29" />
<circle id="eye-left-ball" cx="168" cy="195" r="13" fill="#111111" />
<circle id="eye-left-pupil" cx="166" cy="193" r="5" fill="#ffffff" />
<circle id="eye-left-sub-pupil" cx="171" cy="199" r="2.5" fill="#ffffff" />
<path id="eye-left-happy" d="M153 200 Q 168 180 183 200" fill="none" stroke="#2c2a29" stroke-width="5" stroke-linecap="round" class="hidden" />
<path id="eye-left-sleep" d="M153 193 Q 168 208 183 193" fill="none" stroke="#2c2a29" stroke-width="5" stroke-linecap="round" class="hidden" />
</g>
<g id="eye-right-group" class="smooth-transition">
<circle cx="232" cy="195" r="15" fill="#2c2a29" />
<circle id="eye-right-ball" cx="232" cy="195" r="13" fill="#111111" />
<circle id="eye-right-pupil" cx="230" cy="193" r="5" fill="#ffffff" />
<circle id="eye-right-sub-pupil" cx="235" cy="199" r="2.5" fill="#ffffff" />
<path id="eye-right-happy" d="M217 200 Q 232 180 247 200" fill="none" stroke="#2c2a29" stroke-width="5" stroke-linecap="round" class="hidden" />
<path id="eye-right-sleep" d="M217 193 Q 232 208 247 193" fill="none" stroke="#2c2a29" stroke-width="5" stroke-linecap="round" class="hidden" />
</g>
<!-- 鼻と口 -->
<g id="shiba-snout">
<path d="M190 232 Q 200 223 210 232 Q 200 240 190 232 Z" fill="#111111" />
<ellipse cx="196" cy="229" rx="2" ry="1" fill="#ffffff" opacity="0.8" />
<path id="mouth-default" d="M185 246 Q 200 256 200 246 Q 200 256 215 246" fill="none" stroke="#2c2a29" stroke-width="4.5" stroke-linecap="round" />
<path id="mouth-open" d="M185 246 Q 200 270 215 246 Q 200 242 185 246 Z" fill="#881337" class="hidden" />
<path id="mouth-tongue" d="M192 254 Q 200 268 208 254 Z" fill="#fda4af" class="hidden" />
</g>
<!-- お着替え -->
<g id="item-glasses" class="hidden" stroke="#9a3412" stroke-width="5" fill="none">
<circle cx="168" cy="195" r="24" />
<circle cx="232" cy="195" r="24" />
<path d="M192 195 Q 200 190 208 195" />
<path d="M144 195 L 128 190" />
<path d="M256 195 L 272 190" />
</g>
<g id="item-hat" class="hidden">
<ellipse cx="200" cy="115" rx="55" ry="20" fill="#78350f" />
<path d="M150 115 C 150 65, 250 65, 250 115 Z" fill="#78350f" />
<path d="M149 110 Q 200 118 251 110 L 250 115 Q 200 123 150 115 Z" fill="#f59e0b" />
</g>
<g id="item-crown" class="hidden" fill="#facc15" stroke="#ca8a04" stroke-width="2">
<path d="M165 120 L 175 80 L 190 100 L 200 70 L 210 100 L 225 80 L 235 120 Z" />
<circle cx="175" cy="80" r="3" fill="#ef4444" />
<circle cx="200" cy="70" r="4" fill="#3b82f6" />
<circle cx="225" cy="80" r="3" fill="#10b981" />
</g>
</g>
</svg>
</div>
</div>
<!-- 右側:おやつをドラッグしてあげるエリア&着せ替え・設定 -->
<div class="w-full lg:w-80 flex flex-col gap-4">
<!-- おやつパレット -->
<div class="bg-white/80 backdrop-blur-md p-4 rounded-2xl shadow-md border border-amber-100">
<h3 class="text-sm font-bold text-amber-900 mb-2.5 flex items-center gap-1.5">
<i class="fa-solid fa-cookie-bite text-amber-500"></i> おやつをドラッグしてあげてね
</h3>
<div class="grid grid-cols-4 gap-2">
<div id="food-bone" class="bg-amber-50 hover:bg-amber-100 border border-amber-200 rounded-xl p-2 flex flex-col items-center justify-center cursor-grab active:cursor-grabbing transition-transform hover:scale-105" draggable="true">
<span class="text-2xl mb-1">🦴</span>
<span class="text-[10px] font-bold text-amber-900">ほね</span>
</div>
<div id="food-cookie" class="bg-amber-50 hover:bg-amber-100 border border-amber-200 rounded-xl p-2 flex flex-col items-center justify-center cursor-grab active:cursor-grabbing transition-transform hover:scale-105" draggable="true">
<span class="text-2xl mb-1">🍪</span>
<span class="text-[10px] font-bold text-amber-900">クッキー</span>
</div>
<div id="food-strawberry" class="bg-amber-50 hover:bg-amber-100 border border-amber-200 rounded-xl p-2 flex flex-col items-center justify-center cursor-grab active:cursor-grabbing transition-transform hover:scale-105" draggable="true">
<span class="text-2xl mb-1">🍓</span>
<span class="text-[10px] font-bold text-amber-900">いちご</span>
</div>
<div id="food-onigiri" class="bg-amber-50 hover:bg-amber-100 border border-amber-200 rounded-xl p-2 flex flex-col items-center justify-center cursor-grab active:cursor-grabbing transition-transform hover:scale-105" draggable="true">
<span class="text-2xl mb-1">🍙</span>
<span class="text-[10px] font-bold text-amber-900">おむすび</span>
</div>
</div>
</div>
<!-- お着替えパネル -->
<div class="bg-white/80 backdrop-blur-md p-4 rounded-2xl shadow-md border border-amber-100">
<h3 class="text-sm font-bold text-amber-900 mb-2.5 flex items-center gap-1.5">
<i class="fa-solid fa-shirt text-amber-500"></i> おきがえ
</h3>
<div class="flex flex-wrap gap-2">
<button id="toggle-bandana" class="cute-btn bg-amber-100 hover:bg-amber-200 text-amber-900 text-xs font-bold py-1.5 px-3 rounded-lg border border-amber-200">
🧣 バンダナ / リボン
</button>
<button id="toggle-glasses" class="cute-btn bg-amber-100 hover:bg-amber-200 text-amber-900 text-xs font-bold py-1.5 px-3 rounded-lg border border-amber-200">
👓 まるメガネ
</button>
<button id="toggle-hat" class="cute-btn bg-amber-100 hover:bg-amber-200 text-amber-900 text-xs font-bold py-1.5 px-3 rounded-lg border border-amber-200">
🎩 たんてい帽
</button>
<button id="toggle-crown" class="cute-btn bg-amber-100 hover:bg-amber-200 text-amber-900 text-xs font-bold py-1.5 px-3 rounded-lg border border-amber-200">
👑 クラウン
</button>
</div>
</div>
<!-- もようがえパネル (背景選択) -->
<div class="bg-white/80 backdrop-blur-md p-4 rounded-2xl shadow-md border border-amber-100">
<h3 class="text-sm font-bold text-amber-900 mb-2.5 flex items-center gap-1.5">
<i class="fa-solid fa-house text-amber-500"></i> もようがえ (背景)
</h3>
<div class="grid grid-cols-3 gap-2">
<button id="theme-day" class="cute-btn bg-sky-100 hover:bg-sky-200 text-sky-900 text-xs font-bold py-2 rounded-lg border border-sky-300">
☀️ おひる
</button>
<button id="theme-sunset" class="cute-btn bg-orange-100 hover:bg-orange-200 text-orange-900 text-xs font-bold py-2 rounded-lg border border-orange-300">
🌇 ゆうがた
</button>
<button id="theme-night" class="cute-btn bg-indigo-900 hover:bg-indigo-950 text-indigo-100 text-xs font-bold py-2 rounded-lg border border-indigo-950">
🌌 よなか
</button>
</div>
</div>
</div>
</main>
<!-- おそらの雲 -->
<div class="absolute bottom-16 left-0 right-0 h-20 pointer-events-none z-0 overflow-hidden">
<svg id="cloud-1" class="absolute w-28 h-14 -left-32 bottom-4 transition-all duration-[30s] linear opacity-0 overflow-visible" viewBox="0 0 100 50">
<path d="M 10 35 L 90 35 A 15 15 0 0 0 85 15 A 18 18 0 0 0 50 10 A 15 15 0 0 0 20 20 A 15 15 0 0 0 10 35" fill="rgba(255,255,255,0.98)" />
<ellipse cx="25" cy="28" rx="6" ry="3" fill="#fca5a5" opacity="0.8" />
<ellipse cx="75" cy="28" rx="6" ry="3" fill="#fca5a5" opacity="0.8" />
</svg>
<svg id="cloud-2" class="absolute w-36 h-18 -left-48 top-0 transition-all duration-[45s] linear opacity-0 overflow-visible" viewBox="0 0 100 50">
<path d="M 10 35 L 90 35 A 15 15 0 0 0 85 15 A 18 18 0 0 0 50 10 A 15 15 0 0 0 20 20 A 15 15 0 0 0 10 35" fill="rgba(255,255,255,0.92)" />
<ellipse cx="30" cy="28" rx="5" ry="2.5" fill="#fca5a5" opacity="0.75" />
<ellipse cx="70" cy="28" rx="5" ry="2.5" fill="#fca5a5" opacity="0.75" />
</svg>
</div>
<!-- フッター -->
<footer class="w-full text-center py-3 text-xs text-amber-800 bg-white/45 border-t border-amber-100/50 z-10">
<p>ほっぺを引っ張ったり、お顔を撫でてね!おやつをお口に運ぶと喜びます🐾 ちょこちょこ歩きまわるよ!</p>
</footer>
<!-- あそびかたヘルプモーダル -->
<div id="help-modal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4 hidden">
<div class="bg-white rounded-3xl p-6 max-w-sm w-full border-4 border-amber-400 shadow-2xl relative">
<button id="btn-close-help" class="absolute top-4 right-4 text-amber-900 hover:text-rose-500 text-xl">
<i class="fa-solid fa-circle-xmark"></i>
</button>
<h2 class="text-xl font-bold text-amber-900 mb-4 text-center">🐾 黒柴ちゃんとのあそびかた 🐾</h2>
<ul class="space-y-3 text-sm text-amber-950">
<li class="flex gap-2"><span class="text-base">👆</span><span><strong>ほっぺを引っ張る:</strong> 左右のふっくらほっぺをドラッグすると、びよーんと伸びるワン!</li>
<li class="flex gap-2"><span class="text-base">👋</span><span><strong>なでなで:</strong> お顔のあたりをマウスや指で優しくすりすり撫でると、うっとりするワン!</li>
<li class="flex gap-2"><span class="text-base">🍗</span><span><strong>おやつをあげる:</strong> おやつを黒柴ちゃんのお口までドラッグ&ドロップすると「もぐもぐ」食べるワン!</li>
<li class="flex gap-2"><span class="text-base">👔</span><span><strong>おきがえ:</strong> お気に入りの服やメガネをプレゼントしてオシャレさせよう!</li>
</ul>
<button id="btn-start-play" class="w-full mt-6 bg-amber-400 hover:bg-amber-500 text-amber-950 font-bold py-3 rounded-2xl shadow-md transition-all">
あそぶ!
</button>
</div>
</div>
<div id="toast-message" class="fixed bottom-20 left-1/2 transform -translate-x-1/2 bg-amber-900/90 text-white text-xs py-2 px-4 rounded-full shadow-md transition-opacity duration-300 opacity-0 pointer-events-none z-50"></div>
<!-- JavaScript制御部 -->
<script>
const state = {
isMuted: false,
isPetting: false,
isCheekDragging: null, // 'left', 'right', or null
dragStartX: 0,
dragStartY: 0,
cheekOffsetX: 0,
pettedMeter: 0,
lastPetTime: 0,
lastMouseX: 0,
lastMouseY: 0,
activeItems: {
bandana: true,
ribbon: false,
glasses: false,
hat: false,
crown: false
},
currentTheme: 'day',
isSleeping: false,
isSinging: false,
isTrickHand: false,
chewingTimer: null,
// 歩行システム用ステート
shibaX: 0, // 現在のX位置オフセット
shibaTargetX: 0, // 歩く先の目標X位置
isWalking: false, // 歩いているかどうか
walkDirection: 1, // 1: 右向き, -1: 左向き
shibaSpeed: 1.2 // 歩くスピード
};
let audioCtx = null;
function initAudio() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
}
function playSound(type) {
if (state.isMuted) return;
initAudio();
if (!audioCtx) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain);
gain.connect(audioCtx.destination);
const now = audioCtx.currentTime;
if (type === 'pet') {
osc.type = 'sine';
osc.frequency.setValueAtTime(320, now);
osc.frequency.exponentialRampToValueAtTime(170, now + 0.15);
gain.gain.setValueAtTime(0.15, now);
gain.gain.exponentialRampToValueAtTime(0.01, now + 0.15);
osc.start(now);
osc.stop(now + 0.15);
}
else if (type === 'stretch') {
osc.type = 'triangle';
osc.frequency.setValueAtTime(190, now);
osc.frequency.linearRampToValueAtTime(280, now + 0.2);
gain.gain.setValueAtTime(0.1, now);
gain.gain.exponentialRampToValueAtTime(0.01, now + 0.25);
osc.start(now);
osc.stop(now + 0.25);
}
else if (type === 'snap') {
osc.type = 'sine';
osc.frequency.setValueAtTime(300, now);
osc.frequency.exponentialRampToValueAtTime(420, now + 0.1);
gain.gain.setValueAtTime(0.15, now);
gain.gain.exponentialRampToValueAtTime(0.01, now + 0.1);
osc.start(now);
osc.stop(now + 0.1);
}
else if (type === 'eat') {
const osc2 = audioCtx.createOscillator();
const gain2 = audioCtx.createGain();
osc2.connect(gain2);
gain2.connect(audioCtx.destination);
osc2.type = 'triangle';
osc2.frequency.setValueAtTime(90, now);
osc2.frequency.linearRampToValueAtTime(60, now + 0.08);
gain2.gain.setValueAtTime(0.3, now);
gain2.gain.exponentialRampToValueAtTime(0.01, now + 0.08);
osc2.start(now);
osc2.stop(now + 0.08);
}
else if (type === 'happy') {
osc.type = 'triangle';
osc.frequency.setValueAtTime(900, now);
osc.frequency.exponentialRampToValueAtTime(1250, now + 0.1);
osc.frequency.exponentialRampToValueAtTime(450, now + 0.35);
gain.gain.setValueAtTime(0.18, now);
gain.gain.exponentialRampToValueAtTime(0.01, now + 0.35);
osc.start(now);
osc.stop(now + 0.35);
}
else if (type === 'sing') {
osc.type = 'sine';
osc.frequency.setValueAtTime(380, now);
osc.frequency.exponentialRampToValueAtTime(580, now + 0.3);
osc.frequency.linearRampToValueAtTime(620, now + 0.7);
osc.frequency.exponentialRampToValueAtTime(330, now + 1.2);
gain.gain.setValueAtTime(0.18, now);
gain.gain.exponentialRampToValueAtTime(0.01, now + 1.2);
osc.start(now);
osc.stop(now + 1.2);
}
else if (type === 'click') {
osc.type = 'sine';
osc.frequency.setValueAtTime(420, now);
osc.frequency.exponentialRampToValueAtTime(270, now + 0.15);
gain.gain.setValueAtTime(0.12, now);
gain.gain.exponentialRampToValueAtTime(0.01, now + 0.15);
osc.start(now);
osc.stop(now + 0.15);
}
}
const elements = {
shibaHead: document.getElementById('shiba-head'),
shibaTail: document.getElementById('shiba-tail'),
shibaBody: document.getElementById('shiba-body'),
shibaNeckwear: document.getElementById('shiba-neckwear'),
interactionOverlay: document.getElementById('interaction-overlay'),
shibaContainer: document.getElementById('shiba-container'),
shibaSvg: document.getElementById('shiba-svg'),
speechBubble: document.getElementById('speech-bubble'),
eyeLeftBall: document.getElementById('eye-left-ball'),
eyeLeftPupil: document.getElementById('eye-left-pupil'),
eyeLeftSubPupil: document.getElementById('eye-left-sub-pupil'),
eyeLeftHappy: document.getElementById('eye-left-happy'),
eyeLeftSleep: document.getElementById('eye-left-sleep'),
eyeRightBall: document.getElementById('eye-right-ball'),
eyeRightPupil: document.getElementById('eye-right-pupil'),
eyeRightSubPupil: document.getElementById('eye-right-sub-pupil'),
eyeRightHappy: document.getElementById('eye-right-happy'),
eyeRightSleep: document.getElementById('eye-right-sleep'),
maroLeft: document.getElementById('maro-left'),
maroRight: document.getElementById('maro-right'),
mouthDefault: document.getElementById('mouth-default'),
mouthOpen: document.getElementById('mouth-open'),
mouthTongue: document.getElementById('mouth-tongue'),
cheekLeftGroup: document.getElementById('cheek-left-group'),
cheekRightGroup: document.getElementById('cheek-right-group'),
blushLeft: document.getElementById('blush-left'),
blushRight: document.getElementById('blush-right'),
itemBandana: document.getElementById('item-bandana'),
itemBandanaKnot: document.getElementById('item-bandana-knot'),
itemRibbon: document.getElementById('item-ribbon'),
itemGlasses: document.getElementById('item-glasses'),
itemHat: document.getElementById('item-hat'),
itemCrown: document.getElementById('item-crown'),
foods: [
document.getElementById('food-bone'),
document.getElementById('food-cookie'),
document.getElementById('food-strawberry'),
document.getElementById('food-onigiri')
],
btnSound: document.getElementById('btn-sound'),
soundIcon: document.getElementById('sound-icon'),
btnHelp: document.getElementById('btn-help'),
helpModal: document.getElementById('help-modal'),
btnCloseHelp: document.getElementById('btn-close-help'),
btnStartPlay: document.getElementById('btn-start-play'),
toast: document.getElementById('toast-message'),
btnTrickHand: document.getElementById('btn-trick-hand'),
btnTrickSing: document.getElementById('btn-trick-sing'),
btnTrickSleep: document.getElementById('btn-trick-sleep'),
btnThemeDay: document.getElementById('theme-day'),
btnThemeSunset: document.getElementById('theme-sunset'),
btnThemeNight: document.getElementById('theme-night'),
btnToggleBandana: document.getElementById('toggle-bandana'),
btnToggleGlasses: document.getElementById('toggle-glasses'),
btnToggleHat: document.getElementById('toggle-hat'),
btnToggleCrown: document.getElementById('toggle-crown'),
bgPattern: document.getElementById('bg-pattern'),
skySun: document.getElementById('sky-sun'),
skyMoon: document.getElementById('sky-moon'),
bgBubbles: document.getElementById('bg-bubbles')
};
// --- STREAMING_CHUNK: Implementing background effects and bubbles ---
function spawnEffect(emoji, targetX = null, targetY = null) {
const container = document.body;
const particle = document.createElement('div');
particle.className = 'effect-particle text-3xl md:text-4xl';
particle.innerText = emoji;
if (targetX !== null && targetY !== null) {
particle.style.left = `${targetX}px`;
particle.style.top = `${targetY}px`;
} else {
const shibaRect = elements.shibaContainer.getBoundingClientRect();
particle.style.left = `${shibaRect.left + shibaRect.width / 2 + (Math.random() - 0.5) * 100}px`;
particle.style.top = `${shibaRect.top + shibaRect.height / 3 + (Math.random() - 0.5) * 50}px`;
}
container.appendChild(particle);
setTimeout(() => {
particle.remove();
}, 1200);
}
function spawnDecorBubbles() {
const container = elements.bgBubbles;
for (let i = 0; i < 7; i++) {
setTimeout(() => {
if (state.currentTheme === 'night') return;
const bubble = document.createElement('div');
bubble.className = 'bubble-decor';
const size = Math.random() * 30 + 20;
bubble.style.width = `${size}px`;
bubble.style.height = `${size}px`;
bubble.style.left = `${Math.random() * 95}vw`;
const duration = Math.random() * 8 + 10;
bubble.style.animation = `float-bubble ${duration}s infinite linear`;
container.appendChild(bubble);
setTimeout(() => {
bubble.remove();
}, duration * 1000);
}, i * 1800);
}
}
setInterval(() => {
const bubblesCount = elements.bgBubbles.childElementCount;
if (bubblesCount < 10 && state.currentTheme !== 'night') {
const bubble = document.createElement('div');
bubble.className = 'bubble-decor';
const size = Math.random() * 30 + 20;
bubble.style.width = `${size}px`;
bubble.style.height = `${size}px`;
bubble.style.left = `${Math.random() * 95}vw`;
const duration = Math.random() * 8 + 10;
bubble.style.animation = `float-bubble ${duration}s infinite linear`;
elements.bgBubbles.appendChild(bubble);
setTimeout(() => { bubble.remove(); }, duration * 1000);
}
}, 3000);
// --- STREAMING_CHUNK: Implementing Shiba talk and tracking ---
let bubbleTimeout = null;
function talk(message, duration = 3000) {
if (state.isSleeping && !message.includes('zzz') && !message.includes('ハッ')) return;
elements.speechBubble.innerText = message;
elements.speechBubble.style.transform = 'scale(1)';
if (bubbleTimeout) clearTimeout(bubbleTimeout);
bubbleTimeout = setTimeout(() => {
elements.speechBubble.style.transform = 'scale(0)';
}, duration);
}
const quotes = [
"遊んで遊んで!🐾",
"なでなでするといい音がするかも?✨",
"ほっぺ、もちもちだよ〜!ひっぱってみて!",
"おやつくれるとうれしいワン!🍗",
"ぼく、黒柴!まゆげがチャームポイントなんだ!🐶",
"おもしろいメガネ持ってる?👓",
"お外、いいお天気かな?☀️",
];
setInterval(() => {
if (!state.isSleeping && !state.isSinging && !state.isPetting && !state.isWalking && Math.random() < 0.25) {
const randomQuote = quotes[Math.floor(Math.random() * quotes.length)];
talk(randomQuote);
}
}, 15000);
document.addEventListener('mousemove', (e) => {
if (state.isSleeping) return;
updateShibaFaceTracking(e.clientX, e.clientY);
});
document.addEventListener('touchmove', (e) => {
if (state.isSleeping || e.touches.length === 0) return;
const touch = e.touches[0];
updateShibaFaceTracking(touch.clientX, touch.clientY);
}, { passive: true });
function updateShibaFaceTracking(mouseX, mouseY) {
const containerRect = elements.shibaContainer.getBoundingClientRect();
const shibaCenterX = containerRect.left + containerRect.width / 2;
const shibaCenterY = containerRect.top + containerRect.height / 2;
const dx = mouseX - shibaCenterX;
const dy = mouseY - shibaCenterY;
const distance = Math.sqrt(dx * dx + dy * dy);
const maxEyeMove = 4.5;
const maxHeadRotate = 6;
const maxHeadTranslate = 10;
const angle = Math.atan2(dy, dx);
const moveX = Math.cos(angle) * Math.min(maxEyeMove, distance / 40);
const moveY = Math.sin(angle) * Math.min(maxEyeMove, distance / 40);
// おめめの向き追従 (進行方向の反転 scaleX が効いている場合は、X座標の補正を反転する)
const scaleMultiplier = state.walkDirection === -1 ? -1 : 1;
if (!elements.eyeLeftBall.classList.contains('hidden')) {
elements.eyeLeftPupil.setAttribute('cx', 166 + (moveX * scaleMultiplier));
elements.eyeLeftPupil.setAttribute('cy', 193 + moveY);
elements.eyeLeftSubPupil.setAttribute('cx', 171 + (moveX * 0.7 * scaleMultiplier));
elements.eyeLeftSubPupil.setAttribute('cy', 199 + moveY * 0.7);
elements.eyeRightPupil.setAttribute('cx', 230 + (moveX * scaleMultiplier));
elements.eyeRightPupil.setAttribute('cy', 193 + moveY);
elements.eyeRightSubPupil.setAttribute('cx', 235 + (moveX * 0.7 * scaleMultiplier));
elements.eyeRightSubPupil.setAttribute('cy', 199 + moveY * 0.7);
}
if (!state.isCheekDragging) {
const headRot = Math.min(maxHeadRotate, Math.max(-maxHeadRotate, dx / 60));
const headTransX = Math.min(maxHeadTranslate, Math.max(-maxHeadTranslate, dx / 40));
const headTransY = Math.min(maxHeadTranslate, Math.max(-maxHeadTranslate, dy / 40));
// 向き(scaleX)を反転していない通常の回転制御
elements.shibaHead.style.transform = `translate(${headTransX * scaleMultiplier}px, ${headTransY}px) rotate(${headRot * scaleMultiplier}deg)`;
}
}
function triggerBlink() {
if (state.isSleeping || state.isPetting) return;
elements.eyeLeftBall.classList.add('hidden');
elements.eyeLeftPupil.classList.add('hidden');
elements.eyeLeftSubPupil.classList.add('hidden');
elements.eyeRightBall.classList.add('hidden');
elements.eyeRightPupil.classList.add('hidden');
elements.eyeRightSubPupil.classList.add('hidden');
elements.eyeLeftSleep.classList.remove('hidden');
elements.eyeRightSleep.classList.remove('hidden');
setTimeout(() => {
if (!state.isSleeping && !state.isPetting) {
elements.eyeLeftBall.classList.remove('hidden');
elements.eyeLeftPupil.classList.remove('hidden');
elements.eyeLeftSubPupil.classList.remove('hidden');
elements.eyeRightBall.classList.remove('hidden');
elements.eyeRightPupil.classList.remove('hidden');
elements.eyeRightSubPupil.classList.remove('hidden');
elements.eyeLeftSleep.classList.add('hidden');
elements.eyeRightSleep.classList.add('hidden');
}
}, 150);
}
setInterval(() => {
if (Math.random() < 0.4) {
triggerBlink();
}
}, 4000);
// --- STREAMING_CHUNK: Implementing touch/mouse interactions ---
// --- なでなで&ほっぺ引っ張りのジェスチャー制御 ---
elements.interactionOverlay.addEventListener('mousedown', startInteract);
elements.interactionOverlay.addEventListener('touchstart', startInteract, { passive: false });
function startInteract(e) {
e.preventDefault();
initAudio();
const clientX = e.clientX || (e.touches && e.touches[0].clientX);
const clientY = e.clientY || (e.touches && e.touches[0].clientY);
const overlayRect = elements.interactionOverlay.getBoundingClientRect();
const x = clientX - overlayRect.left;
const y = clientY - overlayRect.top;
const w = overlayRect.width;
const h = overlayRect.height;
// 歩き回るため、常に現在のオーバーレイ基準でほっぺの位置を判定
const onLeftCheek = (x > w * 0.15 && x < w * 0.4) && (y > h * 0.45 && y < h * 0.65);
const onRightCheek = (x > w * 0.6 && x < w * 0.85) && (y > h * 0.45 && y < h * 0.65);
if (onLeftCheek && !state.isSleeping) {
state.isCheekDragging = 'left';
state.dragStartX = clientX;
state.dragStartY = clientY;
playSound('stretch');
} else if (onRightCheek && !state.isSleeping) {
state.isCheekDragging = 'right';
state.dragStartX = clientX;
state.dragStartY = clientY;
playSound('stretch');
} else {
state.isPetting = true;
state.lastMouseX = clientX;
state.lastMouseY = clientY;
setShibaExpression('happy');
}
document.addEventListener('mousemove', handleInteractMove);
document.addEventListener('mouseup', endInteract);
document.addEventListener('touchmove', handleInteractMove, { passive: false });
document.addEventListener('touchend', endInteract);
}
function handleInteractMove(e) {
if (state.isSleeping) return;
const clientX = e.clientX || (e.touches && e.touches[0].clientX);
const clientY = e.clientY || (e.touches && e.touches[0].clientY);
if (state.isCheekDragging) {
e.preventDefault();
const deltaX = clientX - state.dragStartX;
let pullAmount = deltaX;
if (state.isCheekDragging === 'left') {
pullAmount = Math.min(20, Math.max(-80, deltaX));
const scaleX = 1 + Math.abs(pullAmount) / 45;
const shiftX = pullAmount * 0.5;
elements.cheekLeftGroup.style.transform = `translate(${shiftX}px, 0px) scale(${scaleX}, ${1 + Math.abs(pullAmount)/150})`;
elements.blushLeft.style.opacity = 0.75 + Math.abs(pullAmount) / 100;
} else {
pullAmount = Math.min(80, Math.max(-20, deltaX));
const scaleX = 1 + Math.abs(pullAmount) / 45;
const shiftX = pullAmount * 0.5;
elements.cheekRightGroup.style.transform = `translate(${shiftX}px, 0px) scale(${scaleX}, ${1 + Math.abs(pullAmount)/150})`;
elements.blushRight.style.opacity = 0.75 + Math.abs(pullAmount) / 100;
}
if (Math.abs(pullAmount) > 20 && Math.random() < 0.1) {
playSound('pet');
spawnEffect('✨', clientX, clientY);
}
if (Math.abs(pullAmount) > 40 && Math.random() < 0.05) {
talk("ひゃ〜!もちもちだよ〜!🍡");
}
} else if (state.isPetting) {
const dx = clientX - state.lastMouseX;
const dy = clientY - state.lastMouseY;
const dist = Math.sqrt(dx*dx + dy*dy);
state.pettedMeter += dist;
state.lastMouseX = clientX;
state.lastMouseY = clientY;
const now = Date.now();
if (state.pettedMeter > 120 && now - state.lastPetTime > 200) {
state.pettedMeter = 0;
state.lastPetTime = now;
playSound('pet');
spawnEffect('❤️', clientX, clientY);
setShibaExpression('so-happy');
if (Math.random() < 0.2) {
const petQuotes = ["むふふ、きもちいいワン…🐶", "そこそこ!もっと撫でて!", "うっとりしちゃうワン…🐾", "ハッピー!✨"];
talk(petQuotes[Math.floor(Math.random() * petQuotes.length)]);
}
elements.shibaTail.classList.add('wagging');
setTimeout(() => {
elements.shibaTail.classList.remove('wagging');
}, 1000);
}
}
}
function endInteract() {
if (state.isCheekDragging) {
playSound('snap');
const targetCheek = state.isCheekDragging === 'left' ? elements.cheekLeftGroup : elements.cheekRightGroup;
const targetBlush = state.isCheekDragging === 'left' ? elements.blushLeft : elements.blushRight;
targetCheek.classList.add('purupuru-anim');
targetCheek.style.transform = 'translate(0px, 0px) scale(1, 1)';
targetBlush.style.opacity = 0.75;
setTimeout(() => {
targetCheek.classList.remove('purupuru-anim');
}, 600);
state.isCheekDragging = null;
}
if (state.isPetting) {
state.isPetting = false;
setTimeout(() => {
if (!state.isPetting && !state.isSleeping && !state.isSinging) {
setShibaExpression('default');
}
}, 1200);
}
document.removeEventListener('mousemove', handleInteractMove);
document.removeEventListener('mouseup', endInteract);
document.removeEventListener('touchmove', handleInteractMove);
document.removeEventListener('touchend', endInteract);
}
function setShibaExpression(mode) {
const allEyes = [
elements.eyeLeftBall, elements.eyeLeftPupil, elements.eyeLeftSubPupil, elements.eyeLeftHappy, elements.eyeLeftSleep,
elements.eyeRightBall, elements.eyeRightPupil, elements.eyeRightSubPupil, elements.eyeRightHappy, elements.eyeRightSleep
];
allEyes.forEach(el => el.classList.add('hidden'));
elements.mouthDefault.classList.remove('hidden');
elements.mouthOpen.classList.add('hidden');
elements.mouthTongue.classList.add('hidden');
elements.maroLeft.style.transform = 'rotate(0deg)';
elements.maroRight.style.transform = 'rotate(0deg)';
switch (mode) {
case 'default':
elements.eyeLeftBall.classList.remove('hidden');
elements.eyeLeftPupil.classList.remove('hidden');
elements.eyeLeftSubPupil.classList.remove('hidden');
elements.eyeRightBall.classList.remove('hidden');
elements.eyeRightPupil.classList.remove('hidden');
elements.eyeRightSubPupil.classList.remove('hidden');
break;
case 'happy':
elements.eyeLeftHappy.classList.remove('hidden');
elements.eyeRightHappy.classList.remove('hidden');
elements.maroLeft.style.transform = 'rotate(5deg) translateY(-2px)';
elements.maroRight.style.transform = 'rotate(-5deg) translateY(-2px)';
break;
case 'so-happy':
elements.eyeLeftHappy.classList.remove('hidden');
elements.eyeRightHappy.classList.remove('hidden');
elements.mouthDefault.classList.add('hidden');
elements.mouthOpen.classList.remove('hidden');
elements.mouthTongue.classList.remove('hidden');
elements.maroLeft.style.transform = 'rotate(8deg) translateY(-4px)';
elements.maroRight.style.transform = 'rotate(-8deg) translateY(-4px)';
break;
case 'sleep':
elements.eyeLeftSleep.classList.remove('hidden');
elements.eyeRightSleep.classList.remove('hidden');
elements.maroLeft.style.transform = 'rotate(-5deg) translateY(2px)';
elements.maroRight.style.transform = 'rotate(5deg) translateY(2px)';
break;
case 'sing':
elements.eyeLeftHappy.classList.remove('hidden');
elements.eyeRightHappy.classList.remove('hidden');
elements.mouthDefault.classList.add('hidden');
elements.mouthOpen.classList.remove('hidden');
elements.maroLeft.style.transform = 'rotate(10deg) translateY(-5px)';
elements.maroRight.style.transform = 'rotate(-10deg) translateY(-5px)';
break;
}
}
// --- STREAMING_CHUNK: Implementing walking mechanics & physics ---
// --- ちょこちょこ自律歩行AI & アニメーション制御 ---
function canWalk() {
return !state.isSleeping &&
!state.isPetting &&
!state.isCheekDragging &&
!state.isSinging &&
!state.isTrickHand &&
!state.chewingTimer;
}
function startWalking(targetX) {
state.isWalking = true;
state.shibaTargetX = targetX;
state.walkDirection = targetX > state.shibaX ? 1 : -1;
// 歩行用のアニメーションクラスを付与
elements.shibaSvg.classList.add('walking-shiba');
document.getElementById('leg-left').classList.add('walking-leg-left');
document.getElementById('leg-right').classList.add('walking-leg-right');
elements.shibaTail.classList.add('walking-tail');
// 進行方向に向きを切り替え (くるっと可愛く反転)
if (state.walkDirection === -1) {
elements.shibaSvg.style.transform = 'scaleX(-1)';
} else {
elements.shibaSvg.style.transform = 'none';
}
}
function stopWalking() {
state.isWalking = false;
// アニメーション解除
elements.shibaSvg.classList.remove('walking-shiba');
document.getElementById('leg-left').classList.remove('walking-leg-left');
document.getElementById('leg-right').classList.remove('walking-leg-right');
elements.shibaTail.classList.remove('walking-tail');
}
// 毎フレームの座標アップデート (物理演算)
function updatePhysics() {
if (state.isWalking && canWalk()) {
const diff = state.shibaTargetX - state.shibaX;
const step = state.shibaSpeed * state.walkDirection;
if (Math.abs(diff) <= Math.abs(step)) {
state.shibaX = state.shibaTargetX;
stopWalking();
} else {
state.shibaX += step;
}
// 黒柴コンテナの位置を横移動
elements.shibaContainer.style.transform = `translateX(${state.shibaX}px)`;
} else if (!canWalk() && state.isWalking) {
stopWalking(); // インタラクションが入ったら歩行即時停止
}
requestAnimationFrame(updatePhysics);
}
// お散歩AI (脳ミソシステム)
function startShibaBrain() {
setInterval(() => {
if (!canWalk()) {
if (state.isWalking) stopWalking();
return;
}
// 既に歩いている最中はゴールするまで考えない
if (state.isWalking) return;
// 40%の確率でちょこちょこお散歩に出発!
if (Math.random() < 0.4) {
const moveRange = 90; // 左右にちょこちょこ歩く可動域
const targetX = (Math.random() - 0.5) * moveRange * 2;
if (Math.abs(targetX - state.shibaX) > 20) {
startWalking(targetX);
}
} else {
// 立ち止まっている間、たまに匂いクンクンなどの仕草
if (Math.random() < 0.15) {
talk("クンクン…🐾 何かおいしい匂いがするワン?");
spawnEffect('🐾');
} else if (Math.random() < 0.3) {
triggerBlink();
}
}
}, 3500);
}
// --- STREAMING_CHUNK: Implementing feeding and food dragging ---
// --- ドラッグ&ドロップでおやつをあげる機能 ---
let activeFood = null;
let foodGhost = null;
elements.foods.forEach(foodEl => {
foodEl.addEventListener('dragstart', (e) => {
if (state.isSleeping) {
e.preventDefault();
showToast("黒柴ちゃんはぐっすり眠っています💤");
return;
}
initAudio();
e.dataTransfer.setData('text/plain', foodEl.id);
state.isTrickHand = false;
setShibaExpression('so-happy');
talk("わくわく!それぼくの?🍖");
});
foodEl.addEventListener('dragend', () => {
if (!state.isPetting && !state.isSleeping) {
setShibaExpression('default');
}
});
foodEl.addEventListener('touchstart', (e) => {
if (state.isSleeping) {
showToast("黒柴ちゃんはぐっすり眠っています💤");
return;
}
e.preventDefault();
initAudio();
activeFood = foodEl;
const rect = foodEl.getBoundingClientRect();
foodGhost = foodEl.cloneNode(true);
foodGhost.style.position = 'fixed';
foodGhost.style.left = `${rect.left}px`;
foodGhost.style.top = `${rect.top}px`;
foodGhost.style.width = `${rect.width}px`;
foodGhost.style.height = `${rect.height}px`;
foodGhost.style.opacity = '0.9';
foodGhost.style.pointerEvents = 'none';
foodGhost.style.zIndex = '100';
document.body.appendChild(foodGhost);
setShibaExpression('so-happy');
talk("おやつだぁ〜!お口に運んで!🐾");
});
});
document.addEventListener('touchmove', (e) => {
if (!activeFood || !foodGhost) return;
const touch = e.touches[0];
foodGhost.style.left = `${touch.clientX - 30}px`;
foodGhost.style.top = `${touch.clientY - 30}px`;
});
document.addEventListener('touchend', (e) => {
if (!activeFood || !foodGhost) return;
const touch = e.changedTouches[0];
const shibaRect = elements.shibaContainer.getBoundingClientRect();
const shibaCenterX = shibaRect.left + shibaRect.width / 2;
const shibaCenterY = shibaRect.top + shibaRect.height * 0.6;
const dist = Math.sqrt(
Math.pow(touch.clientX - shibaCenterX, 2) +
Math.pow(touch.clientY - shibaCenterY, 2)
);
foodGhost.remove();
foodGhost = null;
if (dist < 100) {
eatFood(activeFood.id);
} else {
setShibaExpression('default');
talk("あれれ、おあずけ?🐶");
}
activeFood = null;
});
elements.interactionOverlay.addEventListener('dragover', (e) => {
e.preventDefault();
});
elements.interactionOverlay.addEventListener('drop', (e) => {
e.preventDefault();
const foodId = e.dataTransfer.getData('text/plain');
eatFood(foodId);
});
function eatFood(foodId) {
let foodEmoji = '🍖';
let foodName = 'ほね';
if (foodId.includes('cookie')) { foodEmoji = '🍪'; foodName = 'クッキー'; }
if (foodId.includes('strawberry')) { foodEmoji = '🍓'; foodName = 'いちご'; }
if (foodId.includes('onigiri')) { foodEmoji = '🍙'; foodName = 'おむすび'; }
setShibaExpression('so-happy');
let chewCount = 0;
state.isSinging = false;
if (state.chewingTimer) clearInterval(state.chewingTimer);
talk(`もぐもぐ…サクサク…😋`);
state.chewingTimer = setInterval(() => {
playSound('eat');
spawnEffect(foodEmoji);
chewCount++;
if (chewCount % 2 === 0) {
elements.mouthDefault.classList.add('hidden');
elements.mouthOpen.classList.remove('hidden');
} else {
elements.mouthDefault.classList.remove('hidden');
elements.mouthOpen.classList.add('hidden');
}
if (chewCount >= 6) {
clearInterval(state.chewingTimer);
playSound('happy');
setShibaExpression('so-happy');
spawnEffect('🎉');
spawnEffect('❤️');
talk(`おいしい!${foodName}おかわり!🐾🐶`);
elements.shibaTail.classList.add('wagging');
setTimeout(() => {
elements.shibaTail.classList.remove('wagging');
if (!state.isPetting && !state.isSleeping) {
setShibaExpression('default');
}
}, 1500);
}
}, 250);
}
// --- STREAMING_CHUNK: Setting up trick actions and dress up ---
// --- アクションボタン処理 ---
elements.btnTrickHand.addEventListener('click', () => {
if (state.isSleeping) return showToast("スヤスヤ眠っています…💤");
playSound('click');
talk("おて!ほら、できたワン!🐾", 3000);
playSound('happy');
spawnEffect('🐾');
const leg = document.getElementById('leg-left');
leg.style.transform = 'translateY(-20px) rotate(-15deg)';
leg.style.transformOrigin = '164px 325px';
leg.style.transition = 'transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
setShibaExpression('so-happy');
setTimeout(() => {
leg.style.transform = 'none';
if (!state.isPetting && !state.isSleeping) {
setShibaExpression('default');
}
}, 2000);
});
elements.btnTrickSing.addEventListener('click', () => {
if (state.isSleeping) return showToast("眠っていて聞こえないみたい💤");
playSound('click');
if (state.isSinging) return;
state.isSinging = true;
talk("あおーーーん!るんるん🎵🐶");
setShibaExpression('sing');
elements.shibaHead.style.transform = 'translateY(-15px) rotate(-8deg) scale(1.05)';
elements.shibaTail.classList.add('wagging');
setTimeout(() => { playSound('sing'); }, 200);
let noteInterval = setInterval(() => {
if (state.isSinging) spawnEffect('🎵');
}, 300);
setTimeout(() => {
clearInterval(noteInterval);
state.isSinging = false;
elements.shibaTail.classList.remove('wagging');
elements.shibaHead.style.transform = 'none';
if (!state.isPetting && !state.isSleeping) {
setShibaExpression('default');
}
}, 1500);
});
elements.btnTrickSleep.addEventListener('click', () => {
playSound('click');
if (!state.isSleeping) {
state.isSleeping = true;
setShibaExpression('sleep');
talk("すやすや…おやすみワン…💤");
elements.btnTrickSleep.innerText = "☀️ おめざめ";
elements.btnTrickSleep.classList.replace('bg-blue-400', 'bg-amber-400');
elements.btnTrickSleep.classList.replace('hover:bg-blue-500', 'hover:bg-amber-500');
elements.btnTrickSleep.classList.replace('text-blue-950', 'text-amber-950');
state.sleepInterval = setInterval(() => {
if (state.isSleeping) spawnEffect('💤');
}, 1500);
elements.shibaHead.style.transform = 'translateY(15px) rotate(3deg)';
elements.shibaTail.classList.remove('tail-wagging');
} else {
state.isSleeping = false;
clearInterval(state.sleepInterval);
setShibaExpression('default');
talk("ハッ!おはようワン!シャキッ✨");
playSound('happy');
spawnEffect('☀️');
elements.btnTrickSleep.innerText = "💤 おねむ";
elements.btnTrickSleep.classList.replace('bg-amber-400', 'bg-blue-400');
elements.btnTrickSleep.classList.replace('hover:bg-amber-500', 'hover:bg-blue-500');
elements.btnTrickSleep.classList.replace('text-amber-950', 'text-blue-950');
elements.shibaHead.style.transform = 'none';
elements.shibaTail.classList.add('tail-wagging');
}
});
// --- 着せ替えパネルトグル ---
elements.btnToggleBandana.addEventListener('click', () => {
playSound('click');
if (state.activeItems.bandana) {
state.activeItems.bandana = false;
state.activeItems.ribbon = true;
elements.itemBandana.classList.add('hidden');
elements.itemBandanaKnot.classList.add('hidden');
elements.itemRibbon.classList.remove('hidden');
talk("赤いリボンもおしゃれでしょ?🎀");
} else if (state.activeItems.ribbon) {
state.activeItems.ribbon = false;
elements.itemRibbon.classList.add('hidden');
talk("なにも着けてないのも身軽でいいね!🌱");
} else {
state.activeItems.bandana = true;
elements.itemBandana.classList.remove('hidden');
elements.itemBandanaKnot.classList.remove('hidden');
talk("自慢の緑の唐草バンダナだよ!わんこ定番!💚");
}
spawnEffect('✨');
});
elements.btnToggleGlasses.addEventListener('click', () => {
playSound('click');
state.activeItems.glasses = !state.activeItems.glasses;
if (state.activeItems.glasses) {
elements.itemGlasses.classList.remove('hidden');
talk("なんだかインテリ黒柴になったワン!👓📖");
} else {
elements.itemGlasses.classList.add('hidden');
talk("メガネはずしたよ!お目めパッチリ!🐶");
}
spawnEffect('✨');
});
elements.btnToggleHat.addEventListener('click', () => {
playSound('click');
state.activeItems.hat = !state.activeItems.hat;
if (state.activeItems.hat) {
elements.itemHat.classList.remove('hidden');
elements.itemCrown.classList.add('hidden');
state.activeItems.crown = false;
talk("事件のにおいがするワン!🕵️♂️🐾");
} else {
elements.itemHat.classList.add('hidden');
}
spawnEffect('✨');
});
elements.btnToggleCrown.addEventListener('click', () => {
playSound('click');
state.activeItems.crown = !state.activeItems.crown;
if (state.activeItems.crown) {
elements.itemCrown.classList.remove('hidden');
elements.itemHat.classList.add('hidden');
state.activeItems.hat = false;
talk("ぼくが今日から犬の国の王様だワン!👑🐕");
} else {
elements.itemCrown.classList.add('hidden');
}
spawnEffect('✨');
});
// --- もようがえ(テーマ切り替え) ---
function changeTheme(theme) {
playSound('click');
state.currentTheme = theme;
const body = document.body;
const stars = document.getElementById('bg-stars');
const pattern = elements.bgPattern;
body.className = "min-h-screen flex flex-col justify-between overflow-hidden relative smooth-transition ";
pattern.className = "absolute inset-0 pointer-events-none transition-all duration-700 ";
stars.innerHTML = '';
if (theme === 'day') {
body.classList.add('from-sky-200', 'via-pink-100', 'to-amber-100');
pattern.classList.add('bg-dots', 'opacity-65');
talk("お日さまぽかぽかで気持ちいいね!☀️");
elements.skySun.style.transform = 'scale(1)';
elements.skyMoon.style.transform = 'scale(0)';
animateClouds(true);
}
else if (theme === 'sunset') {
body.classList.add('from-purple-300', 'via-rose-200', 'to-orange-200');
pattern.classList.add('bg-stars-pattern', 'opacity-60');
talk("夕暮れ時だワン。星が見えてきたね🌇");
elements.skySun.style.transform = 'scale(0)';
elements.skyMoon.style.transform = 'scale(0)';
animateClouds(true, '#fda4af');
for (let i = 0; i < 20; i++) {
const star = document.createElement('div');
star.className = 'absolute bg-amber-300 rounded-full';
const size = Math.random() * 5 + 3;
star.style.width = `${size}px`;
star.style.height = `${size}px`;
star.style.left = `${Math.random() * 100}%`;
star.style.top = `${Math.random() * 50}%`;
star.style.opacity = Math.random() * 0.8 + 0.4;
stars.appendChild(star);
}
}
else if (theme === 'night') {
body.classList.add('from-indigo-900', 'via-purple-900', 'to-neutral-900');
pattern.classList.add('bg-stars-pattern', 'opacity-35');
talk("お空が満天の星空になったワン!🌌✨");
elements.skySun.style.transform = 'scale(0)';
elements.skyMoon.style.transform = 'scale(1)';
for (let i = 0; i < 40; i++) {
const star = document.createElement('div');
star.className = 'absolute bg-white rounded-full';
const size = Math.random() * 4 + 1.5;
star.style.width = `${size}px`;
star.style.height = `${size}px`;
star.style.left = `${Math.random() * 100}%`;
star.style.top = `${Math.random() * 65}%`;
star.style.opacity = Math.random() * 0.9 + 0.1;
star.style.animation = `pulse ${1 + Math.random() * 2}s infinite alternate`;
stars.appendChild(star);
}
animateClouds(false);
}
}
elements.btnThemeDay.addEventListener('click', () => changeTheme('day'));
elements.btnThemeSunset.addEventListener('click', () => changeTheme('sunset'));
elements.btnThemeNight.addEventListener('click', () => changeTheme('night'));
function animateClouds(show, colorOverride = null) {
const c1 = document.getElementById('cloud-1');
const c2 = document.getElementById('cloud-2');
if (!show) {
c1.style.opacity = '0';
c2.style.opacity = '0';
return;
}
c1.style.opacity = '1';
c2.style.opacity = '1';
if (colorOverride) {
c1.querySelector('path').setAttribute('fill', colorOverride);
c2.querySelector('path').setAttribute('fill', colorOverride);
} else {
c1.querySelector('path').setAttribute('fill', 'rgba(255, 255, 255, 0.98)');
c2.querySelector('path').setAttribute('fill', 'rgba(255, 255, 255, 0.92)');
}
function runCloud1() {
if (state.currentTheme === 'night') return;
c1.style.left = '-150px';
setTimeout(() => {
c1.style.transition = 'left 40s linear';
c1.style.left = '110vw';
}, 100);
}
function runCloud2() {
if (state.currentTheme === 'night') return;
c2.style.left = '-190px';
setTimeout(() => {
c2.style.transition = 'left 60s linear';
c2.style.left = '110vw';
}, 100);
}
runCloud1();
runCloud2();
setInterval(() => {
if (parseFloat(c1.style.left) >= window.innerWidth) runCloud1();
if (parseFloat(c2.style.left) >= window.innerWidth) runCloud2();
}, 5000);
}
elements.btnSound.addEventListener('click', () => {
state.isMuted = !state.isMuted;
if (state.isMuted) {
elements.soundIcon.className = "fa-solid fa-volume-xmark";
elements.btnSound.classList.replace('bg-amber-100', 'bg-rose-100');
elements.btnSound.classList.replace('text-amber-800', 'text-rose-800');
} else {
elements.soundIcon.className = "fa-solid fa-volume-high";
elements.btnSound.classList.replace('bg-rose-100', 'bg-amber-100');
elements.btnSound.classList.replace('text-rose-800', 'text-amber-800');
initAudio();
playSound('click');
}
});
elements.btnHelp.addEventListener('click', () => {
playSound('click');
elements.helpModal.classList.remove('hidden');
});
elements.btnCloseHelp.addEventListener('click', () => {
playSound('click');
elements.helpModal.classList.add('hidden');
});
elements.btnStartPlay.addEventListener('click', () => {
playSound('click');
elements.helpModal.classList.add('hidden');
});
function showToast(msg) {
elements.toast.innerText = msg;
elements.toast.style.opacity = '1';
setTimeout(() => {
elements.toast.style.opacity = '0';
}, 2500);
}
// --- STREAMING_CHUNK: Bootstrapping walking loops and loading game ---
window.onload = function() {
changeTheme('day');
spawnDecorBubbles();
// 自律歩行システムと物理アップデートエンジンの立ち上げ
startShibaBrain();
updatePhysics();
setTimeout(() => {
setShibaExpression('so-happy');
playSound('happy');
spawnEffect('🐾');
talk("わん!あそびに来てくれてうれしいワン!🐾", 4000);
setTimeout(() => {
if (!state.isPetting && !state.isSleeping) {
setShibaExpression('default');
}
}, 2000);
}, 1000);
};
</script>
</body>
</html>
この芝ちゃんはお手とか寝ること撫でること ほっぺを引っ張ることができます