🔒 サンドボックス内で実行中 ⛶ 全画面で遊ぶ
HTML / CSS / JS
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>シン・ゴジラ 3D シミュレーター | Shin Godzilla 3D Simulator</title>
    <!-- Tailwind CSS (UI用) -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- FontAwesome (アイコン用) -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #050505;
            user-select: none;
            -webkit-user-select: none;
            touch-action: manipulation;
        }
        /* スクロールバー非表示 */
        ::-webkit-scrollbar {
            display: none;
        }
        /* 巨災対風のUI装飾 */
        .neon-border {
            box-shadow: 0 0 15px rgba(239, 68, 68, 0.5);
            border: 1px solid rgba(239, 68, 68, 0.8);
        }
        .neon-border-purple {
            box-shadow: 0 0 15px rgba(168, 85, 247, 0.5);
            border: 1px solid rgba(168, 85, 247, 0.8);
        }
        /* モバイル用の快適なジェスチャー無効化 */
        #canvas-container canvas {
            touch-action: none;
        }
    </style>
</head>
<body class="text-gray-100 font-mono">

    <!-- メイン3Dキャンバス -->
    <div id="canvas-container" class="absolute inset-0 z-0"></div>

    <!-- UIオーバーレイ -->
    <div class="absolute inset-0 flex flex-col justify-between p-3 sm:p-4 pointer-events-none z-10">
        
        <!-- ヘッダー: 巨災対風ステータスモニター -->
        <header class="w-full flex justify-between items-start pointer-events-auto gap-2">
            <div id="status-panel" class="bg-black/80 p-3 sm:p-4 rounded neon-border transition-all duration-300 transform w-full max-w-[280px] sm:max-w-sm md:max-w-md">
                <div class="flex items-center space-x-2 mb-1">
                    <span class="inline-block w-2.5 h-2.5 rounded-full bg-red-600 animate-pulse" id="status-dot"></span>
                    <h1 class="text-red-500 font-black text-sm sm:text-base md:text-xl tracking-wider truncate">巨大不明生物災害対策本部</h1>
                </div>
                <p class="text-[9px] sm:text-xs text-gray-400 border-b border-red-900/50 pb-1.5 mb-1.5">GIANT UNIDENTIFIED LIFE FORM MONITOR</p>
                <div class="grid grid-cols-2 gap-x-2 sm:gap-x-4 gap-y-0.5 sm:gap-y-1 text-[10px] sm:text-xs">
                    <div class="truncate">識別名: <span class="text-red-400 font-bold">ゴジラ</span></div>
                    <div class="truncate">型式: <span class="text-red-400 font-bold" id="code-type-text">第4形態</span></div>
                    <div class="truncate">全長: <span class="text-gray-300" id="height-text">118.5 m</span></div>
                    <div class="truncate">状態: <span id="state-text" class="text-red-500 font-bold animate-pulse">警戒監視中</span></div>
                    <div class="col-span-2 mt-1">エネルギー蓄積率: 
                        <div class="w-full bg-gray-800 h-1.5 mt-1 rounded-full overflow-hidden">
                            <div id="energy-bar" class="bg-red-600 h-full transition-all duration-300" style="width: 45%"></div>
                        </div>
                    </div>
                </div>
            </div>

            <!-- 右上操作ガイド & UI非表示スイッチ -->
            <div class="flex flex-col items-end space-y-2 pointer-events-auto shrink-0">
                <button id="btn-toggle-ui" class="bg-red-950/80 hover:bg-red-900 border border-red-500 text-red-200 px-2.5 py-1.5 sm:px-3 sm:py-2 rounded text-[10px] sm:text-xs transition active:scale-95 flex items-center space-x-1 shadow-lg">
                    <i class="fa-solid fa-eye-slash" id="toggle-ui-icon"></i>
                    <span id="toggle-ui-text">UI非表示</span>
                </button>
                
                <!-- PC向け操作ガイド -->
                <div class="hidden md:block bg-black/60 p-3 rounded border border-gray-800 text-right text-[10px] sm:text-xs text-gray-400 max-w-[200px]">
                    <p class="mb-0.5"><i class="fa-solid fa-arrows-to-eye mr-1"></i> ドラッグ: 回転</p>
                    <p class="mb-0.5"><i class="fa-solid fa-arrows-up-down mr-1"></i> スクロール: ズーム</p>
                    <p><i class="fa-solid fa-hand-pointer mr-1"></i> 右ドラッグ: 平行移動</p>
                </div>
                
                <!-- モバイル向け操作ガイド -->
                <div class="block md:hidden bg-black/60 p-2 rounded border border-gray-800 text-right text-[9px] text-gray-400">
                    <p class="mb-0.5"><i class="fa-solid fa-finger mr-1"></i> 1本指スワイプ: 回転</p>
                    <p><i class="fa-solid fa-arrows-pinch mr-1"></i> 2本指ピンチ: ズーム/移動</p>
                </div>
            </div>
        </header>

        <!-- 下部: コントロールパネル -->
        <footer class="w-full flex flex-col justify-end items-center gap-1 sm:gap-2 pointer-events-auto transition-all duration-300 transform">
            
            <!-- モバイル専用タブ切り替えバー (画面が小さい時だけ出現) -->
            <div id="mobile-tabs" class="flex md:hidden w-full max-w-sm bg-black/95 border border-gray-800 rounded-t-lg overflow-hidden shadow-lg transition-all duration-300">
                <button id="tab-actions" class="flex-1 py-2 text-center text-red-500 border-b-2 border-red-500 font-black text-[11px] sm:text-xs transition">
                    <i class="fa-solid fa-gamepad mr-1"></i>指令ユニット
                </button>
                <button id="tab-env" class="flex-1 py-2 text-center text-gray-500 border-b-2 border-transparent text-[11px] sm:text-xs transition">
                    <i class="fa-solid fa-sliders mr-1"></i>環境設定
                </button>
            </div>

            <!-- パネルコンテナ -->
            <div class="w-full flex flex-col md:flex-row justify-between items-end gap-3 md:gap-4">
                
                <!-- 左下: カメラ・環境コントロール -->
                <div id="env-panel" class="bg-black/80 p-3 sm:p-4 rounded border border-gray-800 w-full md:w-80 text-[11px] sm:text-xs space-y-2 sm:space-y-3 hidden md:block transition-all duration-300">
                    <h2 class="text-gray-400 font-bold uppercase border-b border-gray-800 pb-1 mb-1 sm:mb-2">環境設定 / シミュレーション</h2>
                    
                    <!-- 尻尾のうねり速度 -->
                    <div>
                        <div class="flex justify-between mb-1">
                            <span>尾部細胞活性度 (運動速度)</span>
                            <span id="tail-speed-val" class="text-red-400">1.0x</span>
                        </div>
                        <input type="range" id="tail-speed" min="0" max="3" step="0.1" value="1" class="w-full accent-red-600 bg-gray-800 h-1 sm:h-1.5 rounded-lg appearance-none cursor-pointer">
                    </div>

                    <!-- 霧の濃さ -->
                    <div>
                        <div class="flex justify-between mb-1">
                            <span>放射性物質濃度 (フォグ)</span>
                            <span id="fog-val" class="text-red-400">標準</span>
                        </div>
                        <input type="range" id="fog-density" min="0" max="10" step="1" value="3" class="w-full accent-red-600 bg-gray-800 h-1 sm:h-1.5 rounded-lg appearance-none cursor-pointer">
                    </div>

                    <!-- 明るさ(ライティング) -->
                    <div>
                        <div class="flex justify-between mb-1">
                            <span>環境の明るさ (照明)</span>
                            <span id="light-val" class="text-red-400">明るい</span>
                        </div>
                        <input type="range" id="scene-brightness" min="1" max="15" step="1" value="8" class="w-full accent-red-600 bg-gray-800 h-1 sm:h-1.5 rounded-lg appearance-none cursor-pointer">
                    </div>
                    
                    <!-- 昼夜切り替え -->
                    <div class="grid grid-cols-2 gap-2 pt-1">
                        <button id="btn-env-night" class="bg-gray-900 hover:bg-gray-800 text-gray-400 py-1.5 px-2 rounded border border-gray-700 text-[10px] sm:text-xs font-bold transition flex items-center justify-center space-x-1 active:scale-95">
                            <i class="fa-solid fa-moon"></i>
                            <span>夜間作戦</span>
                        </button>
                        <button id="btn-env-day" class="bg-amber-950/40 hover:bg-amber-900/60 text-amber-400 py-1.5 px-2 rounded border border-amber-800/80 text-[10px] sm:text-xs font-bold transition flex items-center justify-center space-x-1 active:scale-95">
                            <i class="fa-solid fa-sun"></i>
                            <span>昼間作戦</span>
                        </button>
                    </div>

                    <!-- カメラリセット -->
                    <button id="btn-reset-cam" class="w-full bg-gray-900 hover:bg-gray-800 text-gray-300 py-1.5 px-3 rounded border border-gray-700 transition active:scale-95 flex items-center justify-center space-x-1 text-[10px] sm:text-xs">
                        <i class="fa-solid fa-video"></i>
                        <span>視点をリセット</span>
                    </button>
                </div>

                <!-- 右下: 兵器・ゴジラアクションコントロール -->
                <div id="action-panel" class="bg-black/80 p-3 sm:p-4 rounded neon-border transition-all duration-500 w-full md:w-96 space-y-3 sm:space-y-4 block md:block">
                    <h2 class="text-red-500 font-bold text-xs sm:text-sm tracking-wider uppercase border-b border-red-900/50 pb-1 flex items-center justify-between">
                        <span>活動指令シミュレーター</span>
                        <span class="text-[9px] sm:text-xs text-gray-500">CONTROL UNIT</span>
                    </h2>

                    <!-- 形態選択トグル (第2・第3・第4) -->
                    <div class="grid grid-cols-3 gap-1">
                        <button id="btn-phase-2" class="bg-gray-900 hover:bg-gray-800 text-gray-400 border border-gray-700 font-bold py-1.5 px-1 rounded text-[10px] sm:text-xs transition flex flex-col items-center justify-center active:scale-95">
                            <span class="w-2 h-2 rounded-full bg-amber-600 mb-1"></span>
                            <span>第2形態</span>
                        </button>
                        <button id="btn-phase-3" class="bg-gray-900 hover:bg-gray-800 text-gray-400 border border-gray-700 font-bold py-1.5 px-1 rounded text-[10px] sm:text-xs transition flex flex-col items-center justify-center active:scale-95">
                            <span class="w-2 h-2 rounded-full bg-red-800 mb-1"></span>
                            <span>第3形態</span>
                        </button>
                        <button id="btn-phase-4" class="bg-red-950/80 hover:bg-red-900 text-red-200 border border-red-500 font-bold py-1.5 px-1 rounded text-[10px] sm:text-xs transition flex flex-col items-center justify-center active:scale-95">
                            <span class="w-2 h-2 rounded-full bg-red-500 mb-1 animate-pulse"></span>
                            <span>第4形態</span>
                        </button>
                    </div>

                    <!-- 第4形態専用: 通常/熱線 変更トグル -->
                    <div id="phase4-glow-toggle" class="grid grid-cols-2 gap-2 transition-all duration-300">
                        <button id="btn-state-red" class="bg-red-950/80 hover:bg-red-900 text-red-200 border border-red-500 font-bold py-1.5 px-2 rounded text-[10px] sm:text-xs transition flex items-center justify-center space-x-1.5 active:scale-95">
                            <span class="w-2 h-2 rounded-full bg-red-500 animate-pulse"></span>
                            <span>通常 (赤)</span>
                        </button>
                        <button id="btn-state-purple" class="bg-gray-900 hover:bg-gray-800 text-gray-400 border border-gray-700 font-bold py-1.5 px-2 rounded text-[10px] sm:text-xs transition flex items-center justify-center space-x-1.5 active:scale-95">
                            <span class="w-2 h-2 rounded-full bg-purple-500"></span>
                            <span>熱線 (紫)</span>
                        </button>
                    </div>

                    <!-- 形態適応アクションボタン -->
                    <div class="grid grid-cols-2 gap-2">
                        <button id="btn-roar" class="bg-amber-950/50 hover:bg-amber-900/80 text-amber-200 border border-amber-600 py-2 sm:py-2.5 px-2 rounded text-[11px] sm:text-xs font-bold transition flex items-center justify-center space-x-1.5 active:scale-95">
                            <i id="icon-action-1" class="fa-solid fa-volume-high"></i>
                            <span id="lbl-action-1">咆哮モーション</span>
                        </button>
                        <button id="btn-beam" class="bg-purple-950/50 hover:bg-purple-900/80 text-purple-200 border border-purple-600 py-2 sm:py-2.5 px-2 rounded text-[11px] sm:text-xs font-bold transition flex items-center justify-center space-x-1.5 relative overflow-hidden group active:scale-95">
                            <span class="absolute inset-0 bg-purple-600/20 translate-y-full group-hover:translate-y-0 transition-transform"></span>
                            <i id="icon-action-2" class="fa-solid fa-bolt-lightning"></i>
                            <span id="lbl-action-2">放射線流 発射</span>
                        </button>
                    </div>

                    <!-- ミニ解説・警報 -->
                    <div class="bg-red-950/20 border border-red-900/30 p-2 sm:p-2.5 rounded text-[10px] text-red-400/90 leading-relaxed" id="warning-box">
                        <i class="fa-solid fa-circle-exclamation mr-1 animate-ping text-red-500"></i>
                        <strong>警告:</strong> 熱線放出の際、体内温度が急上昇し、周囲の全システムが融解・破壊される恐れがあります。
                    </div>
                </div>
            </div>

        </footer>
    </div>

    <!-- 音響効果用Web Audio API、3Dライブラリ読み込み -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>

    <script>
        // --- 1. グローバル変数と初期設定 ---
        let scene, camera, renderer, controls;
        let godzilla, tailSegments = [], backPlates = [], tailPlates = [];
        let bodyMesh, headMesh, jawMesh, leftArm, rightArm;
        let glowLights = [], ambientLight, dirLight;
        let particles, particleGeometry;
        let beamsGroup; // 熱線ビームグループ
        let beamParticles; // ビームのスパーク

        // エラ排熱と第2形態用の追加オブジェクト・変数
        let currentPhase = 4; // デフォルト:第4形態
        let gills = []; // 首元のエラ
        let gillLights = [];
        let isGillEjecting = false; // エラ排熱アクションフラグ
        let gillEjectTimer = 0;
        let gillParticles; // エラから出る血液・熱水風パーティクル
        let isSquirming = false; // 急速蛇行フラグ
        let squirmTimer = 0;
        let isStandingUp = false; // 直立試行フラグ
        let standUpTimer = 0;

        // アニメーション用状態
        let animationSpeed = 1.0;
        let isPurpleState = false;
        let isRoaring = false;
        let isBeaming = false;
        let roarTimer = 0;
        let beamTimer = 0;
        
        let isDayMode = true; // デフォルトを昼間モードにして明るく
        let brightnessMultiplier = 0.8; // 明るさスライダー倍率
        let uiHidden = false; // UI隠しフラグ
        
        // 音声効果
        const audioCtx = new (window.AudioContext || window.webkitAudioContext)();

        // カラーパレット
        const COLORS = {
            skin: 0x111111,          // 不気味な濃い黒
            redGlow: 0xff1100,       // 第4形態の赤発光
            purpleGlow: 0xaa00ff,    // 熱線の紫発光
            ground: 0x0a0a0a,        // 焦土
            fogRed: 0x0f0505,        // 赤フォグ
            fogPurple: 0x09050f,     // 紫フォグ
            
            dayGround: 0x2c2d30,     // 灰色のガレキ地面
            dayFogRed: 0x6e727a,     // 煙が立ち込める明るい昼フォグ
            dayFogPurple: 0x5a556b   // 昼間の熱線時フォグ
        };

        // --- 2. 起動処理 (window.onload) ---
        window.onload = function() {
            init3D();
            buildGodzilla(4); // デフォルトは第4形態
            buildEnvironment();
            setupInteractions();
            animate();
        };

        // --- 3. 3Dシーンの初期化 ---
        function init3D() {
            const container = document.getElementById('canvas-container');
            
            // シーン
            scene = new THREE.Scene();
            
            // 初期カラーの決定
            const initialEnvColor = isDayMode ? COLORS.dayFogRed : COLORS.fogRed;
            scene.background = new THREE.Color(initialEnvColor);
            scene.fog = new THREE.FogExp2(initialEnvColor, 0.03);

            // カメラ
            camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
            resetCamera();

            // レンダラー (モバイルのGPU負荷を抑えるために設定を適正化)
            renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: "high-performance" });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5)); // 1.5に制限して発熱防止
            renderer.shadowMap.enabled = true;
            renderer.shadowMap.type = THREE.PCFSoftShadowMap;
            container.appendChild(renderer.domElement);

            // コントロール
            controls = new THREE.OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.05;
            controls.maxPolarAngle = Math.PI / 2 - 0.02; // 地面以下に行かないように
            controls.minDistance = 5;
            controls.maxDistance = 45;
            controls.target.set(0, 5, 0);

            // 照明
            ambientLight = new THREE.AmbientLight(0x0f0f15, 1.2);
            scene.add(ambientLight);

            // メイン指向性ライト
            dirLight = new THREE.DirectionalLight(0x445566, 1.5);
            dirLight.position.set(15, 30, 15);
            dirLight.castShadow = true;
            dirLight.shadow.mapSize.width = 1024;
            dirLight.shadow.mapSize.height = 1024;
            dirLight.shadow.bias = -0.001;
            scene.add(dirLight);

            // 昼間を明るく照らす補助半球ライト
            const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.3);
            hemiLight.position.set(0, 50, 0);
            hemiLight.name = "hemiLight";
            scene.add(hemiLight);

            // 体内発光再現用ポイントライト
            const light1 = new THREE.PointLight(COLORS.redGlow, 5, 15);
            light1.position.set(0, 5, -1);
            scene.add(light1);
            glowLights.push(light1);

            const light2 = new THREE.PointLight(COLORS.redGlow, 3, 10);
            light2.position.set(0, 3, -4);
            scene.add(light2);
            glowLights.push(light2);

            // エラ排熱用の赤いパーティクルシステムを構築
            initGillParticles();

            // 初期明るさの適用
            updateLights();

            // リサイズ対応
            window.addEventListener('resize', onWindowResize, false);
        }

        function initGillParticles() {
            const pCount = 80;
            const geom = new THREE.BufferGeometry();
            const positions = new Float32Array(pCount * 3);
            const velocities = [];

            for (let i = 0; i < pCount; i++) {
                positions[i * 3] = 0;
                positions[i * 3 + 1] = -999; // 最初は画面外に隠す
                positions[i * 3 + 2] = 0;
                velocities.push(new THREE.Vector3(0, 0, 0));
            }

            geom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
            const mat = new THREE.PointsMaterial({
                color: 0xcc1100, // 血の赤
                size: 0.22,
                transparent: true,
                opacity: 0.85,
                blending: THREE.AdditiveBlending
            });

            gillParticles = new THREE.Points(geom, mat);
            gillParticles.userData = { velocities: velocities };
            scene.add(gillParticles);
        }

        // カメラ視点のリセット (モバイル/タブレットのアスペクト比に合わせて見切れを防止)
        function resetCamera() {
            const isMobile = window.innerWidth < 768;
            if (controls) {
                if (currentPhase === 2) {
                    controls.target.set(0, 1.2, 1.0);
                    camera.position.set(0, isMobile ? 5 : 4, isMobile ? 15 : 12); // モバイルは一歩引く
                } else if (currentPhase === 3) {
                    controls.target.set(0, 1.8, 1.0);
                    camera.position.set(0, isMobile ? 6 : 5, isMobile ? 18 : 14);
                } else {
                    controls.target.set(0, 5, 0);
                    camera.position.set(0, isMobile ? 10 : 8, isMobile ? 28 : 22);
                }
            }
        }

        // ライティングの動的更新
        function updateLights() {
            const baseAmbient = isDayMode ? 1.8 : 0.8;
            const baseDir = isDayMode ? 2.5 : 1.2;
            const hemiLight = scene.getObjectByName("hemiLight");

            // スライダー倍率を乗算
            ambientLight.intensity = baseAmbient * brightnessMultiplier;
            dirLight.intensity = baseDir * brightnessMultiplier;
            
            if (hemiLight) {
                hemiLight.intensity = (isDayMode ? 1.0 : 0.2) * brightnessMultiplier;
                hemiLight.color.setHex(isDayMode ? 0xddeeff : 0x445566);
            }

            // 皮膚マテリアルの調整
            if (godzilla) {
                godzilla.traverse((child) => {
                    if (child.isMesh && child.material) {
                        if (child.material.color && child.material.color.getHex() === COLORS.skin) {
                            child.material.roughness = isDayMode ? 0.75 : 0.9;
                            child.material.metalness = isDayMode ? 0.25 : 0.15;
                        }
                    }
                });
            }

            // 背景色とフォグ色の更新
            let envColor;
            if (isDayMode) {
                envColor = isPurpleState ? COLORS.dayFogPurple : COLORS.dayFogRed;
            } else {
                envColor = isPurpleState ? COLORS.fogPurple : COLORS.fogRed;
            }

            scene.background.set(envColor);
            scene.fog.color.set(envColor);
        }

        // --- 4. シン・ゴジラプロシージャルモデルの構築 (形態分岐版) ---
        function buildGodzilla(phase = 4) {
            currentPhase = phase;

            // 既存オブジェクトのクリーンアップ
            if (godzilla) {
                scene.remove(godzilla);
            }
            godzilla = new THREE.Group();
            scene.add(godzilla);

            tailSegments = [];
            backPlates = [];
            tailPlates = [];
            gills = [];
            gillLights = [];

            // マテリアル定義
            let skinColor, emissiveColor, emissiveIntensity;
            let roughness = 0.9;
            let metalness = 0.15;

            if (phase === 2) {
                skinColor = 0xb59e7f; // 画像のフィギュアに近いオリーブベージュ
                emissiveColor = 0xff3300; // オレンジ寄りの赤発光
                emissiveIntensity = 1.0;
            } else if (phase === 3) {
                skinColor = 0x221110; // 品川くんの赤黒い焦げた肌色
                emissiveColor = 0xff0000; // 鮮血のような強烈な赤発光
                emissiveIntensity = 2.0;
            } else {
                // 第4形態
                skinColor = COLORS.skin;
                emissiveColor = isPurpleState ? COLORS.purpleGlow : COLORS.redGlow;
                emissiveIntensity = isPurpleState ? 3.5 : 2.5;
            }

            const skinMat = new THREE.MeshStandardMaterial({
                color: skinColor,
                roughness: roughness,
                metalness: metalness,
                flatShading: true
            });

            const glowMat = new THREE.MeshStandardMaterial({
                color: 0x110000,
                emissive: emissiveColor,
                emissiveIntensity: emissiveIntensity,
                roughness: 0.4,
                flatShading: true
            });

            // 胴体の構築
            const bodyGroup = new THREE.Group();
            godzilla.add(bodyGroup);

            if (phase === 2) {
                // --- 第2形態 (蒲田くん / アップロード画像フィギュア準拠) の構築 ---
                bodyGroup.position.set(0, 0.8, 0.8);
                bodyGroup.rotation.x = Math.PI / 12;

                // 扁平で前後に長い胴体
                const hipsGeo = new THREE.CylinderGeometry(0.8, 1.4, 4.0, 10);
                hipsGeo.scale(1.2, 0.8, 1.0);
                const hips = new THREE.Mesh(hipsGeo, skinMat);
                hips.position.set(0, 0.2, -0.6);
                hips.rotation.x = Math.PI / 2.6;
                hips.castShadow = true;
                hips.receiveShadow = true;
                bodyGroup.add(hips);

                // 胸部
                const chestGeo = new THREE.CylinderGeometry(0.6, 1.0, 3.0, 10);
                chestGeo.scale(1.1, 0.7, 1.0);
                bodyMesh = new THREE.Mesh(chestGeo, skinMat);
                bodyMesh.position.set(0, 0.8, 0.8);
                bodyMesh.rotation.x = Math.PI / 2.8;
                bodyMesh.castShadow = true;
                bodyGroup.add(bodyMesh);

                // 4連スリットエラ
                const gillSlitMat = new THREE.MeshStandardMaterial({
                    color: 0x880000,
                    emissive: 0xff0000,
                    emissiveIntensity: 1.5,
                    flatShading: true
                });
                
                for (let j = 0; j < 4; j++) {
                    const gillSiltGeo = new THREE.BoxGeometry(0.06, 0.5, 0.12);
                    const zPos = 1.1 + j * 0.22;
                    const yPos = 0.6 - j * 0.04;
                    
                    const lG = new THREE.Mesh(gillSiltGeo, gillSlitMat);
                    lG.position.set(0.68 + j * 0.01, yPos, zPos);
                    lG.rotation.set(0.1, 0.35, -0.05);
                    bodyGroup.add(lG);
                    gills.push(lG);

                    const rG = lG.clone();
                    rG.position.x = -(0.68 + j * 0.01);
                    rG.rotation.y = -0.35;
                    bodyGroup.add(rG);
                    gills.push(rG);
                }

                // 前肢
                const armGeo = new THREE.BoxGeometry(0.12, 0.4, 0.6);
                leftArm = new THREE.Mesh(armGeo, skinMat);
                leftArm.position.set(0.9, 0.3, 0.1);
                leftArm.rotation.set(0.3, 0.2, -0.5);
                bodyGroup.add(leftArm);

                rightArm = leftArm.clone();
                rightArm.position.x = -0.9;
                rightArm.rotation.y = -0.2;
                rightArm.rotation.z = 0.5;
                bodyGroup.add(rightArm);

                // 頭部と目
                const headGroup = new THREE.Group();
                headGroup.position.set(0, 0.9, 2.3);
                bodyGroup.add(headGroup);

                const skullGeo = new THREE.BoxGeometry(0.65, 0.45, 1.4);
                headMesh = new THREE.Mesh(skullGeo, skinMat);
                headMesh.position.set(0, 0.1, 0.1);
                headMesh.castShadow = true;
                headGroup.add(headMesh);

                jawMesh = new THREE.Group();
                jawMesh.position.set(0, -0.15, 0.3);
                headGroup.add(jawMesh);
                const jawBaseGeo = new THREE.BoxGeometry(0.6, 0.15, 1.2);
                const jawBase = new THREE.Mesh(jawBaseGeo, skinMat);
                jawMesh.add(jawBase);

                // 巨大ギョロ目
                const eyeWhiteGeo = new THREE.SphereGeometry(0.16, 12, 12);
                const eyeBlackGeo = new THREE.SphereGeometry(0.06, 8, 8);
                const eyeWhiteMat = new THREE.MeshBasicMaterial({ color: 0xfafafa });
                const eyeBlackMat = new THREE.MeshBasicMaterial({ color: 0x000000 });

                const lEyeWhite = new THREE.Mesh(eyeWhiteGeo, eyeWhiteMat);
                lEyeWhite.position.set(0.32, 0.1, 0.4);
                const lEyeBlack = new THREE.Mesh(eyeBlackGeo, eyeBlackMat);
                lEyeBlack.position.set(0.04, 0, 0.13);
                lEyeWhite.add(lEyeBlack);
                headMesh.add(lEyeWhite);

                const rEyeWhite = lEyeWhite.clone();
                rEyeWhite.position.x = -0.32;
                rEyeWhite.children[0].position.x = -0.04;
                headMesh.add(rEyeWhite);

                // 脚
                const legGroup = new THREE.Group();
                godzilla.add(legGroup);

                const lLegGeo = new THREE.CylinderGeometry(0.35, 0.6, 1.2, 8);
                const lLeg = new THREE.Mesh(lLegGeo, skinMat);
                lLeg.position.set(1.0, 0.4, -0.8);
                lLeg.rotation.set(0.8, 0, -0.4);
                legGroup.add(lLeg);

                const lFootGeo = new THREE.BoxGeometry(0.6, 0.3, 1.2);
                const lFoot = new THREE.Mesh(lFootGeo, skinMat);
                lFoot.position.set(0, -0.6, 0.3);
                lFoot.rotation.x = -0.5;
                lLeg.add(lFoot);

                const rLeg = lLeg.clone();
                rLeg.position.x = -1.0;
                rLeg.rotation.z = 0.4;
                legGroup.add(rLeg);

                // 背鰭
                const platesGroup = new THREE.Group();
                bodyGroup.add(platesGroup);
                for (let i = 0; i < 11; i++) {
                    const ratio = i / 10;
                    const size = 0.7 * (1.0 - ratio * 0.4);
                    
                    const pGlowMat = new THREE.MeshStandardMaterial({
                        color: 0x6e4e3b,
                        emissive: 0xff3300,
                        emissiveIntensity: 1.0,
                        flatShading: true
                    });

                    const plateGeo = new THREE.ConeGeometry(0.18 * size, 0.9 * size, 4);
                    const plate = new THREE.Mesh(plateGeo, pGlowMat);
                    plate.position.set(0, 1.1 - (i * 0.22), -0.1 - (i * 0.28));
                    plate.rotation.x = -Math.PI / 3.2;
                    platesGroup.add(plate);
                    backPlates.push(plate);
                }

            } else if (phase === 3) {
                // --- 第3形態 (品川くん / アップロード画像準拠の赤黒・極太首仕様) の構築 ---
                bodyGroup.position.set(0, 1.6, 0.4);
                bodyGroup.rotation.x = Math.PI / 10;

                // 胴体
                const hipsGeo = new THREE.CylinderGeometry(1.2, 2.2, 3.8, 10);
                const hips = new THREE.Mesh(hipsGeo, skinMat);
                hips.position.set(0, -0.4, -0.4);
                hips.rotation.x = Math.PI / 6;
                hips.castShadow = true;
                hips.receiveShadow = true;
                bodyGroup.add(hips);

                // 胸部
                const chestGeo = new THREE.CylinderGeometry(0.9, 1.5, 3.2, 10);
                bodyMesh = new THREE.Mesh(chestGeo, skinMat);
                bodyMesh.position.set(0, 0.9, 0.6);
                bodyMesh.rotation.x = Math.PI / 4;
                bodyMesh.castShadow = true;
                bodyGroup.add(bodyMesh);

                // 首 (太い蛇腹仕様)
                const neckGroup = new THREE.Group();
                neckGroup.position.set(0, 1.8, 1.4);
                neckGroup.rotation.x = -Math.PI / 10;
                bodyGroup.add(neckGroup);

                for (let j = 0; j < 5; j++) {
                    const neckSegGeo = new THREE.CylinderGeometry(0.6 - j*0.04, 0.7 - j*0.04, 0.4, 8);
                    const neckSeg = new THREE.Mesh(neckSegGeo, skinMat);
                    neckSeg.position.set(0, j * 0.35, j * 0.15);
                    neckSeg.rotation.x = Math.PI / 4;
                    neckGroup.add(neckSeg);

                    const ringGeo = new THREE.CylinderGeometry(0.61 - j*0.04, 0.71 - j*0.04, 0.08, 8);
                    const ring = new THREE.Mesh(ringGeo, new THREE.MeshStandardMaterial({
                        color: 0x660000,
                        emissive: 0xff1100,
                        emissiveIntensity: 2.0,
                        flatShading: true
                    }));
                    ring.position.set(0, j * 0.35, j * 0.15);
                    ring.rotation.x = Math.PI / 4;
                    neckGroup.add(ring);
                }

                // エラ
                const gillGeo = new THREE.BoxGeometry(0.2, 0.8, 0.4);
                const gillMat = new THREE.MeshStandardMaterial({
                    color: 0x440000,
                    emissive: 0xff0000,
                    emissiveIntensity: 1.8,
                    flatShading: true
                });
                
                const leftGill = new THREE.Mesh(gillGeo, gillMat);
                leftGill.position.set(0.7, 0.8, 1.2);
                leftGill.rotation.set(0.2, 0.4, -0.2);
                bodyGroup.add(leftGill);
                gills.push(leftGill);

                const rightGill = leftGill.clone();
                rightGill.position.x = -0.7;
                rightGill.rotation.y = -0.4;
                rightGill.rotation.z = 0.2;
                bodyGroup.add(rightGill);
                gills.push(rightGill);

                // 前腕
                leftArm = new THREE.Group();
                leftArm.position.set(0.7, 0.8, 0.9);
                
                const lShoulder = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.16, 0.6, 5), skinMat);
                lShoulder.rotation.z = -0.3;
                lShoulder.rotation.x = 0.5;
                leftArm.add(lShoulder);

                const lForearm = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.12, 0.5, 5), skinMat);
                lForearm.position.set(0.05, -0.4, 0.15);
                lForearm.rotation.x = 0.8;
                leftArm.add(lForearm);

                const clawMat = new THREE.MeshStandardMaterial({ color: 0xdddddd, roughness: 0.3 });
                for(let c = -1; c <= 1; c++) {
                    const claw = new THREE.Mesh(new THREE.ConeGeometry(0.04, 0.15, 4), clawMat);
                    claw.position.set(c * 0.06, -0.3, 0.1);
                    claw.rotation.x = Math.PI / 3;
                    lForearm.add(claw);
                }
                bodyGroup.add(leftArm);

                rightArm = leftArm.clone();
                rightArm.position.x = -0.7;
                rightArm.children[0].rotation.z = 0.3;
                rightArm.children[1].position.x = -0.05;
                bodyGroup.add(rightArm);

                // 頭部、目、歯
                const headGroup = new THREE.Group();
                headGroup.position.set(0, 1.7, 2.1);
                neckGroup.add(headGroup);

                const skullGeo = new THREE.BoxGeometry(0.65, 0.5, 1.2);
                headMesh = new THREE.Mesh(skullGeo, skinMat);
                headMesh.position.set(0, 0, 0);
                headMesh.castShadow = true;
                headGroup.add(headMesh);

                jawMesh = new THREE.Group();
                jawMesh.position.set(0, -0.22, 0.15);
                headGroup.add(jawMesh);
                const jawBase = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.2, 1.0), skinMat);
                jawMesh.add(jawBase);

                const toothGeo = new THREE.ConeGeometry(0.03, 0.12, 4);
                const toothMat = new THREE.MeshBasicMaterial({ color: 0xeeeeee });
                for (let t = 0; t < 6; t++) {
                    const toothU = new THREE.Mesh(toothGeo, toothMat);
                    toothU.position.set((t - 2.5) * 0.08, -0.2, 0.4 - Math.abs(t - 2.5)*0.02);
                    toothU.rotation.x = Math.PI;
                    headMesh.add(toothU);

                    const toothL = new THREE.Mesh(toothGeo, toothMat);
                    toothL.position.set((t - 2.5) * 0.08, 0.1, 0.4 - Math.abs(t - 2.5)*0.02);
                    jawMesh.add(toothL);
                }

                // 黄色い目
                const eyeWhiteGeo = new THREE.SphereGeometry(0.13, 10, 10);
                const eyeBlackGeo = new THREE.SphereGeometry(0.05, 6, 6);
                const eyeWhiteMat = new THREE.MeshBasicMaterial({ color: 0xffdd11 });
                const eyeBlackMat = new THREE.MeshBasicMaterial({ color: 0x000000 });

                const lEyeWhite = new THREE.Mesh(eyeWhiteGeo, eyeWhiteMat);
                lEyeWhite.position.set(0.33, 0.1, 0.2);
                const lEyeBlack = new THREE.Mesh(eyeBlackGeo, eyeBlackMat);
                lEyeBlack.position.set(0.03, 0, 0.09);
                lEyeWhite.add(lEyeBlack);
                headMesh.add(lEyeWhite);

                const rEyeWhite = lEyeWhite.clone();
                rEyeWhite.position.x = -0.33;
                rEyeWhite.children[0].position.x = -0.03;
                headMesh.add(rEyeWhite);

                // 脚
                const legGroup = new THREE.Group();
                godzilla.add(legGroup);

                const lLegUpperGeo = new THREE.CylinderGeometry(0.55, 1.0, 2.4, 8);
                const lLegUpper = new THREE.Mesh(lLegUpperGeo, skinMat);
                lLegUpper.position.set(1.1, 1.4, -0.2);
                lLegUpper.rotation.set(0.4, 0, -0.1);
                lLegUpper.castShadow = true;
                legGroup.add(lLegUpper);

                const lLegLowerGeo = new THREE.CylinderGeometry(0.8, 1.0, 1.6, 8);
                const lLegLower = new THREE.Mesh(lLegLowerGeo, skinMat);
                lLegLower.position.set(0, -1.0, 0.2);
                lLegLower.rotation.x = -0.6;
                lLegLower.castShadow = true;
                lLegUpper.add(lLegLower);

                const lFoot = new THREE.Mesh(new THREE.BoxGeometry(1.0, 0.5, 1.6), skinMat);
                lFoot.position.set(0, -0.8, 0.2);
                lFoot.castShadow = true;
                lLegLower.add(lFoot);

                for(let c = -1; c <= 1; c++) {
                    const claw = new THREE.Mesh(new THREE.ConeGeometry(0.14, 0.4, 4), clawMat);
                    claw.position.set(c * 0.35, -0.15, 0.8);
                    claw.rotation.x = Math.PI / 2.5;
                    lFoot.add(claw);
                }

                const rLegUpper = lLegUpper.clone();
                rLegUpper.position.x = -1.1;
                rLegUpper.rotation.z = 0.1;
                legGroup.add(rLegUpper);

                // 灰白色の骨っぽ背鰭
                const platesGroup = new THREE.Group();
                bodyGroup.add(platesGroup);
                for (let i = 0; i < 11; i++) {
                    const ratio = i / 10;
                    const size = 0.95 * (1.0 - ratio * 0.4);
                    
                    const pGlowMat = new THREE.MeshStandardMaterial({
                        color: 0x888888,
                        emissive: 0xff2200,
                        emissiveIntensity: 1.8,
                        roughness: 0.8,
                        flatShading: true
                    });

                    const plateGeo = new THREE.ConeGeometry(0.24 * size, 1.3 * size, 4);
                    const plate = new THREE.Mesh(plateGeo, pGlowMat);
                    plate.position.set(0, 1.4 - (i * 0.3), -0.5 - (i * 0.24));
                    plate.rotation.x = -Math.PI / 3;
                    platesGroup.add(plate);
                    backPlates.push(plate);
                }

            } else {
                // --- 第4形態 (通常完全体) の構築 ---
                bodyGroup.position.set(0, 4.5, 0);

                // 下半身
                const hipsGeo = new THREE.CylinderGeometry(1.6, 2.8, 4, 10);
                const hips = new THREE.Mesh(hipsGeo, skinMat);
                hips.position.y = -1;
                hips.castShadow = true;
                hips.receiveShadow = true;
                bodyGroup.add(hips);

                // 胸部
                const chestGeo = new THREE.CylinderGeometry(1.0, 1.8, 3.5, 10);
                bodyMesh = new THREE.Mesh(chestGeo, skinMat);
                bodyMesh.position.y = 1.8;
                bodyMesh.castShadow = true;
                bodyGroup.add(bodyMesh);

                // 突起
                for(let i=0; i<30; i++) {
                    const detailGeo = new THREE.DodecahedronGeometry(0.3 + Math.random()*0.3);
                    const detail = new THREE.Mesh(detailGeo, skinMat);
                    detail.position.set(
                        (Math.random() - 0.5) * 2.5,
                        (Math.random() - 0.5) * 4 + 1,
                        (Math.random() - 0.5) * 2.5
                    );
                    bodyGroup.add(detail);
                }

                // 腕
                leftArm = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.25, 1.2, 5), skinMat);
                leftArm.position.set(1.1, 2.3, 0.8);
                leftArm.rotation.set(0.5, 0.3, -0.6);
                bodyGroup.add(leftArm);

                rightArm = leftArm.clone();
                rightArm.position.x = -1.1;
                rightArm.rotation.set(0.5, -0.3, 0.6);
                bodyGroup.add(rightArm);

                const handGeo = new THREE.ConeGeometry(0.15, 0.6, 4);
                const leftHand = new THREE.Mesh(handGeo, skinMat);
                leftHand.position.set(0, 0.6, 0.2);
                leftHand.rotation.x = 0.5;
                leftArm.add(leftHand);
                
                const rightHand = leftHand.clone();
                rightArm.add(rightHand);

                // 頭部
                const headGroup = new THREE.Group();
                headGroup.position.set(0, 3.8, 0.8);
                bodyGroup.add(headGroup);

                const skullGeo = new THREE.BoxGeometry(0.9, 0.7, 1.6);
                headMesh = new THREE.Mesh(skullGeo, skinMat);
                headMesh.position.set(0, 0.4, 0.6);
                headMesh.castShadow = true;
                headGroup.add(headMesh);

                // 目
                const eyeGeo = new THREE.SphereGeometry(0.08, 6, 6);
                const eyeMat = new THREE.MeshBasicMaterial({ color: 0xdddddd });
                const leftEye = new THREE.Mesh(eyeGeo, eyeMat);
                leftEye.position.set(0.48, 0.4, 0.8);
                headMesh.add(leftEye);
                const rightEye = leftEye.clone();
                rightEye.position.x = -0.48;
                headMesh.add(rightEye);

                // 下顎
                jawMesh = new THREE.Group();
                jawMesh.position.set(0, 0.05, 0.6);
                headGroup.add(jawMesh);

                const jawBaseGeo = new THREE.BoxGeometry(0.85, 0.3, 1.4);
                const jawBase = new THREE.Mesh(jawBaseGeo, skinMat);
                jawBase.position.y = -0.15;
                jawMesh.add(jawBase);

                const mouthGlowGeo = new THREE.BoxGeometry(0.6, 0.1, 1.1);
                const mouthGlow = new THREE.Mesh(mouthGlowGeo, glowMat);
                mouthGlow.position.set(0, 0.05, 0.1);
                jawMesh.add(mouthGlow);

                // 脚
                const legGroup = new THREE.Group();
                godzilla.add(legGroup);

                const lLegUpperGeo = new THREE.CylinderGeometry(0.9, 1.6, 3.5, 8);
                const lLegUpper = new THREE.Mesh(lLegUpperGeo, skinMat);
                lLegUpper.position.set(1.9, 3.5, -0.5);
                lLegUpper.rotation.z = -0.1;
                lLegUpper.castShadow = true;
                legGroup.add(lLegUpper);

                const lLegLowerGeo = new THREE.CylinderGeometry(1.4, 1.8, 3.0, 8);
                const lLegLower = new THREE.Mesh(lLegLowerGeo, skinMat);
                lLegLower.position.set(0, -1.8, 0.5);
                lLegLower.rotation.x = 0.2;
                lLegLower.castShadow = true;
                lLegUpper.add(lLegLower);

                const footGeo = new THREE.BoxGeometry(1.8, 1.0, 2.8);
                const lFoot = new THREE.Mesh(footGeo, skinMat);
                lFoot.position.set(0, -1.5, 0.4);
                lFoot.castShadow = true;
                lLegLower.add(lFoot);

                const clawGeo = new THREE.ConeGeometry(0.25, 0.8, 4);
                const clawMat = new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.5 });
                for(let c = -1; c <= 1; c++) {
                    const claw = new THREE.Mesh(clawGeo, clawMat);
                    claw.position.set(c * 0.6, -0.3, 1.4);
                    claw.rotation.x = Math.PI / 2.5;
                    lFoot.add(claw);
                }

                const rLegUpper = lLegUpper.clone();
                rLegUpper.position.x = -1.9;
                rLegUpper.rotation.z = 0.1;
                legGroup.add(rLegUpper);

                // 背鰭
                const platesGroup = new THREE.Group();
                bodyGroup.add(platesGroup);

                const numPlates = 14;
                for (let i = 0; i < numPlates; i++) {
                    const ratio = i / (numPlates - 1);
                    const size = 1.3 * (1.0 - ratio * 0.4);
                    
                    const plateGeo = new THREE.ConeGeometry(0.35 * size, 1.8 * size, 4);
                    const mainPlate = new THREE.Mesh(plateGeo, glowMat);
                    mainPlate.position.set(0, 2.5 - (i * 0.45), -1.1 - (i * 0.15));
                    mainPlate.rotation.x = -Math.PI / 3 - (ratio * 0.2);
                    platesGroup.add(mainPlate);
                    backPlates.push(mainPlate);

                    if (i < 12) {
                        const lPlate = mainPlate.clone();
                        lPlate.scale.set(0.6, 0.8, 0.6);
                        lPlate.position.x = 0.45;
                        lPlate.position.z += 0.1;
                        lPlate.rotation.z = 0.2;
                        platesGroup.add(lPlate);
                        backPlates.push(lPlate);

                        const rPlate = lPlate.clone();
                        rPlate.position.x = -0.45;
                        rPlate.rotation.z = -0.2;
                        platesGroup.add(rPlate);
                        backPlates.push(rPlate);
                    }
                }
            }

            // --- 尻尾 (連結関節構造、形態別サイズスケール) ---
            const totalTailSegments = phase === 2 ? 18 : (phase === 3 ? 19 : 20);
            let lastSegment = bodyGroup;

            for (let i = 0; i < totalTailSegments; i++) {
                const ratio = i / totalTailSegments;
                let thickness = 2.0 * (1.0 - ratio * 0.88);
                let segLength = 1.2 - (ratio * 0.4);

                if (phase === 2) {
                    thickness *= 0.50; // 画像の細い尻尾に補正
                    segLength *= 0.70;
                } else if (phase === 3) {
                    thickness *= 0.75;
                    segLength *= 0.88;
                }

                const segGeo = new THREE.CylinderGeometry(thickness * 0.82, thickness, segLength, 8);
                const segMesh = new THREE.Mesh(segGeo, skinMat);
                segMesh.castShadow = true;
                
                segMesh.rotation.x = Math.PI / 2.0;
                
                const bone = new THREE.Group();
                if (i === 0) {
                    if (phase === 2) {
                        bone.position.set(0, -0.1, -1.8);
                    } else if (phase === 3) {
                        bone.position.set(0, -0.5, -2.0);
                    } else {
                        bone.position.set(0, -0.8, -2.4);
                    }
                } else {
                    bone.position.set(0, 0, -segLength * 0.95);
                }

                bone.add(segMesh);
                lastSegment.add(bone);
                lastSegment = bone;
                tailSegments.push(bone);

                if (i < totalTailSegments - 2) {
                    const tPlateGeo = new THREE.ConeGeometry(thickness * 0.2, segLength * 1.5, 4);
                    
                    const tGlowMat = phase === 2 ? new THREE.MeshStandardMaterial({
                        color: 0x6e4e3b,
                        emissive: 0xff3300,
                        emissiveIntensity: 1.0,
                        flatShading: true
                    }) : (phase === 3 ? new THREE.MeshStandardMaterial({
                        color: 0x888888,
                        emissive: 0xff2200,
                        emissiveIntensity: 1.8,
                        flatShading: true
                    }) : glowMat);

                    const tPlate = new THREE.Mesh(tPlateGeo, tGlowMat);
                    tPlate.position.set(0, thickness * 0.6, 0);
                    tPlate.rotation.x = -Math.PI / 3;
                    segMesh.add(tPlate);
                    tailPlates.push(tPlate);
                }

                if (i === totalTailSegments - 1) {
                    const tipScale = (phase === 2 ? 0.45 : (phase === 3 ? 0.75 : 1.0));
                    const tipGlowGeo = new THREE.SphereGeometry(0.35 * tipScale, 8, 8);
                    
                    const tipGlowMat = phase === 2 ? new THREE.MeshStandardMaterial({
                        color: 0x6e4e3b,
                        emissive: 0xff3300,
                        emissiveIntensity: 1.0,
                        flatShading: true
                    }) : (phase === 3 ? new THREE.MeshStandardMaterial({
                        color: 0x888888,
                        emissive: 0xff2200,
                        emissiveIntensity: 1.8,
                        flatShading: true
                    }) : glowMat);

                    const tipGlow = new THREE.Mesh(tipGlowGeo, tipGlowMat);
                    tipGlow.position.set(0, 0, -0.4);
                    segMesh.add(tipGlow);
                    tailPlates.push(tipGlow);

                    for(let k=0; k<6; k++) {
                        const microSpikeGeo = new THREE.ConeGeometry(0.06, 0.4, 4);
                        const microSpike = new THREE.Mesh(microSpikeGeo, skinMat);
                        microSpike.position.set(
                            (Math.random()-0.5)*0.3,
                            (Math.random()-0.5)*0.3,
                            -0.5 - Math.random()*0.3
                        );
                        microSpike.rotation.set(Math.random(), Math.random(), 0);
                        segMesh.add(microSpike);
                    }
                }
            }

            if (phase === 4) {
                initBeamEffects();
            }
            updateLights();
        }

        // --- 5. 熱線エフェクトの初期化 ---
        function initBeamEffects() {
            beamsGroup = new THREE.Group();
            scene.add(beamsGroup);
            beamsGroup.visible = false;

            const mouthBeamGeo = new THREE.CylinderGeometry(0.05, 0.4, 60, 8);
            mouthBeamGeo.translate(0, 30, 0);
            const mouthBeamMat = new THREE.MeshBasicMaterial({
                color: 0xee88ff,
                transparent: true,
                opacity: 0.95
            });
            const mouthBeam = new THREE.Mesh(mouthBeamGeo, mouthBeamMat);
            mouthBeam.rotation.x = Math.PI / 2;
            mouthBeam.name = "mouthBeam";
            beamsGroup.add(mouthBeam);

            const flareGeo = new THREE.SphereGeometry(1.2, 16, 16);
            const flareMat = new THREE.MeshBasicMaterial({
                color: 0xcc44ff,
                transparent: true,
                opacity: 0.8
            });
            const flare = new THREE.Mesh(flareGeo, flareMat);
            flare.name = "mouthFlare";
            beamsGroup.add(flare);

            const tailBeamGeo = new THREE.CylinderGeometry(0.03, 0.3, 50, 8);
            tailBeamGeo.translate(0, 25, 0);
            const tailBeam = new THREE.Mesh(tailBeamGeo, mouthBeamMat);
            tailBeam.rotation.x = -Math.PI / 4;
            tailBeam.name = "tailBeam";
            beamsGroup.add(tailBeam);

            const backBeams = new THREE.Group();
            backBeams.name = "backBeams";
            for (let i = 0; i < 12; i++) {
                const subBeamGeo = new THREE.CylinderGeometry(0.01, 0.08, 40, 4);
                subBeamGeo.translate(0, 20, 0);
                const subBeam = new THREE.Mesh(subBeamGeo, new THREE.MeshBasicMaterial({
                    color: 0xaa22ff,
                    transparent: true,
                    opacity: 0.6
                }));
                subBeam.rotation.set(
                    -Math.PI/3 + (Math.random() - 0.5) * 0.5,
                    (Math.random() - 0.5) * 2,
                    (Math.random() - 0.5) * 0.5
                );
                subBeam.position.set(0, 5 - i*0.4, -1.5 - i*0.2);
                backBeams.add(subBeam);
            }
            beamsGroup.add(backBeams);

            const sparkGeo = new THREE.BufferGeometry();
            const sparkCount = 100;
            const positions = new Float32Array(sparkCount * 3);
            const velocities = [];

            for(let i=0; i<sparkCount; i++) {
                positions[i*3] = (Math.random() - 0.5) * 2;
                positions[i*3+1] = 6 + (Math.random() - 0.5) * 2;
                positions[i*3+2] = 2 + (Math.random() - 0.5) * 2;
                velocities.push(new THREE.Vector3(
                    (Math.random() - 0.5) * 0.3,
                    (Math.random() - 0.5) * 0.3,
                    (Math.random() + 0.1) * 0.5
                ));
            }
            sparkGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
            
            const sparkMat = new THREE.PointsMaterial({
                color: 0xdd66ff,
                size: 0.25,
                transparent: true,
                blending: THREE.AdditiveBlending
            });
            beamParticles = new THREE.Points(sparkGeo, sparkMat);
            beamParticles.userData = { velocities: velocities };
            beamsGroup.add(beamParticles);
        }

        // --- 6. 環境の構築 ---
        function buildEnvironment() {
            const floorGeo = new THREE.PlaneGeometry(300, 300, 30, 30);
            const pos = floorGeo.attributes.position;
            for (let i = 0; i < pos.count; i++) {
                const vx = pos.getX(i);
                const vy = pos.getY(i);
                const z = Math.sin(vx * 0.05) * Math.cos(vy * 0.05) * 1.5 + (Math.random() - 0.5) * 0.2;
                pos.setZ(i, z);
            }
            floorGeo.computeVertexNormals();

            const floorMat = new THREE.MeshStandardMaterial({
                color: isDayMode ? COLORS.dayGround : COLORS.ground,
                roughness: 0.95,
                metalness: 0.1,
                flatShading: true
            });
            const floor = new THREE.Mesh(floorGeo, floorMat);
            floor.rotation.x = -Math.PI / 2;
            floor.receiveShadow = true;
            scene.add(floor);

            const lavaGridGeo = new THREE.PlaneGeometry(60, 60);
            const lavaGridMat = new THREE.MeshBasicMaterial({
                color: 0x1a0500,
                transparent: true,
                opacity: 0.3
            });
            const lavaGrid = new THREE.Mesh(lavaGridGeo, lavaGridMat);
            lavaGrid.rotation.x = -Math.PI / 2;
            lavaGrid.position.y = 0.01;
            scene.add(lavaGrid);

            const particleCount = 250;
            particleGeometry = new THREE.BufferGeometry();
            const positions = new Float32Array(particleCount * 3);

            for (let i = 0; i < particleCount; i++) {
                positions[i * 3] = (Math.random() - 0.5) * 60;
                positions[i * 3 + 1] = Math.random() * 25;
                positions[i * 3 + 2] = (Math.random() - 0.5) * 60;
            }

            particleGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

            const particleMaterial = new THREE.PointsMaterial({
                color: 0xff3300,
                size: 0.15,
                transparent: true,
                opacity: 0.6,
                blending: THREE.AdditiveBlending
            });

            particles = new THREE.Points(particleGeometry, particleMaterial);
            scene.add(particles);
        }

        // --- 7. UIインタラクション設定 ---
        function setupInteractions() {
            // 形態選択ボタン
            const btnPhase2 = document.getElementById('btn-phase-2');
            const btnPhase3 = document.getElementById('btn-phase-3');
            const btnPhase4 = document.getElementById('btn-phase-4');
            const glowToggle = document.getElementById('phase4-glow-toggle');
            const warningBox = document.getElementById('warning-box');

            function setPhase(phase) {
                if (currentPhase === phase && godzilla) return;
                
                isRoaring = false;
                isBeaming = false;
                isGillEjecting = false;
                isSquirming = false;
                isStandingUp = false;
                if (beamsGroup) beamsGroup.visible = false;

                currentPhase = phase;

                btnPhase2.className = "bg-gray-900 hover:bg-gray-800 text-gray-400 border border-gray-700 font-bold py-1.5 px-1 rounded text-[10px] sm:text-xs transition flex flex-col items-center justify-center active:scale-95";
                btnPhase3.className = "bg-gray-900 hover:bg-gray-800 text-gray-400 border border-gray-700 font-bold py-1.5 px-1 rounded text-[10px] sm:text-xs transition flex flex-col items-center justify-center active:scale-95";
                btnPhase4.className = "bg-gray-900 hover:bg-gray-800 text-gray-400 border border-gray-700 font-bold py-1.5 px-1 rounded text-[10px] sm:text-xs transition flex flex-col items-center justify-center active:scale-95";

                const stateText = document.getElementById('state-text');
                const energyBar = document.getElementById('energy-bar');

                if (phase === 2) {
                    btnPhase2.className = "bg-amber-950/80 hover:bg-amber-900 text-amber-200 border border-amber-500 font-bold py-1.5 px-1 rounded text-[10px] sm:text-xs transition flex flex-col items-center justify-center active:scale-95";
                    
                    document.querySelector('#status-panel h1').innerText = "巨大不明生物第2形態監視モニター";
                    document.querySelector('#status-panel span').className = "inline-block w-2.5 h-2.5 rounded-full bg-amber-500 animate-pulse";
                    
                    stateText.innerText = "這行進中 (EVADING)";
                    stateText.className = "text-amber-400 font-bold animate-pulse";
                    document.getElementById('code-type-text').innerText = "第2形態 (蒲田くん)";
                    document.getElementById('code-type-text').className = "text-amber-400 font-bold";
                    document.getElementById('height-text').innerText = "約 28.0 m";
                    
                    energyBar.style.width = "20%";
                    energyBar.className = "bg-amber-500 h-full transition-all duration-300";

                    document.getElementById('lbl-action-1').innerText = "エラ排熱 (出血)";
                    document.getElementById('icon-action-1').className = "fa-solid fa-water";
                    document.getElementById('lbl-action-2').innerText = "急速蛇行進";
                    document.getElementById('icon-action-2').className = "fa-solid fa-gauge-high";

                    glowToggle.style.display = "none";
                    warningBox.style.display = "none";

                } else if (phase === 3) {
                    btnPhase3.className = "bg-red-950/80 hover:bg-red-900 text-red-200 border border-red-500 font-bold py-1.5 px-1 rounded text-[10px] sm:text-xs transition flex flex-col items-center justify-center active:scale-95";
                    
                    document.querySelector('#status-panel h1').innerText = "巨大不明生物第3形態監視モニター";
                    document.querySelector('#status-panel span').className = "inline-block w-2.5 h-2.5 rounded-full bg-red-600 animate-pulse";
                    
                    stateText.innerText = "急速進化中 (EVOLVING)";
                    stateText.className = "text-red-400 font-bold animate-pulse";
                    document.getElementById('code-type-text').innerText = "第3形態 (品川くん)";
                    document.getElementById('code-type-text').className = "text-red-400 font-bold";
                    document.getElementById('height-text').innerText = "約 57.0 m";
                    
                    energyBar.style.width = "40%";
                    energyBar.className = "bg-red-600 h-full transition-all duration-300";

                    document.getElementById('lbl-action-1').innerText = "未完の咆哮";
                    document.getElementById('icon-action-1').className = "fa-solid fa-volume-high";
                    document.getElementById('lbl-action-2').innerText = "直立試行";
                    document.getElementById('icon-action-2').className = "fa-solid fa-arrow-up-from-ground";

                    glowToggle.style.display = "none";
                    warningBox.style.display = "none";

                } else {
                    btnPhase4.className = "bg-red-950/80 hover:bg-red-900 text-red-200 border border-red-500 font-bold py-1.5 px-1 rounded text-[10px] sm:text-xs transition flex flex-col items-center justify-center active:scale-95";
                    
                    document.querySelector('#status-panel h1').innerText = "巨大不明生物災害対策本部";
                    const dotColor = isPurpleState ? "bg-purple-500 animate-ping" : "bg-red-600 animate-pulse";
                    document.querySelector('#status-panel span').className = "inline-block w-2.5 h-2.5 rounded-full " + dotColor;
                    
                    stateText.innerText = isPurpleState ? "熱線充填中 (ENERGY CRITICAL)" : "警戒監視中 (STANDBY)";
                    stateText.className = isPurpleState ? "text-purple-400 font-bold animate-pulse" : "text-red-500 font-bold animate-pulse";
                    document.getElementById('code-type-text').innerText = "第4形態 (鎌倉さん)";
                    document.getElementById('code-type-text').className = "text-red-400 font-bold";
                    document.getElementById('height-text').innerText = "118.5 m";
                    
                    energyBar.style.width = isPurpleState ? "95%" : "45%";
                    energyBar.className = isPurpleState ? "bg-purple-500 h-full" : "bg-red-600 h-full";

                    document.getElementById('lbl-action-1').innerText = "咆哮モーション";
                    document.getElementById('icon-action-1').className = "fa-solid fa-volume-high";
                    document.getElementById('lbl-action-2').innerText = "放射線流 発射";
                    document.getElementById('icon-action-2').className = "fa-solid fa-bolt-lightning";

                    glowToggle.style.display = "grid";
                    warningBox.style.display = "block";
                }

                buildGodzilla(phase);
                resetCamera(); // 形態変更時の自動ズーム補正
                playBeepSound(150 + phase * 100, 0.15);
            }

            btnPhase2.addEventListener('click', () => setPhase(2));
            btnPhase3.addEventListener('click', () => setPhase(3));
            btnPhase4.addEventListener('click', () => setPhase(4));

            // 通常/熱線 変更トグル (第4形態用)
            const btnRed = document.getElementById('btn-state-red');
            const btnPurple = document.getElementById('btn-state-purple');

            btnRed.addEventListener('click', () => {
                if (currentPhase === 4) setGodzillaState(false);
            });

            btnPurple.addEventListener('click', () => {
                if (currentPhase === 4) setGodzillaState(true);
            });

            // 昼夜切り替えボタン
            const btnNight = document.getElementById('btn-env-night');
            const btnDay = document.getElementById('btn-env-day');

            btnNight.addEventListener('click', () => setEnvironmentDayMode(false));
            btnDay.addEventListener('click', () => setEnvironmentDayMode(true));

            // アクションボタン分岐
            document.getElementById('btn-roar').addEventListener('click', () => {
                if (currentPhase === 2) {
                    triggerGillEject();
                } else if (currentPhase === 3) {
                    triggerRoarPhase3();
                } else {
                    triggerRoar();
                }
            });

            document.getElementById('btn-beam').addEventListener('click', () => {
                if (currentPhase === 2) {
                    triggerSquirm();
                } else if (currentPhase === 3) {
                    triggerStandUp();
                } else {
                    triggerBeam();
                }
            });

            document.getElementById('btn-reset-cam').addEventListener('click', () => resetCamera());

            // 尾部速度スライダー
            const speedSlider = document.getElementById('tail-speed');
            const speedVal = document.getElementById('tail-speed-val');
            speedSlider.addEventListener('input', (e) => {
                animationSpeed = parseFloat(e.target.value);
                speedVal.innerText = animationSpeed.toFixed(1) + "x";
            });

            // フォグスライダー
            const fogSlider = document.getElementById('fog-density');
            const fogVal = document.getElementById('fog-val');
            fogSlider.addEventListener('input', (e) => {
                const val = parseInt(e.target.value);
                const density = val * 0.01;
                scene.fog.density = density;
                
                if (val === 0) fogVal.innerText = "なし";
                else if (val < 4) fogVal.innerText = "薄い";
                else if (val < 8) fogVal.innerText = "標準";
                else fogVal.innerText = "濃霧";
            });

            // 明るさスライダー
            const brightSlider = document.getElementById('scene-brightness');
            const brightVal = document.getElementById('light-val');
            brightSlider.addEventListener('input', (e) => {
                const val = parseInt(e.target.value);
                brightnessMultiplier = val * 0.1;
                updateLights();

                if (val <= 3) brightVal.innerText = "暗い";
                else if (val <= 7) brightVal.innerText = "通常";
                else if (val <= 11) brightVal.innerText = "明るい";
                else brightVal.innerText = "極光";
            });

            // ---------------- モバイル専用タブ切り替えロジック ----------------
            const tabActions = document.getElementById('tab-actions');
            const tabEnv = document.getElementById('tab-env');
            const envPanel = document.getElementById('env-panel');
            const actionPanel = document.getElementById('action-panel');

            function switchMobileTab(tab) {
                if (tab === 'actions') {
                    tabActions.className = "flex-1 py-2 text-center text-red-500 border-b-2 border-red-500 font-black text-xs transition";
                    tabEnv.className = "flex-1 py-2 text-center text-gray-500 border-b-2 border-transparent text-xs transition";
                    actionPanel.classList.remove('hidden');
                    envPanel.classList.add('hidden');
                } else {
                    tabEnv.className = "flex-1 py-2 text-center text-red-500 border-b-2 border-red-500 font-black text-xs transition";
                    tabActions.className = "flex-1 py-2 text-center text-gray-500 border-b-2 border-transparent text-xs transition";
                    envPanel.classList.remove('hidden');
                    actionPanel.classList.add('hidden');
                }
                playBeepSound(300, 0.05);
            }

            tabActions.addEventListener('click', () => switchMobileTab('actions'));
            tabEnv.addEventListener('click', () => switchMobileTab('env'));

            // ---------------- UI表示/非表示 トグル ----------------
            const btnToggleUi = document.getElementById('btn-toggle-ui');
            const toggleUiIcon = document.getElementById('toggle-ui-icon');
            const toggleUiText = document.getElementById('toggle-ui-text');
            const statusPanel = document.getElementById('status-panel');
            const footer = document.querySelector('footer');

            btnToggleUi.addEventListener('click', () => {
                uiHidden = !uiHidden;
                if (uiHidden) {
                    // UI隠しモード
                    statusPanel.classList.add('opacity-0', 'pointer-events-none', '-translate-y-4');
                    footer.classList.add('opacity-0', 'pointer-events-none', 'translate-y-4');
                    toggleUiIcon.className = "fa-solid fa-eye";
                    toggleUiText.innerText = "UI表示";
                    btnToggleUi.classList.remove('bg-red-950/80', 'border-red-500', 'text-red-200');
                    btnToggleUi.classList.add('bg-black/80', 'border-gray-700', 'text-gray-300');
                } else {
                    // UI通常モード
                    statusPanel.classList.remove('opacity-0', 'pointer-events-none', '-translate-y-4');
                    footer.classList.remove('opacity-0', 'pointer-events-none', 'translate-y-4');
                    toggleUiIcon.className = "fa-solid fa-eye-slash";
                    toggleUiText.innerText = "UI非表示";
                    btnToggleUi.classList.add('bg-red-950/80', 'border-red-500', 'text-red-200');
                    btnToggleUi.classList.remove('bg-black/80', 'border-gray-700', 'text-gray-300');
                }
                playBeepSound(400, 0.08);
            });
        }

        // 環境モード(昼夜)切り替え
        function setEnvironmentDayMode(toDay) {
            if (isDayMode === toDay) return;
            isDayMode = toDay;

            const btnNight = document.getElementById('btn-env-night');
            const btnDay = document.getElementById('btn-env-day');

            if (isDayMode) {
                btnDay.className = "bg-amber-950/40 hover:bg-amber-900/60 text-amber-400 py-1.5 px-2 rounded border border-amber-800/80 text-[10px] sm:text-xs font-bold transition flex items-center justify-center space-x-1 active:scale-95";
                btnNight.className = "bg-gray-900 hover:bg-gray-800 text-gray-400 py-1.5 px-2 rounded border border-gray-700 text-[10px] sm:text-xs font-bold transition flex items-center justify-center space-x-1 active:scale-95";
            } else {
                btnNight.className = "bg-red-950/40 hover:bg-red-900/60 text-red-400 py-1.5 px-2 rounded border border-red-800/80 text-[10px] sm:text-xs font-bold transition flex items-center justify-center space-x-1 active:scale-95";
                btnDay.className = "bg-gray-900 hover:bg-gray-800 text-gray-400 py-1.5 px-2 rounded border border-gray-700 text-[10px] sm:text-xs font-bold transition flex items-center justify-center space-x-1 active:scale-95";
            }

            // 地面の色の変更
            scene.traverse((child) => {
                if (child.isMesh && child.geometry && child.geometry.type === "PlaneGeometry") {
                    if (child.material && child.material.color) {
                        child.material.color.setHex(isDayMode ? COLORS.dayGround : COLORS.ground);
                    }
                }
            });

            updateLights();
            playBeepSound(300, 0.1);
        }

        // 第4形態用状態切り替え (赤 ⇔ 紫)
        function setGodzillaState(toPurple) {
            if (isPurpleState === toPurple) return;
            isPurpleState = toPurple;

            const btnRed = document.getElementById('btn-state-red');
            const btnPurple = document.getElementById('btn-state-purple');
            const statusPanel = document.getElementById('status-panel');
            const actionPanel = document.getElementById('action-panel');
            const statusDot = document.getElementById('status-dot');
            const stateText = document.getElementById('state-text');
            const energyBar = document.getElementById('energy-bar');

            const emissiveColor = isPurpleState ? COLORS.purpleGlow : COLORS.redGlow;
            const targetColor = new THREE.Color(emissiveColor);
            
            backPlates.concat(tailPlates).forEach(plate => {
                plate.material.emissive.set(targetColor);
                plate.material.emissiveIntensity = isPurpleState ? 3.5 : 2.5;
            });

            glowLights.forEach(light => {
                light.color.set(targetColor);
            });

            particles.material.color.set(isPurpleState ? 0x9955ff : 0xff3300);

            updateLights();

            if (isPurpleState) {
                btnPurple.className = "bg-purple-950/80 hover:bg-purple-900 text-purple-200 border border-purple-500 font-bold py-1.5 px-2 rounded text-[10px] sm:text-xs transition flex items-center justify-center space-x-1.5 active:scale-95";
                btnRed.className = "bg-gray-900 hover:bg-gray-800 text-gray-400 border border-gray-700 font-bold py-1.5 px-2 rounded text-[10px] sm:text-xs transition flex items-center justify-center space-x-1.5 active:scale-95";
                
                statusPanel.className = "bg-black/80 p-3 sm:p-4 rounded neon-border-purple transition-all duration-300 transform w-full max-w-[280px] sm:max-w-sm md:max-w-md";
                actionPanel.className = "bg-black/80 p-3 sm:p-4 rounded neon-border-purple transition-all duration-500 w-full md:w-96 space-y-3 sm:space-y-4";
                statusDot.className = "inline-block w-2.5 h-2.5 rounded-full bg-purple-500 animate-ping";
                stateText.innerText = "熱線充填中 (ENERGY CRITICAL)";
                stateText.className = "text-purple-400 font-bold animate-pulse";
                energyBar.style.width = "95%";
                energyBar.className = "bg-purple-500 h-full transition-all duration-300";
            } else {
                btnRed.className = "bg-red-950/80 hover:bg-red-900 text-red-200 border border-red-500 font-bold py-1.5 px-2 rounded text-[10px] sm:text-xs transition flex items-center justify-center space-x-1.5 active:scale-95";
                btnPurple.className = "bg-gray-900 hover:bg-gray-800 text-gray-400 border border-gray-700 font-bold py-1.5 px-2 rounded text-[10px] sm:text-xs transition flex items-center justify-center space-x-1.5 active:scale-95";
                
                statusPanel.className = "bg-black/80 p-3 sm:p-4 rounded neon-border transition-all duration-300 transform w-full max-w-[280px] sm:max-w-sm md:max-w-md";
                actionPanel.className = "bg-black/80 p-3 sm:p-4 rounded neon-border transition-all duration-500 w-full md:w-96 space-y-3 sm:space-y-4";
                statusDot.className = "inline-block w-2.5 h-2.5 rounded-full bg-red-600 animate-pulse";
                stateText.innerText = "警戒監視中 (STANDBY)";
                stateText.className = "text-red-500 font-bold animate-pulse";
                energyBar.style.width = "45%";
                energyBar.className = "bg-red-600 h-full transition-all duration-300";
                
                if (isBeaming) {
                    isBeaming = false;
                    beamsGroup.visible = false;
                }
            }

            playBeepSound(isPurpleState ? 440 : 220, 0.15);
        }

        // --- 8. ゴジラ特殊アクション ---

        // アクション: 第2形態 エラ排熱 (血液ブシャー)
        function triggerGillEject() {
            if (isGillEjecting) return;
            isGillEjecting = true;
            gillEjectTimer = 0;

            const stateText = document.getElementById('state-text');
            stateText.innerText = "エラ排熱 (EJECTING)";
            stateText.className = "text-red-500 font-bold animate-pulse";

            try {
                if (audioCtx.state === 'suspended') audioCtx.resume();
                const duration = 1.5;
                const bufferSize = audioCtx.sampleRate * duration;
                const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
                const data = buffer.getChannelData(0);
                for (let i = 0; i < bufferSize; i++) {
                    data[i] = (Math.random() * 2 - 1) * 0.4;
                }
                const noise = audioCtx.createBufferSource();
                noise.buffer = buffer;
                
                const filter = audioCtx.createBiquadFilter();
                filter.type = 'lowpass';
                filter.frequency.setValueAtTime(320, audioCtx.currentTime);
                filter.frequency.exponentialRampToValueAtTime(70, audioCtx.currentTime + duration);

                const gain = audioCtx.createGain();
                gain.gain.setValueAtTime(0.6, audioCtx.currentTime);
                gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);

                noise.connect(filter);
                filter.connect(gain);
                gain.connect(audioCtx.destination);
                noise.start();
            } catch(e){}

            setTimeout(() => {
                isGillEjecting = false;
                stateText.innerText = "這行進中 (EVADING)";
                stateText.className = "text-amber-400 font-bold animate-pulse";
            }, 2500);
        }

        // アクション: 第2形態 急速蛇行
        function triggerSquirm() {
            if (isSquirming) return;
            isSquirming = true;
            squirmTimer = 0;

            const stateText = document.getElementById('state-text');
            stateText.innerText = "急速蛇行進 (SQUIRMING)";
            stateText.className = "text-amber-500 font-bold animate-bounce";

            try {
                if (audioCtx.state === 'suspended') audioCtx.resume();
                const duration = 2.5;
                const osc = audioCtx.createOscillator();
                osc.type = 'sawtooth';
                osc.frequency.setValueAtTime(35, audioCtx.currentTime);
                osc.frequency.linearRampToValueAtTime(80, audioCtx.currentTime + 1.0);
                osc.frequency.linearRampToValueAtTime(15, audioCtx.currentTime + 2.5);

                const filter = audioCtx.createBiquadFilter();
                filter.type = 'bandpass';
                filter.frequency.setValueAtTime(120, audioCtx.currentTime);

                const gain = audioCtx.createGain();
                gain.gain.setValueAtTime(0.4, audioCtx.currentTime);
                gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);

                osc.connect(filter);
                filter.connect(gain);
                gain.connect(audioCtx.destination);
                osc.start();
                osc.stop(audioCtx.currentTime + duration);
            } catch(e){}

            setTimeout(() => {
                isSquirming = false;
                stateText.innerText = "這行進中 (EVADING)";
                stateText.className = "text-amber-400 font-bold animate-pulse";
            }, 3000);
        }

        // アクション: 第3形態 未完の咆哮
        function triggerRoarPhase3() {
            if (isRoaring) return;
            isRoaring = true;
            roarTimer = 0;

            const stateText = document.getElementById('state-text');
            stateText.innerText = "未完の咆哮 (ROARING)";
            stateText.className = "text-red-400 font-bold animate-bounce";

            try {
                if (audioCtx.state === 'suspended') audioCtx.resume();
                const duration = 1.8;
                const osc = audioCtx.createOscillator();
                osc.type = 'sawtooth';
                osc.frequency.setValueAtTime(110, audioCtx.currentTime);
                osc.frequency.linearRampToValueAtTime(180, audioCtx.currentTime + 0.3);
                osc.frequency.exponentialRampToValueAtTime(55, audioCtx.currentTime + 1.5);

                const filter = audioCtx.createBiquadFilter();
                filter.type = 'bandpass';
                filter.frequency.setValueAtTime(280, audioCtx.currentTime);

                const gain = audioCtx.createGain();
                gain.gain.setValueAtTime(0.4, audioCtx.currentTime);
                gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);

                osc.connect(filter);
                filter.connect(gain);
                gain.connect(audioCtx.destination);
                osc.start();
                osc.stop(audioCtx.currentTime + duration);
            } catch(e){}

            setTimeout(() => {
                isRoaring = false;
                stateText.innerText = "急速進化中 (EVOLVING)";
                stateText.className = "text-red-400 font-bold animate-pulse";
            }, 2000);
        }

        // アクション: 第3形態 直立試行
        function triggerStandUp() {
            if (isStandingUp) return;
            isStandingUp = true;
            standUpTimer = 0;

            const stateText = document.getElementById('state-text');
            stateText.innerText = "直立試行 (ERECTING)";
            stateText.className = "text-red-500 font-bold animate-pulse";

            try {
                if (audioCtx.state === 'suspended') audioCtx.resume();
                const osc = audioCtx.createOscillator();
                osc.type = 'triangle';
                osc.frequency.setValueAtTime(30, audioCtx.currentTime);
                osc.frequency.exponentialRampToValueAtTime(8, audioCtx.currentTime + 1.0);
                const gain = audioCtx.createGain();
                gain.gain.setValueAtTime(0.9, audioCtx.currentTime);
                gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 1.5);
                osc.connect(gain);
                gain.connect(audioCtx.destination);
                osc.start();
                osc.stop(audioCtx.currentTime + 1.5);
            } catch(e){}

            setTimeout(() => {
                isStandingUp = false;
                stateText.innerText = "急速進化中 (EVOLVING)";
                stateText.className = "text-red-400 font-bold animate-pulse";
            }, 4000);
        }

        // アクション: 第4形態 咆哮モーション
        function triggerRoar() {
            if (isRoaring) return;
            isRoaring = true;
            roarTimer = 0;

            const stateText = document.getElementById('state-text');
            const originalText = stateText.innerText;
            const originalClass = stateText.className;

            stateText.innerText = "咆哮検知 (ROARING)";
            stateText.className = "text-amber-400 font-bold animate-bounce";

            playRoarSound();

            setTimeout(() => {
                isRoaring = false;
                stateText.innerText = originalText;
                stateText.className = originalClass;
            }, 2500);
        }

        // アクション: 第4形態 放射線流発射
        function triggerBeam() {
            if (isBeaming) return;

            if (!isPurpleState) {
                setGodzillaState(true);
                setTimeout(startBeamFiring, 800);
            } else {
                startBeamFiring();
            }
        }

        function startBeamFiring() {
            isBeaming = true;
            beamTimer = 0;
            beamsGroup.visible = true;

            const stateText = document.getElementById('state-text');
            stateText.innerText = "放射線流発射 (THERMAL BEAM)";
            stateText.className = "text-purple-400 font-bold animate-ping";

            playBeamSound();

            setTimeout(() => {
                isBeaming = false;
                beamsGroup.visible = false;
                
                const originalText = isPurpleState ? "熱線充填中 (ENERGY CRITICAL)" : "警戒監視中 (STANDBY)";
                const originalClass = isPurpleState ? "text-purple-400 font-bold animate-pulse" : "text-red-500 font-bold animate-pulse";
                stateText.innerText = originalText;
                stateText.className = originalClass;
            }, 4500);
        }

        // --- 9. シンセサイザー効果音生成 (Web Audio API) ---
        function playBeepSound(freq, duration) {
            try {
                if (audioCtx.state === 'suspended') audioCtx.resume();
                const osc = audioCtx.createOscillator();
                const gain = audioCtx.createGain();
                osc.type = 'sine';
                osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
                gain.gain.setValueAtTime(0.1, audioCtx.currentTime);
                gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
                osc.connect(gain);
                gain.connect(audioCtx.destination);
                osc.start();
                osc.stop(audioCtx.currentTime + duration);
            } catch (e) {}
        }

        function playRoarSound() {
            try {
                if (audioCtx.state === 'suspended') audioCtx.resume();
                const duration = 2.0;
                const bufferSize = audioCtx.sampleRate * duration;
                const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
                const data = buffer.getChannelData(0);
                for (let i = 0; i < bufferSize; i++) {
                    data[i] = Math.random() * 2 - 1;
                }

                const noise = audioCtx.createBufferSource();
                noise.buffer = buffer;

                const filter = audioCtx.createBiquadFilter();
                filter.type = 'bandpass';
                filter.frequency.setValueAtTime(80, audioCtx.currentTime);
                filter.frequency.exponentialRampToValueAtTime(350, audioCtx.currentTime + 0.3);
                filter.frequency.exponentialRampToValueAtTime(100, audioCtx.currentTime + 1.2);
                filter.Q.setValueAtTime(2.0, audioCtx.currentTime);

                const osc = audioCtx.createOscillator();
                osc.type = 'sawtooth';
                osc.frequency.setValueAtTime(65, audioCtx.currentTime);
                osc.frequency.linearRampToValueAtTime(120, audioCtx.currentTime + 0.4);
                osc.frequency.exponentialRampToValueAtTime(45, audioCtx.currentTime + 1.5);

                const oscGain = audioCtx.createGain();
                oscGain.gain.setValueAtTime(0.3, audioCtx.currentTime);
                oscGain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 1.8);

                const gain = audioCtx.createGain();
                gain.gain.setValueAtTime(0.4, audioCtx.currentTime);
                gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);

                noise.connect(filter);
                filter.connect(gain);
                
                osc.connect(oscGain);
                oscGain.connect(gain);
                
                gain.connect(audioCtx.destination);

                noise.start();
                osc.start();
                noise.stop(audioCtx.currentTime + duration);
                osc.stop(audioCtx.currentTime + duration);
            } catch (e) {}
        }

        function playBeamSound() {
            try {
                if (audioCtx.state === 'suspended') audioCtx.resume();
                const duration = 4.0;
                
                const osc = audioCtx.createOscillator();
                osc.type = 'sawtooth';
                osc.frequency.setValueAtTime(40, audioCtx.currentTime);
                osc.frequency.exponentialRampToValueAtTime(1200, audioCtx.currentTime + 0.8);
                osc.frequency.setValueAtTime(800, audioCtx.currentTime + 1.2);

                const filter = audioCtx.createBiquadFilter();
                filter.type = 'peaking';
                filter.frequency.setValueAtTime(1000, audioCtx.currentTime);
                filter.Q.setValueAtTime(5, audioCtx.currentTime);

                const gain = audioCtx.createGain();
                gain.gain.setValueAtTime(0.01, audioCtx.currentTime);
                gain.gain.linearRampToValueAtTime(0.4, audioCtx.currentTime + 0.4);
                gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);

                osc.connect(filter);
                filter.connect(gain);
                gain.connect(audioCtx.destination);

                osc.start();
                osc.stop(audioCtx.currentTime + duration);
            } catch (e) {}
        }

        // --- 10. メイン・アニメーションループ ---
        const clock = new THREE.Clock();

        function animate() {
            requestAnimationFrame(animate);

            const delta = clock.getDelta();
            const time = clock.getElapsedTime();

            // 発光脈動 (全形態共通)
            const glowSpeed = isPurpleState ? 5.0 : 2.5;
            const glowBase = isPurpleState ? 3.0 : 2.0;
            const pulse = glowBase + Math.sin(time * glowSpeed) * 1.2;
            
            backPlates.concat(tailPlates).forEach((plate, idx) => {
                const individualPulse = pulse + Math.sin(time * 3 + idx * 0.2) * 0.5;
                plate.material.emissiveIntensity = Math.max(0.5, individualPulse);
            });

            glowLights.forEach((light, index) => {
                light.intensity = pulse * (index === 0 ? 3.5 : 2.0);
            });


            // ==================== 形態別アニメーション定義 ====================

            if (currentPhase === 2) {
                // ------------------ 第2形態 (蒲田くん) ------------------
                const currentSpeed = isSquirming ? 2.8 : animationSpeed;
                const squirmAmp = isSquirming ? 1.6 : 1.0;
                
                // 蛇行:這いずりながら体を大きく左右にクネクネ振る
                if (godzilla.children[0]) {
                    godzilla.children[0].rotation.y = Math.sin(time * 3.5 * currentSpeed) * 0.16 * squirmAmp;
                    godzilla.children[0].position.x = Math.sin(time * 3.5 * currentSpeed) * 0.55 * squirmAmp;
                }

                // ギョロ目のピクピクとした不揃いな視線の動き
                if (headMesh && headMesh.children[0] && headMesh.children[1]) {
                    headMesh.children[0].children[0].position.x = 0.04 + Math.sin(time * 8) * 0.01;
                    headMesh.children[1].children[0].position.x = -0.04 + Math.sin(time * 8 + 3) * 0.01;
                }

                // エラスリットのひくひく脈動
                gills.forEach((gill, idx) => {
                    const individualDelay = Math.floor(idx / 2) * 0.5;
                    const breath = 1.0 + Math.sin(time * 4.0 - individualDelay) * 0.18;
                    gill.scale.set(breath, 1.0, 1.0);
                });

                // 首の開口部 (出血排熱アクション時)
                if (isGillEjecting) {
                    jawMesh.position.y = -0.22 - Math.sin(time * 20.0) * 0.04;
                } else {
                    jawMesh.position.y += (-0.12 - jawMesh.position.y) * 0.08;
                }

                // 尻尾の大きな横振りのうねり (完全水平を保ち、画像準拠のまっすぐな美シルエット)
                tailSegments.forEach((bone, index) => {
                    const delay = index * 0.25;
                    const waveY = Math.sin(time * 3.0 * currentSpeed - delay) * (0.12 + index * 0.009) * squirmAmp;
                    bone.rotation.y = waveY;
                    
                    let baseAngleX = 0;
                    if (index === 0) {
                        baseAngleX = -Math.PI / 12; // 胴体の前傾をキャンセル
                    } else {
                        baseAngleX = 0.02; // 水平直線
                    }
                    bone.rotation.x = baseAngleX + Math.sin(time * 1.5 - delay) * 0.01;
                });

                // エラ排熱スプラッシュパーティクル制御
                if (isGillEjecting && gillParticles) {
                    const posAttr = gillParticles.geometry.attributes.position;
                    const vels = gillParticles.userData.velocities;
                    
                    const leftGillWorld = new THREE.Vector3();
                    const rightGillWorld = new THREE.Vector3();
                    if (gills[2] && gills[3]) {
                        gills[2].getWorldPosition(leftGillWorld);
                        gills[3].getWorldPosition(rightGillWorld);
                    }

                    for (let i = 0; i < posAttr.count; i++) {
                        let px = posAttr.getX(i) + vels[i].x;
                        let py = posAttr.getY(i) + vels[i].y;
                        let pz = posAttr.getZ(i) + vels[i].z;

                        vels[i].y -= 0.008; // 重力

                        if (py < 0.15) {
                            const useLeft = Math.random() > 0.5;
                            const src = useLeft ? leftGillWorld : rightGillWorld;
                            px = src.x;
                            py = src.y;
                            pz = src.z;
                            
                            const dirX = useLeft ? (0.4 + Math.random()*0.5) : (-0.4 - Math.random()*0.5);
                            vels[i].set(dirX, 0.06 + Math.random()*0.1, (Math.random() - 0.5)*0.15);
                        }

                        posAttr.setXYZ(i, px, py, pz);
                    }
                    posAttr.needsUpdate = true;
                    gillParticles.visible = true;
                } else if (gillParticles) {
                    gillParticles.visible = false;
                }

            } else if (currentPhase === 3) {
                // ------------------ 第3形態 (品川くん) ------------------
                const currentSpeed = animationSpeed;
                
                // 直立試行ポーズの制御 (立ち上がる動き)
                if (isStandingUp) {
                    standUpTimer += delta;
                    const targetRotX = -Math.PI / 8;
                    if (godzilla.children[0]) {
                        godzilla.children[0].rotation.x += (targetRotX - godzilla.children[0].rotation.x) * 0.06;
                        if (headMesh) headMesh.parent.rotation.x = -0.5;
                        if (jawMesh) {
                            jawMesh.position.y = -0.32;
                            jawMesh.rotation.x = 0.25;
                        }
                    }
                } else {
                    if (godzilla.children[0]) {
                        godzilla.children[0].rotation.x += (Math.PI / 10 - godzilla.children[0].rotation.x) * 0.06;
                    }
                    if (isRoaring) {
                        roarTimer += delta;
                        const progress = Math.min(roarTimer / 0.5, 1.0);
                        const decay = roarTimer > 1.1 ? Math.max(0, 1 - (roarTimer - 1.1) / 0.7) : 1.0;
                        if (headMesh) headMesh.parent.rotation.x = -0.4 * progress * decay;
                        if (jawMesh) {
                            jawMesh.position.y = -0.25 * progress * decay;
                            jawMesh.rotation.x = 0.18 * progress * decay;
                        }
                    } else {
                        if (headMesh && jawMesh) {
                            headMesh.parent.rotation.x *= 0.92;
                            jawMesh.position.y += (-0.05 - jawMesh.position.y) * 0.08;
                            jawMesh.rotation.x *= 0.92;
                        }
                    }
                }

                // 呼吸ゆらぎ
                if (bodyMesh) {
                    bodyMesh.scale.x = 1.0 + Math.sin(time * 1.5) * 0.02;
                    bodyMesh.scale.z = 1.0 + Math.sin(time * 1.5) * 0.02;
                }

                // 前腕のぶらぶらスイング
                if (leftArm && rightArm) {
                    leftArm.rotation.x = 0.7 + Math.sin(time * 2.5) * 0.12;
                    rightArm.rotation.x = 0.7 - Math.sin(time * 2.5) * 0.12;
                }

                // 尻尾のうねり (ツンと先が上を向くシルエット)
                tailSegments.forEach((bone, index) => {
                    const delay = index * 0.25;
                    const waveY = Math.sin(time * 2.0 * currentSpeed - delay) * (0.07 + index * 0.006);
                    const waveX = Math.cos(time * 1.0 * currentSpeed - delay) * 0.015;

                    bone.rotation.y = waveY;
                    
                    let baseAngleX = 0;
                    if (index === 0) {
                        baseAngleX = -Math.PI / 10; // 前傾キャンセル
                    } else if (index > 13) {
                        baseAngleX = -0.08; // 先端のみツンと反らせる
                    } else {
                        baseAngleX = 0.01;
                    }
                    bone.rotation.x = baseAngleX + waveX;
                });

                if (gillParticles) gillParticles.visible = false;

            } else {
                // ------------------ 第4形態 (鎌倉さん) ------------------
                if (bodyMesh) {
                    bodyMesh.scale.x = 1.0 + Math.sin(time * 1.2) * 0.015;
                    bodyMesh.scale.z = 1.0 + Math.sin(time * 1.2) * 0.015;
                }
                if (leftArm && rightArm) {
                    leftArm.rotation.x = 0.5 + Math.sin(time * 3.0) * 0.04;
                    rightArm.rotation.x = 0.5 - Math.sin(time * 3.0) * 0.04;
                }

                // 尻尾 (浮遊J型そり立ち)
                const tailSpeedMultiplier = animationSpeed;
                tailSegments.forEach((bone, index) => {
                    const delay = index * 0.28;
                    const waveY = Math.sin(time * 2.2 * tailSpeedMultiplier - delay) * (0.06 + index * 0.005);
                    const waveX = Math.cos(time * 1.1 * tailSpeedMultiplier - delay) * 0.02;

                    bone.rotation.y = waveY;
                    
                    let baseAngleX = -0.03;
                    if (index > 3 && index <= 12) {
                        baseAngleX = -0.08;
                    } else if (index > 12) {
                        baseAngleX = -0.11;
                    }
                    
                    bone.rotation.x = baseAngleX + waveX;
                });

                // 咆哮
                if (isRoaring) {
                    roarTimer += delta;
                    if (headMesh && jawMesh) {
                        const progress = Math.min(roarTimer / 0.5, 1.0);
                        const decay = roarTimer > 1.8 ? Math.max(0, 1 - (roarTimer - 1.8) / 0.7) : 1.0;
                        
                        const angle = 0.6 * Math.sin(progress * Math.PI / 2) * decay;
                        headMesh.parent.rotation.x = -angle;
                        
                        jawMesh.position.y = -0.4 * Math.sin(progress * Math.PI / 2) * decay;
                        jawMesh.rotation.x = 0.3 * Math.sin(progress * Math.PI / 2) * decay;
                    }
                } else if (!isBeaming) {
                    if (headMesh && jawMesh) {
                        headMesh.parent.rotation.x *= 0.92;
                        jawMesh.position.y += (-0.05 - jawMesh.position.y) * 0.08;
                        jawMesh.rotation.x *= 0.92;
                    }
                }

                // 熱線ビーム
                if (isBeaming) {
                    beamTimer += delta;
                    beamsGroup.visible = true;

                    const progress = Math.min(beamTimer / 0.6, 1.0);
                    if (headMesh && jawMesh) {
                        headMesh.parent.rotation.x = -0.3 * progress;
                        jawMesh.position.y = -0.55 * progress;
                        jawMesh.rotation.x = 0.45 * progress;
                    }

                    const mBeam = beamsGroup.getObjectByName("mouthBeam");
                    const mFlare = beamsGroup.getObjectByName("mouthFlare");
                    const tBeam = beamsGroup.getObjectByName("tailBeam");
                    const bBeams = beamsGroup.getObjectByName("backBeams");

                    if (mBeam && mFlare && tBeam && bBeams) {
                        const beamPulse = 1.0 + Math.sin(time * 25.0) * 0.2;
                        mBeam.scale.set(beamPulse, 1, beamPulse);
                        tBeam.scale.set(beamPulse * 0.8, 1, beamPulse * 0.8);
                        
                        mBeam.rotation.z = Math.sin(time * 15.0) * 0.01;
                        mBeam.rotation.y = Math.cos(time * 18.0) * 0.01;

                        mFlare.scale.setScalar(beamPulse * (1.2 + Math.random() * 0.3));

                        const headWorldPos = new THREE.Vector3();
                        headMesh.getWorldPosition(headWorldPos);
                        mBeam.position.copy(headWorldPos);
                        mBeam.position.y -= 0.2;
                        mBeam.position.z += 0.4;
                        mFlare.position.copy(mBeam.position);

                        const tailTipWorldPos = new THREE.Vector3();
                        const lastBone = tailSegments[tailSegments.length - 1];
                        if (lastBone && lastBone.children[0]) {
                            lastBone.children[0].getWorldPosition(tailTipWorldPos);
                            tBeam.position.copy(tailTipWorldPos);
                        }
                        
                        bBeams.children.forEach((subBeam, idx) => {
                            const subPulse = 0.8 + Math.sin(time * 30.0 + idx) * 0.3;
                            subBeam.scale.set(subPulse, 1, subPulse);
                        });
                    }

                    if (beamParticles) {
                        const posAttr = beamParticles.geometry.attributes.position;
                        const vels = beamParticles.userData.velocities;
                        const mouthPos = new THREE.Vector3();
                        headMesh.getWorldPosition(mouthPos);

                        for(let i=0; i<posAttr.count; i++) {
                            let px = posAttr.getX(i) + vels[i].x;
                            let py = posAttr.getY(i) + vels[i].y;
                            let pz = posAttr.getZ(i) + vels[i].z;

                            const dist = Math.sqrt((px - mouthPos.x)**2 + (py - mouthPos.y)**2 + (pz - mouthPos.z)**2);
                            if (dist > 8.0) {
                                px = mouthPos.x + (Math.random()-0.5)*1.0;
                                py = mouthPos.y + (Math.random()-0.5)*1.0;
                                pz = mouthPos.z + (Math.random()-0.5)*1.0;
                                vels[i].set(
                                    (Math.random() - 0.5) * 0.2,
                                    (Math.random() - 0.5) * 0.2,
                                    (Math.random() + 0.2) * 0.4
                                );
                            }
                            posAttr.setXYZ(i, px, py, pz);
                        }
                        posAttr.needsUpdate = true;
                    }

                } else {
                    if (beamsGroup && beamsGroup.visible) {
                        beamsGroup.visible = false;
                    }
                }

                if (gillParticles) gillParticles.visible = false;
            }


            // 火の粉
            if (particleGeometry) {
                const posAttr = particleGeometry.attributes.position;
                const speed = isPurpleState ? 0.08 : 0.03;
                
                for (let i = 0; i < posAttr.count; i++) {
                    let y = posAttr.getY(i) + speed;
                    let x = posAttr.getX(i) + Math.sin(time * 0.5 + i) * 0.01;
                    
                    if (y > 25) {
                        y = 0;
                        x = (Math.random() - 0.5) * 60;
                    }
                    posAttr.setY(i, y);
                    posAttr.setX(i, x);
                }
                posAttr.needsUpdate = true;
            }

            // カメラのゆらぎ
            if (controls && controls.state === -1) {
                camera.position.y += Math.sin(time * 0.3) * 0.001;
            }

            controls.update();
            renderer.render(scene, camera);
        }

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
            resetCamera(); // リサイズ時も見切れ防止を再評価
        }
    </script>
</body>
</html>

シン

ゴジラ ゴジラ ゴジラがやってきた

🤖 使用したAI
Gemini
👁 21 回閲覧
📅 投稿:2026年6月7日 🔄 更新:2026年8月13日
応援スタンプを押してみよう!