ELDER Social VR.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" />
  <title>ELDER Social VR - 超完全版(クライアントのみ)</title>
  <script src="https://aframe.io/releases/1.4.2/aframe.min.js"></script>

  <style>
    :root{
      --bg: rgba(10,12,14,.82);
      --glass: rgba(255,255,255,.06);
      --stroke: rgba(255,255,255,.13);
      --accent: rgba(120,240,255,.95);
      --accent2: rgba(120,160,255,.95);
    }
    html,body{ margin:0; padding:0; height:100%; overflow:hidden; background:#000; font-family: "Yu Gothic", system-ui, -apple-system, Segoe UI, sans-serif; }
    a-scene{ position:fixed; inset:0; z-index:0; }
    canvas{ position:fixed !important; inset:0; z-index:0; }

    /* ===== HUD (DOM UI) ===== */
    #hud{
      position: fixed; inset: 0;
      display:flex; align-items:flex-start; justify-content:center;
      pointer-events:none;
      z-index: 999999;
    }
    #panel{
      margin-top: 18px;
      width: min(560px, calc(100vw - 26px));
      background: linear-gradient(180deg, rgba(255,255,255,.06), rgba(0,0,0,.28));
      border: 1px solid var(--stroke);
      border-radius: 18px;
      box-shadow: 0 18px 60px rgba(0,0,0,.45), 0 0 0 1px rgba(0,0,0,.2) inset;
      backdrop-filter: blur(10px);
      color:#fff;
      padding: 16px;
      pointer-events:auto;
      touch-action: manipulation;
      user-select:none;
      position: relative;
    }
    #panel, #panel *{ pointer-events:auto; }

    /* UI隠すボタン */
    #btnHideUI{
      position:absolute;
      top: 12px;
      right: 12px;
      width: auto;
      padding: 10px 12px;
      border-radius: 12px;
      font-weight: 900;
      font-size: 12px;
      letter-spacing: .2px;
      background: rgba(0,0,0,.35);
      border: 1px solid rgba(255,255,255,.16);
      cursor: pointer;
      display:flex;
      align-items:center;
      gap:8px;
      -webkit-tap-highlight-color: transparent;
    }
    #btnHideUI:hover{ border-color: rgba(120,240,255,.35); }
    #btnHideUI:active{ transform: scale(.99); }

    /* HUDを閉じた後に出す小ボタン */
    #floatingShowUI{
      position: fixed;
      right: 14px;
      bottom: 14px;
      z-index: 9999999;
      display: none;
      pointer-events:auto;
      background: rgba(10,12,14,.72);
      border: 1px solid rgba(255,255,255,.18);
      backdrop-filter: blur(10px);
      color:#fff;
      border-radius: 999px;
      padding: 12px 14px;
      font-weight: 900;
      cursor:pointer;
      box-shadow: 0 10px 40px rgba(0,0,0,.4);
      -webkit-tap-highlight-color: transparent;
    }
    #floatingShowUI:hover{ border-color: rgba(120,240,255,.35); }
    #floatingShowUI:active{ transform: scale(.99); }

    .top{
      display:flex; align-items:center; justify-content:space-between;
      gap: 10px;
      margin-bottom: 12px;
      padding-right: 86px; /* 隠すボタン分スペース */
    }
    .brand{
      font-weight: 900;
      letter-spacing: .5px;
      font-size: 22px;
    }
    .pill{
      display:inline-flex; align-items:center; gap:8px;
      background: rgba(255,255,255,.06);
      border: 1px solid rgba(255,255,255,.14);
      padding: 8px 12px;
      border-radius: 999px;
      font-size: 12px;
      opacity:.95;
    }

    .grid{
      display:grid;
      grid-template-columns: 1fr 1fr;
      gap: 10px;
    }
    .btn{
      display:flex; align-items:center; justify-content:center;
      gap: 10px;
      padding: 14px 12px;
      border-radius: 14px;
      background: linear-gradient(180deg, rgba(255,255,255,.08), rgba(0,0,0,.25));
      border: 1px solid rgba(255,255,255,.14);
      color:#fff;
      font-weight: 900;
      letter-spacing: .4px;
      cursor:pointer;
      transform: translateZ(0);
      transition: transform .08s, border-color .18s, background .18s;
      touch-action: manipulation;
      -webkit-tap-highlight-color: transparent;
    }
    .btn:hover{ border-color: rgba(120,240,255,.35); }
    .btn:active{ transform: scale(.99); }
    .btn.primary{ border-color: rgba(120,240,255,.28); box-shadow: 0 0 0 1px rgba(120,240,255,.10) inset; }
    .btn.danger{ border-color: rgba(255,120,160,.25); }

    .section{
      margin-top: 12px;
      background: rgba(0,0,0,.22);
      border: 1px solid rgba(255,255,255,.10);
      border-radius: 16px;
      padding: 12px;
    }
    .sectionTitle{
      font-weight: 900;
      opacity:.92;
      margin-bottom: 8px;
      display:flex; align-items:center; justify-content:space-between;
      gap: 10px;
    }
    .statGrid{
      display:grid;
      grid-template-columns: 1fr 1fr;
      gap: 10px;
    }
    .card{
      background: rgba(255,255,255,.04);
      border: 1px solid rgba(255,255,255,.10);
      border-radius: 14px;
      padding: 10px;
    }
    .label{ opacity:.82; font-size: 12px; }
    .big{ font-size: 20px; font-weight: 900; margin-top: 4px; }
    .bar{
      margin-top: 8px;
      height: 10px;
      background: rgba(255,255,255,.08);
      border-radius: 999px;
      overflow:hidden;
      border: 1px solid rgba(0,0,0,.22);
    }
    .bar > div{ height:100%; width:50%; background: linear-gradient(90deg, var(--accent), var(--accent2)); }

    .log{
      margin-top: 10px;
      max-height: 140px;
      overflow:auto;
      padding: 10px;
      border-radius: 14px;
      border: 1px solid rgba(255,255,255,.12);
      background: rgba(0,0,0,.28);
      font-size: 12px;
      line-height: 1.35;
    }

    /* モーダル */
    #modalBack{
      position: fixed; inset:0;
      display:none;
      align-items:center; justify-content:center;
      background: rgba(0,0,0,.55);
      z-index: 9999999;
      pointer-events:auto;
    }
    #modal{
      width: min(620px, calc(100vw - 26px));
      background: rgba(10,12,14,.92);
      border: 1px solid rgba(255,255,255,.14);
      border-radius: 18px;
      box-shadow: 0 18px 70px rgba(0,0,0,.55);
      color:#fff;
      padding: 14px;
    }
    #modal h3{
      margin: 0 0 10px 0;
      font-size: 18px;
      letter-spacing: .3px;
    }
    .modalBody{ opacity:.92; font-size: 14px; line-height: 1.5; white-space: pre-wrap; }
    .modalBtns{ display:flex; gap: 10px; margin-top: 12px; flex-wrap:wrap; }
    .modalBtns .btn{ flex: 1 1 140px; }

    /* VR中はDOM HUDを隠す */
    body.vr #hud{ display:none !important; }
    body.vr #floatingShowUI{ display:none !important; }
  </style>
</head>

<body>
  <!-- HUD -->
  <div id="hud">
    <div id="panel">
      <div id="btnHideUI" title="UIを隠す(ESCで戻せる)">✕ UIを隠す</div>

      <div class="top">
        <div class="brand">ELDER Social VR</div>
        <div class="pill">現在地: <b id="fieldTag">街</b></div>
      </div>

      <div class="grid" style="margin-bottom:10px;">
        <div class="btn primary" id="btnTown">🏘️ 街</div>
        <div class="btn primary" id="btnCastle">🏰 城</div>
        <div class="btn primary" id="btnCave">🕳️ 洞窟</div>
        <div class="btn primary" id="btnRuins">🏛️ 遺跡</div>
      </div>

      <div class="grid" style="margin-bottom:10px;">
        <div class="btn" id="btnEnterVR">🕶️ Enter VR</div>
        <div class="btn danger" id="btnExit">⏏ Exit</div>
        <div class="btn" id="btnWave">👋 Wave</div>
        <div class="btn" id="btnCheer">🎉 Cheer</div>
      </div>

      <div class="section">
        <div class="sectionTitle">
          <span>レベル / EXP</span>
          <span class="pill">名前: <b id="playerNameLabel">YOU</b></span>
        </div>
        <div class="statGrid">
          <div class="card">
            <div class="label">Lv / EXP</div>
            <div class="big">Lv.<span id="level">1</span> <span style="opacity:.8;font-size:12px;">EXP</span> <span id="expText">0</span>/<span id="expNeedText">100</span></div>
            <div class="bar"><div id="expBar" style="width:0%"></div></div>
          </div>
          <div class="card">
            <div class="label">ゴールド</div>
            <div class="big"><span id="goldText">0</span> G</div>
            <div class="label" style="margin-top:6px;">街の商人で買い物できる</div>
          </div>
        </div>

        <div class="statGrid" style="margin-top:10px;">
          <div class="card">
            <div class="label">HP</div>
            <div class="big" id="hpText">100</div>
            <div class="bar"><div id="hpBar" style="width:100%"></div></div>
          </div>
          <div class="card">
            <div class="label">魔力</div>
            <div class="big" id="manaText">100</div>
            <div class="bar"><div id="manaBar" style="width:100%"></div></div>
          </div>
        </div>
      </div>

      <div class="grid" style="margin-top:10px;">
        <div class="btn" id="btnTalk">💬 話す</div>
        <div class="btn" id="btnQuest">📜 クエスト</div>
        <div class="btn" id="btnShop">🛒 ショップ</div>
        <div class="btn" id="btnRest">🛏 休憩</div>
        <div class="btn" id="btnSave">💾 セーブ</div>
        <div class="btn" id="btnLoad">📂 ロード</div>
      </div>

      <div class="log" id="log"></div>
      <div style="opacity:.7;font-size:11px;margin-top:8px;line-height:1.35;">
        操作: WASD移動 / Shiftダッシュ / Spaceジャンプ / マウス視点(クリックでポインタロック)<br/>
        VR: Enter VR → コントローラのレーザーで3D UIを押せる(DOM UIは非表示)<br/>
        UI: 右上「UIを隠す」or ESCで切替
      </div>
    </div>
  </div>

  <!-- HUDを隠した後に出すボタン -->
  <div id="floatingShowUI">≡ UIを表示</div>

  <!-- Modal -->
  <div id="modalBack">
    <div id="modal">
      <h3 id="modalTitle">TITLE</h3>
      <div class="modalBody" id="modalBody"></div>
      <div class="modalBtns" id="modalBtns"></div>
    </div>
  </div>

  <!-- A-Frame -->
  <a-scene
    id="scene"
    renderer="colorManagement:true; physicallyCorrectLights:true"
    shadow="type:pcfsoft"
    webxr="optionalFeatures: local-floor, bounded-floor, hand-tracking"
  >
    <a-assets>
      <!-- BGM(エリアで切替) -->
      <audio id="bgmTown"   src="https://www.free-stock-music.com/music/scott-buckley/mp3/scott-buckley-beautiful-oblivion.mp3" crossorigin="anonymous"></audio>
      <audio id="bgmCastle" src="https://www.free-stock-music.com/music/scott-buckley/mp3/scott-buckley-the-endurance.mp3" crossorigin="anonymous"></audio>
      <audio id="bgmCave"   src="https://www.free-stock-music.com/music/scott-buckley/mp3/scott-buckley-in-search-of-solitude.mp3" crossorigin="anonymous"></audio>
      <audio id="bgmRuins"  src="https://www.free-stock-music.com/music/wombat-noises-audio/mp3/wombat-noises-audio-the-ruins-of-atlantis.mp3" crossorigin="anonymous"></audio>
    </a-assets>

    <!-- 空 -->
    <a-sky id="sky" color="#061018"></a-sky>

    <!-- 光 -->
    <a-light type="ambient" intensity="0.9" color="#dff8ff"></a-light>
    <a-light id="sun" type="directional" intensity="1.35" position="30 40 10"
             castShadow="true" shadow-mapWidth="2048" shadow-mapHeight="2048"></a-light>

    <!-- 海 -->
    <a-entity id="ocean" position="0 0 0">
      <a-cylinder position="0 -1.4 0" radius="140" height="2.2" open-ended="true"
                  material="color:#0a2a3a; metalness:0.05; roughness:0.35; opacity:0.95; transparent:true"></a-cylinder>
      <a-ring position="0 -0.2 0" radius-inner="65" radius-outer="140"
              rotation="-90 0 0"
              material="color:#0b3143; opacity:0.88; transparent:true"></a-ring>
    </a-entity>

    <!-- 島(3段) -->
    <a-entity id="island">
      <a-cylinder position="0 -0.1 0" radius="62" height="1"
                  material="color:#cbb48b; roughness:0.95; metalness:0.0"></a-cylinder>
      <a-cylinder position="0 0.05 0" radius="50" height="1"
                  material="color:#2f6a3f; roughness:0.95; metalness:0.0"></a-cylinder>
      <a-cylinder position="0 0.35 0" radius="34" height="1.2"
                  material="color:#2a5f3a; roughness:0.95"></a-cylinder>
      <a-ring position="0 0.42 0" radius-inner="14" radius-outer="17" rotation="-90 0 0"
              material="color:#3b2f23; roughness:1"></a-ring>
      <a-ring position="0 0.42 0" radius-inner="26" radius-outer="29" rotation="-90 0 0"
              material="color:#3b2f23; roughness:1; opacity:0.85; transparent:true"></a-ring>
    </a-entity>

    <!-- BGM -->
    <a-entity id="bgm" sound="src:#bgmTown; autoplay:false; loop:true; volume:0.65; positional:false"></a-entity>

    <!-- プレイヤー(※yは基準0.55で置く。起動時にJSで“地面にスナップ”して埋まりゼロ) -->
    <a-entity id="playerRig" position="0 0.55 18">
      <!-- かっこいい勇者(簡易ハイディテール) -->
      <a-entity id="hero" position="0 0 0"
                animation__idle="property: rotation; dir: alternate; dur: 1800; loop: true; to: 0 1.2 0"
                animation__breath="property: scale; dir: alternate; dur: 1200; loop: true; to: 1.01 1.02 1.01">

        <!-- 影 -->
        <a-circle radius="0.75" rotation="-90 0 0" position="0 0.02 0"
                  material="color:#000; opacity:0.25; transparent:true"></a-circle>

        <!-- マント -->
        <a-entity id="cape" position="0 1.22 0.16" rotation="10 0 0"
                  animation__cape="property: rotation; dir: alternate; dur: 900; loop: true; to: 12 1 0">
          <a-plane width="0.95" height="1.35" position="0 -0.58 -0.20"
                   material="color:#0b1020; opacity:0.96; transparent:true; side:double"></a-plane>
          <a-plane width="0.55" height="1.15" position="-0.28 -0.62 -0.21" rotation="0 8 0"
                   material="color:#0a0f1c; opacity:0.90; transparent:true; side:double"></a-plane>
          <a-plane width="0.55" height="1.15" position="0.28 -0.62 -0.21" rotation="0 -8 0"
                   material="color:#0a0f1c; opacity:0.90; transparent:true; side:double"></a-plane>
        </a-entity>

        <!-- ブーツ -->
        <a-box position="-0.17 0.16 0" width="0.22" height="0.34" depth="0.30"
               material="color:#1a1418; roughness:1"></a-box>
        <a-box position="0.17 0.16 0" width="0.22" height="0.34" depth="0.30"
               material="color:#1a1418; roughness:1"></a-box>
        <a-box position="-0.17 0.06 -0.14" width="0.24" height="0.12" depth="0.22"
               material="color:#0f0c10; roughness:1"></a-box>
        <a-box position="0.17 0.06 -0.14" width="0.24" height="0.12" depth="0.22"
               material="color:#0f0c10; roughness:1"></a-box>

        <!-- 脚(鎧) -->
        <a-box position="-0.17 0.56 0" width="0.26" height="0.54" depth="0.32"
               material="color:#2b3140; metalness:0.25; roughness:0.55"></a-box>
        <a-box position="0.17 0.56 0" width="0.26" height="0.54" depth="0.32"
               material="color:#2b3140; metalness:0.25; roughness:0.55"></a-box>

        <!-- 膝ルーン -->
        <a-box position="-0.17 0.44 -0.18" width="0.26" height="0.14" depth="0.14"
               material="color:#78f0ff; emissive:#2dd; emissiveIntensity:0.20; metalness:0.3; roughness:0.35; opacity:0.55; transparent:true"></a-box>
        <a-box position="0.17 0.44 -0.18" width="0.26" height="0.14" depth="0.14"
               material="color:#78f0ff; emissive:#2dd; emissiveIntensity:0.20; metalness:0.3; roughness:0.35; opacity:0.55; transparent:true"></a-box>

        <!-- 腰 -->
        <a-box position="0 0.92 0.00" width="0.72" height="0.14" depth="0.40"
               material="color:#15151a; roughness:1"></a-box>
        <a-box position="0 0.92 0.23" width="0.16" height="0.12" depth="0.06"
               material="color:#ad7b2e; roughness:0.75; metalness:0.25"></a-box>

        <!-- 腰布 -->
        <a-plane width="0.42" height="0.58" position="0 0.64 0.20"
                 material="color:#152a52; opacity:0.92; transparent:true; side:double"></a-plane>
        <a-plane width="0.35" height="0.36" position="0 0.54 -0.22" rotation="0 180 0"
                 material="color:#0f1733; opacity:0.85; transparent:true; side:double"></a-plane>

        <!-- 胸鎧 -->
        <a-box position="0 1.22 0" width="0.74" height="0.82" depth="0.42"
               material="color:#3b6b8e; metalness:0.38; roughness:0.22"></a-box>

        <!-- コア -->
        <a-box position="0 1.22 0.24" width="0.60" height="0.66" depth="0.06"
               material="color:#78f0ff; emissive:#2dd; emissiveIntensity:0.30; metalness:0.25; roughness:0.18; opacity:0.55; transparent:true"></a-box>
        <a-ring position="0 1.22 0.27" radius-inner="0.12" radius-outer="0.20"
                material="color:#78f0ff; emissive:#2dd; emissiveIntensity:0.35; opacity:0.45; transparent:true"></a-ring>

        <!-- 肩当て -->
        <a-sphere position="-0.50 1.54 0.02" radius="0.22"
                  material="color:#2f4f6a; metalness:0.35; roughness:0.22"></a-sphere>
        <a-sphere position="0.50 1.54 0.02" radius="0.22"
                  material="color:#2f4f6a; metalness:0.35; roughness:0.22"></a-sphere>
        <a-cone position="-0.58 1.58 0.02" radius-bottom="0.10" height="0.22" rotation="0 0 25"
                material="color:#cbd3da; metalness:0.55; roughness:0.25"></a-cone>
        <a-cone position="0.58 1.58 0.02" radius-bottom="0.10" height="0.22" rotation="0 0 -25"
                material="color:#cbd3da; metalness:0.55; roughness:0.25"></a-cone>

        <!-- 腕 -->
        <a-box position="-0.62 1.18 0.02" width="0.20" height="0.68" depth="0.24"
               material="color:#2b3140; metalness:0.25; roughness:0.55"></a-box>
        <a-box position="0.62 1.18 0.02" width="0.20" height="0.68" depth="0.24"
               material="color:#2b3140; metalness:0.25; roughness:0.55"></a-box>
        <a-sphere position="-0.62 0.84 0.02" radius="0.10" material="color:#1a1418; roughness:1"></a-sphere>
        <a-sphere position="0.62 0.84 0.02" radius="0.10" material="color:#1a1418; roughness:1"></a-sphere>

        <!-- 襟 -->
        <a-torus radius="0.24" tube="0.06" position="0 1.58 0.02" rotation="90 0 0"
                 material="color:#0b1020; roughness:1; opacity:0.96; transparent:true"></a-torus>

        <!-- 頭 -->
        <a-entity id="headGroup" position="0 1.78 0">
          <a-sphere id="heroHead" position="0 0 0" radius="0.22" material="color:#f4d7bd; roughness:0.95"></a-sphere>
          <a-sphere position="0 0.05 -0.02" radius="0.24" material="color:#1b1b1f; roughness:0.9; metalness:0.15"></a-sphere>
          <a-box position="0 -0.02 -0.18" width="0.32" height="0.18" depth="0.10"
                 material="color:#0f0f12; roughness:1"></a-box>
          <a-box position="0 0.00 -0.24" width="0.36" height="0.10" depth="0.06"
                 material="color:#78f0ff; emissive:#2dd; emissiveIntensity:0.40; opacity:0.55; transparent:true"></a-box>
          <a-cone position="-0.16 0.20 -0.02" radius-bottom="0.06" height="0.18" rotation="0 0 25"
                  material="color:#cbd3da; metalness:0.55; roughness:0.25"></a-cone>
          <a-cone position="0.16 0.20 -0.02" radius-bottom="0.06" height="0.18" rotation="0 0 -25"
                  material="color:#cbd3da; metalness:0.55; roughness:0.25"></a-cone>
        </a-entity>

        <!-- 剣 -->
        <a-entity id="sword" position="0.40 1.02 0.10" rotation="0 0 18">
          <a-box width="0.05" height="0.86" depth="0.06" position="0 0.42 0"
                 material="color:#cbd3da; metalness:0.78; roughness:0.18"></a-box>
          <a-box width="0.02" height="0.84" depth="0.02" position="0 0.42 -0.03"
                 material="color:#ffffff; opacity:0.18; transparent:true"></a-box>
          <a-box width="0.20" height="0.05" depth="0.12" position="0 0.04 0"
                 material="color:#ad7b2e; metalness:0.35; roughness:0.55"></a-box>
          <a-torus radius="0.09" tube="0.012" position="0 0.04 0.07" rotation="90 0 0"
                   material="color:#78f0ff; emissive:#2dd; emissiveIntensity:0.30; opacity:0.45; transparent:true"></a-torus>
          <a-box width="0.07" height="0.18" depth="0.07" position="0 -0.08 0"
                 material="color:#2b1c12; roughness:1"></a-box>
          <a-sphere radius="0.03" position="0 -0.18 0"
                    material="color:#78f0ff; emissive:#2dd; emissiveIntensity:0.35; opacity:0.6; transparent:true"></a-sphere>
        </a-entity>

        <!-- 盾 -->
        <a-entity id="shield" position="-0.70 1.06 -0.06" rotation="0 0 12">
          <a-cylinder radius="0.24" height="0.09" rotation="90 0 0"
                      material="color:#3a5f7a; metalness:0.28; roughness:0.32"></a-cylinder>
          <a-ring radius-inner="0.13" radius-outer="0.24" rotation="90 0 0"
                  material="color:#78f0ff; emissive:#2dd; emissiveIntensity:0.25; opacity:0.45; transparent:true"></a-ring>
          <a-circle radius="0.07" rotation="90 0 0" position="0 0 0.05"
                    material="color:#ad7b2e; metalness:0.35; roughness:0.55"></a-circle>
        </a-entity>

        <!-- 名前 -->
        <a-text id="name3d" value="YOU" position="0 2.35 0" align="center" width="4" color="#ffffff"
                shader="msdf" font="https://cdn.aframe.io/fonts/Roboto-msdf.json"></a-text>
      </a-entity>

      <a-camera id="cam"
        position="0 1.75 3.4"
        look-controls="pointerLockEnabled: false"
        wasd-controls-enabled="false"
        fov="72"
      ></a-camera>

      <a-entity id="rightHand" laser-controls="hand:right" raycaster="objects: .vrbtn" line="opacity:0.75"></a-entity>
      <a-entity id="leftHand"  laser-controls="hand:left"  raycaster="objects: .vrbtn" line="opacity:0.75"></a-entity>
      <a-entity id="mouseCursor" cursor="rayOrigin: mouse" raycaster="objects: .vrbtn"></a-entity>

      <!-- VR UI -->
      <a-entity id="vrUI" position="0 1.55 -1.25" visible="false">
        <a-plane width="1.55" height="0.92" material="color:#0b0f14; opacity:0.78; transparent:true"></a-plane>
        <a-text value="VRUI" position="0 0.40 0.01" align="center" width="2.6" color="#7ff"
                shader="msdf" font="https://cdn.aframe.io/fonts/Roboto-msdf.json"></a-text>

        <a-entity position="0 0.12 0.02">
          <a-plane class="vrbtn" vr-btn="action:fieldTown"  position="-0.48 0.12 0" width="0.48" height="0.16" material="color:#13202b; opacity:0.95"></a-plane>
          <a-text value="街" position="-0.48 0.12 0.02" align="center" width="1.6" color="#fff" shader="msdf" font="https://cdn.aframe.io/fonts/Roboto-msdf.json"></a-text>

          <a-plane class="vrbtn" vr-btn="action:fieldCastle" position="0.48 0.12 0" width="0.48" height="0.16" material="color:#13202b; opacity:0.95"></a-plane>
          <a-text value="城" position="0.48 0.12 0.02" align="center" width="1.6" color="#fff" shader="msdf" font="https://cdn.aframe.io/fonts/Roboto-msdf.json"></a-text>

          <a-plane class="vrbtn" vr-btn="action:fieldCave" position="-0.48 -0.08 0" width="0.48" height="0.16" material="color:#13202b; opacity:0.95"></a-plane>
          <a-text value="洞窟" position="-0.48 -0.08 0.02" align="center" width="1.6" color="#fff" shader="msdf" font="https://cdn.aframe.io/fonts/Roboto-msdf.json"></a-text>

          <a-plane class="vrbtn" vr-btn="action:fieldRuins" position="0.48 -0.08 0" width="0.48" height="0.16" material="color:#13202b; opacity:0.95"></a-plane>
          <a-text value="遺跡" position="0.48 -0.08 0.02" align="center" width="1.6" color="#fff" shader="msdf" font="https://cdn.aframe.io/fonts/Roboto-msdf.json"></a-text>

          <a-plane class="vrbtn" vr-btn="action:talk" position="-0.48 -0.30 0" width="0.48" height="0.16" material="color:#10261e; opacity:0.95"></a-plane>
          <a-text value="話す" position="-0.48 -0.30 0.02" align="center" width="1.6" color="#fff" shader="msdf" font="https://cdn.aframe.io/fonts/Roboto-msdf.json"></a-text>

          <a-plane class="vrbtn" vr-btn="action:quest" position="0.48 -0.30 0" width="0.48" height="0.16" material="color:#2a2110; opacity:0.95"></a-plane>
          <a-text value="クエスト" position="0.48 -0.30 0.02" align="center" width="1.6" color="#fff" shader="msdf" font="https://cdn.aframe.io/fonts/Roboto-msdf.json"></a-text>
        </a-entity>

        <a-text value="スティック移動 / トリガーで押す" position="0 -0.43 0.01" align="center" width="2.8" color="#bfefff"
                shader="msdf" font="https://cdn.aframe.io/fonts/Roboto-msdf.json"></a-text>
      </a-entity>
    </a-entity>

    <!-- NPC(★snap で“その場所の地面”に自動補正 → 埋まりゼロ) -->
    <a-entity id="npcGroup">
      <a-entity id="npcGuide" class="npc snap" position="-6 1.15 10" rotation="0 25 0">
        <a-cylinder radius="0.35" height="1.2" material="color:#203a4a; roughness:0.9"></a-cylinder>
        <a-sphere radius="0.22" position="0 0.86 0" material="color:#f2d7bf; roughness:0.95"></a-sphere>
        <a-cone radius-bottom="0.28" height="0.35" position="0 1.12 0" material="color:#0a0f18; roughness:1"></a-cone>
        <a-text value="Guide" position="0 1.45 0" align="center" width="4" color="#fff" shader="msdf" font="https://cdn.aframe.io/fonts/Roboto-msdf.json"></a-text>
      </a-entity>

      <a-entity id="npcKnight" class="npc snap" position="10 1.15 0" rotation="0 -120 0" visible="false">
        <a-cylinder radius="0.36" height="1.2" material="color:#3a4652; metalness:0.25; roughness:0.35"></a-cylinder>
        <a-sphere radius="0.22" position="0 0.86 0" material="color:#f2d7bf; roughness:0.95"></a-sphere>
        <a-box width="0.48" height="0.16" depth="0.06" position="0 0.58 0.2" material="color:#78f0ff; opacity:0.5; transparent:true"></a-box>
        <a-text value="Castle Knight" position="0 1.45 0" align="center" width="4" color="#fff" shader="msdf" font="https://cdn.aframe.io/fonts/Roboto-msdf.json"></a-text>
      </a-entity>

      <a-entity id="npcMiner" class="npc snap" position="-10 1.15 -8" rotation="0 60 0" visible="false">
        <a-cylinder radius="0.35" height="1.2" material="color:#3b2f23; roughness:0.95"></a-cylinder>
        <a-sphere radius="0.22" position="0 0.86 0" material="color:#f2d7bf; roughness:0.95"></a-sphere>
        <a-sphere radius="0.18" position="0 1.06 0" material="color:#2d2d2f; roughness:1"></a-sphere>
        <a-text value="Miner" position="0 1.45 0" align="center" width="4" color="#fff" shader="msdf" font="https://cdn.aframe.io/fonts/Roboto-msdf.json"></a-text>
      </a-entity>

      <a-entity id="npcSage" class="npc snap" position="6 1.15 -12" rotation="0 -30 0" visible="false">
        <a-cylinder radius="0.35" height="1.2" material="color:#2a1f2f; roughness:0.95"></a-cylinder>
        <a-sphere radius="0.22" position="0 0.86 0" material="color:#f2d7bf; roughness:0.95"></a-sphere>
        <a-torus radius="0.34" tube="0.05" position="0 0.95 0" rotation="90 0 0" material="color:#7ff; opacity:0.35; transparent:true"></a-torus>
        <a-text value="Ruins Sage" position="0 1.45 0" align="center" width="4" color="#fff" shader="msdf" font="https://cdn.aframe.io/fonts/Roboto-msdf.json"></a-text>
      </a-entity>
    </a-entity>

    <!-- Enemy -->
    <a-entity id="enemy" position="0 1.10 -2" visible="true">
      <a-entity id="enemyModel">
        <a-sphere radius="0.55" material="color:#8a1b2d; metalness:0.15; roughness:0.45; emissive:#200;"></a-sphere>
        <a-sphere radius="0.22" position="0 0.52 0.06" material="color:#2a0b10; roughness:0.9"></a-sphere>
        <a-cone radius-bottom="0.16" height="0.35" position="-0.22 0.78 0.02" rotation="20 0 40" material="color:#ddd; roughness:0.7"></a-cone>
        <a-cone radius-bottom="0.16" height="0.35" position="0.22 0.78 0.02" rotation="20 0 -40" material="color:#ddd; roughness:0.7"></a-cone>
        <a-sphere radius="0.06" position="-0.14 0.55 -0.46" material="color:#fff; emissive:#f0f; emissiveIntensity:0.9"></a-sphere>
        <a-sphere radius="0.06" position="0.14 0.55 -0.46" material="color:#fff; emissive:#f0f; emissiveIntensity:0.9"></a-sphere>
        <a-ring radius-inner="0.62" radius-outer="0.74" rotation="-90 0 0" position="0 -0.35 0"
                material="color:#7ff; opacity:0.14; transparent:true"></a-ring>
      </a-entity>
      <a-text id="enemyName3D" value="Enemy" position="0 1.25 0" align="center" width="4" color="#fff"
              shader="msdf" font="https://cdn.aframe.io/fonts/Roboto-msdf.json"></a-text>
    </a-entity>

    <!-- Fields -->
    <a-entity id="field-town" visible="true">
      <!-- 噴水(snapで地面補正) -->
      <a-entity id="fountain" class="snap" position="0 0 0">
        <a-cylinder radius="2.1" height="0.35" position="0 0.725 0"
                    material="color:#55606a; roughness:0.55; metalness:0.1"></a-cylinder>
        <a-cylinder radius="1.35" height="0.42" position="0 1.11 0"
                    material="color:#3f4a54; roughness:0.55; metalness:0.12"></a-cylinder>
        <a-cylinder radius="0.25" height="1.0" position="0 1.82 0"
                    material="color:#6b7782; roughness:0.5"></a-cylinder>
        <a-sphere radius="0.26" position="0 2.42 0"
                  material="color:#7ff; opacity:0.5; transparent:true; emissive:#2dd; emissiveIntensity:0.25"></a-sphere>
        <a-torus radius="0.95" tube="0.06" position="0 0.90 0" rotation="90 0 0"
                 material="color:#7ff; opacity:0.25; transparent:true"></a-torus>
      </a-entity>

      <!-- 家(各家をsnapで地面補正) -->
      <a-entity id="houses">
        <a-entity class="snap" position="-10 1.70 6" rotation="0 35 0">
          <a-box width="4" height="2.3" depth="3.2" material="color:#bda982; roughness:0.9"></a-box>
          <a-cone radius-bottom="2.8" height="1.4" position="0 1.85 0" material="color:#5a2c1b; roughness:1"></a-cone>
          <a-plane width="1.2" height="0.7" position="0 0.6 1.61" material="color:#1b2a35; opacity:0.55; transparent:true"></a-plane>
        </a-entity>

        <a-entity class="snap" position="10 1.60 8" rotation="0 -20 0">
          <a-box width="3.2" height="2.1" depth="3.0" material="color:#c2b08a; roughness:0.9"></a-box>
          <a-cone radius-bottom="2.2" height="1.3" position="0 1.7 0" material="color:#6a3a22; roughness:1"></a-cone>
          <a-plane width="1.0" height="0.65" position="0.2 0.5 1.51" material="color:#1b2a35; opacity:0.55; transparent:true"></a-plane>
        </a-entity>

        <a-entity class="snap" position="-14 1.55 -6" rotation="0 70 0">
          <a-box width="3.6" height="2.0" depth="2.6" material="color:#b8a27a; roughness:0.9"></a-box>
          <a-cone radius-bottom="2.4" height="1.2" position="0 1.6 0" material="color:#4f2a1a; roughness:1"></a-cone>
        </a-entity>

        <a-entity class="snap" position="13 1.65 -6" rotation="0 -55 0">
          <a-box width="4.2" height="2.2" depth="3.2" material="color:#b5a07a; roughness:0.95"></a-box>
          <a-cone radius-bottom="2.9" height="1.3" position="0 1.75 0" material="color:#2b1c12; roughness:1"></a-cone>
          <a-plane width="2.2" height="0.7" position="0 0.6 1.61" material="color:#0b0f14; opacity:0.65; transparent:true"></a-plane>
          <a-text value="SHOP" position="0 1.1 1.65" align="center" width="4" color="#7ff"
                  shader="msdf" font="https://cdn.aframe.io/fonts/Roboto-msdf.json"></a-text>
        </a-entity>
      </a-entity>

      <!-- 屋台(snapで地面補正) -->
      <a-entity class="snap" position="-6 0.55 -4" rotation="0 15 0">
        <a-box width="2.2" height="0.5" depth="1.2" position="0 0.7 0" material="color:#6a3a22; roughness:1"></a-box>
        <a-plane width="2.4" height="1.2" position="0 1.35 0" rotation="-30 0 0" material="color:#7ff; opacity:0.2; transparent:true"></a-plane>
        <a-cylinder radius="0.05" height="1.4" position="-1.05 0.7 -0.55" material="color:#3b2f23"></a-cylinder>
        <a-cylinder radius="0.05" height="1.4" position="1.05 0.7 -0.55" material="color:#3b2f23"></a-cylinder>
      </a-entity>

      <!-- 木&街灯(snapで地面補正) -->
      <a-entity>
        <a-entity class="snap" position="-18 1.85 2">
          <a-cylinder radius="0.18" height="2.6" material="color:#3b2f23; roughness:1"></a-cylinder>
          <a-sphere radius="1.2" position="0 2.0 0" material="color:#2a7a45; roughness:1"></a-sphere>
        </a-entity>

        <a-entity class="snap" position="18 1.85 4">
          <a-cylinder radius="0.18" height="2.6" material="color:#3b2f23; roughness:1"></a-cylinder>
          <a-sphere radius="1.2" position="0 2.0 0" material="color:#2a7a45; roughness:1"></a-sphere>
        </a-entity>

        <a-entity class="snap" position="6 1.60 14">
          <a-cylinder radius="0.06" height="2.1" material="color:#45515a; roughness:0.6"></a-cylinder>
          <a-sphere radius="0.18" position="0 1.08 0" material="color:#fff; emissive:#7ff; emissiveIntensity:0.65; opacity:0.85; transparent:true"></a-sphere>
        </a-entity>
      </a-entity>
    </a-entity>

    <a-entity id="field-castle" visible="false">
      <a-ring position="0 0.43 0" radius-inner="0" radius-outer="30" rotation="-90 0 0"
              material="color:#636b75; roughness:0.9; opacity:0.9; transparent:true"></a-ring>

      <a-entity position="0 0.75 -18">
        <a-box width="26" height="6" depth="2.8" material="color:#a8b1bb; roughness:0.65; metalness:0.05"></a-box>
        <a-box width="7" height="4" depth="2.2" position="0 -0.4 1.1" material="color:#8a939e; roughness:0.65"></a-box>
        <a-box width="5.2" height="4.2" depth="0.8" position="0 -0.9 1.8" material="color:#2b1c12; roughness:1"></a-box>
        <a-plane width="1.2" height="2.6" position="-4 0.6 1.9" material="color:#7ff; opacity:0.25; transparent:true"></a-plane>
        <a-plane width="1.2" height="2.6" position="4 0.6 1.9" material="color:#7ff; opacity:0.25; transparent:true"></a-plane>
      </a-entity>

      <a-entity>
        <a-entity position="-12 1.2 -18">
          <a-cylinder radius="2.1" height="8.2" material="color:#9aa3ad; roughness:0.55; metalness:0.05"></a-cylinder>
          <a-cone radius-bottom="2.3" height="2.4" position="0 5.2 0" material="color:#6a3a22; roughness:1"></a-cone>
        </a-entity>
        <a-entity position="12 1.2 -18">
          <a-cylinder radius="2.1" height="8.2" material="color:#9aa3ad; roughness:0.55; metalness:0.05"></a-cylinder>
          <a-cone radius-bottom="2.3" height="2.4" position="0 5.2 0" material="color:#6a3a22; roughness:1"></a-cone>
        </a-entity>
      </a-entity>

      <a-ring position="0 0.18 -12" radius-inner="18" radius-outer="28" rotation="-90 0 0"
              material="color:#0b3143; opacity:0.55; transparent:true"></a-ring>

      <a-entity position="0 0.55 -6">
        <a-cylinder radius="1.4" height="0.6" material="color:#4a535c; roughness:0.65"></a-cylinder>
        <a-sphere radius="0.75" position="0 1.0 0" material="color:#a8b1bb; roughness:0.5"></a-sphere>
        <a-torus-knot radius="0.35" tube="0.08" position="0 1.8 0" p="2" q="5"
                      material="color:#7ff; emissive:#2dd; emissiveIntensity:0.25; opacity:0.5; transparent:true"></a-torus-knot>
      </a-entity>
    </a-entity>

    <a-entity id="field-cave" visible="false">
      <a-entity position="0 0.55 -6">
        <a-sphere radius="10" material="color:#0b0f14; opacity:0.22; transparent:true" segments-width="18" segments-height="12"></a-sphere>
      </a-entity>

      <a-entity position="0 0.55 -16">
        <a-torus radius="8" tube="2.2" arc="200" rotation="0 0 90"
                 material="color:#4a3f34; roughness:1; metalness:0"></a-torus>
      </a-entity>

      <a-entity id="rocks">
        <a-sphere radius="5" position="-12 2 -14" material="color:#3a332d; roughness:1"></a-sphere>
        <a-sphere radius="6" position="12 1 -16" material="color:#352f2a; roughness:1"></a-sphere>
        <a-sphere radius="4.5" position="0 3 -22" material="color:#2f2a25; roughness:1"></a-sphere>
      </a-entity>

      <a-entity>
        <a-cone radius-bottom="0.8" height="2.8" position="-4 6 -14" material="color:#2f2a25; roughness:1"></a-cone>
        <a-cone radius-bottom="0.6" height="2.2" position="3 5.7 -16" material="color:#2f2a25; roughness:1"></a-cone>
        <a-cone radius-bottom="0.7" height="2.5" position="8 6.2 -12" material="color:#2f2a25; roughness:1"></a-cone>
      </a-entity>

      <a-entity position="-6 0.55 -10">
        <a-octahedron radius="0.9" material="color:#7ff; opacity:0.55; transparent:true; emissive:#2dd; emissiveIntensity:0.35"></a-octahedron>
        <a-octahedron radius="0.6" position="1.0 0.2 0.3" material="color:#8cf; opacity:0.55; transparent:true; emissive:#2dd; emissiveIntensity:0.25"></a-octahedron>
        <a-light type="point" intensity="0.8" distance="10" color="#7ff"></a-light>
      </a-entity>
    </a-entity>

    <a-entity id="field-ruins" visible="false">
      <a-ring position="0 0.43 0" radius-inner="0" radius-outer="30" rotation="-90 0 0"
              material="color:#6a6555; roughness:0.95; opacity:0.92; transparent:true"></a-ring>

      <a-entity>
        <a-entity position="-12 0.55 -8">
          <a-cylinder radius="0.8" height="3.2" material="color:#c9c2a3; roughness:0.9"></a-cylinder>
          <a-box width="2.2" height="0.35" depth="2.2" position="0 1.85 0" material="color:#bdb493; roughness:0.95"></a-box>
        </a-entity>
        <a-entity position="12 0.55 -8">
          <a-cylinder radius="0.8" height="2.1" material="color:#c9c2a3; roughness:0.9"></a-cylinder>
          <a-box width="2.2" height="0.35" depth="2.2" position="0 1.25 0" material="color:#bdb493; roughness:0.95"></a-box>
        </a-entity>
        <a-entity position="-8 0.55 -18" rotation="0 20 0">
          <a-cylinder radius="0.7" height="2.4" material="color:#bdb493; roughness:0.95"></a-cylinder>
          <a-box width="1.9" height="0.28" depth="1.9" position="0 1.38 0" material="color:#c9c2a3; roughness:0.95"></a-box>
        </a-entity>
        <a-entity position="8 0.55 -18" rotation="0 -20 0">
          <a-cylinder radius="0.7" height="3.0" material="color:#bdb493; roughness:0.95"></a-cylinder>
          <a-box width="1.9" height="0.28" depth="1.9" position="0 1.68 0" material="color:#c9c2a3; roughness:0.95"></a-box>
        </a-entity>
      </a-entity>

      <a-entity position="0 2.0 -16">
        <a-torus radius="4.0" tube="0.55" arc="180" rotation="0 0 90" material="color:#c9c2a3; roughness:0.9"></a-torus>
      </a-entity>

      <a-entity position="0 0.55 -8">
        <a-box width="4.2" height="0.8" depth="2.6" material="color:#5a5648; roughness:0.95"></a-box>
        <a-ring radius-inner="0.9" radius-outer="1.5" rotation="-90 0 0" position="0 0.41 0"
                material="color:#7ff; opacity:0.35; transparent:true; emissive:#2dd; emissiveIntensity:0.25"></a-ring>
        <a-light type="point" intensity="0.9" distance="14" color="#7ff" position="0 1.3 0"></a-light>
      </a-entity>

      <a-entity id="floating" position="0 2.2 -10">
        <a-box width="0.6" height="0.35" depth="0.6" position="-1.2 0.3 0" material="color:#c9c2a3; roughness:0.9"></a-box>
        <a-box width="0.4" height="0.25" depth="0.4" position="1.0 -0.1 0.5" material="color:#bdb493; roughness:0.9"></a-box>
        <a-box width="0.5" height="0.3" depth="0.5" position="0.2 0.5 -0.7" material="color:#c9c2a3; roughness:0.9"></a-box>
      </a-entity>
    </a-entity>

    <a-entity id="confetti" visible="false" position="0 2.4 10">
      <a-ring radius-inner="0.2" radius-outer="0.6" rotation="-90 0 0" material="color:#7ff; opacity:0.35; transparent:true"></a-ring>
      <a-ring radius-inner="0.6" radius-outer="1.0" rotation="-90 0 0" material="color:#fff; opacity:0.18; transparent:true"></a-ring>
    </a-entity>
  </a-scene>

<script>
/* 3D VR UI ボタン */
AFRAME.registerComponent('vr-btn', {
  schema: { action: { type:'string' } },
  init: function(){
    this.el.addEventListener('click', () => {
      const fn = window[this.data.action];
      if(typeof fn === 'function') fn();
    });
  }
});

/* ===== 状態 ===== */
const state = {
  field: "town",
  hp: 100,
  mana: 100,
  level: 1,
  exp: 0,
  expNeed: 100,
  gold: 0,
  quest: null,
  storyStep: 0,
  inVR: false,
  audioUnlocked: false,
  enemy: { name:"影の獣", hp:80, maxHp:80, atk:10, exp:35, gold:15 }
};
const FIELD_JP = { town:"街", castle:"城", cave:"洞窟", ruins:"遺跡" };
const ENEMIES = {
  town:   [{ name:"路地のスライム", hp:60, atk:9,  exp:35,  gold:16 }, { name:"野良ゴブリン", hp:80, atk:12, exp:45, gold:20 }],
  castle: [{ name:"亡霊騎士",     hp:110, atk:16, exp:70,  gold:35 }, { name:"城壁の影",     hp:130, atk:18, exp:85, gold:42 }],
  cave:   [{ name:"洞窟コウモリ", hp:90,  atk:15, exp:65,  gold:30 }, { name:"岩喰い蜥蜴",   hp:140, atk:20, exp:95, gold:55 }],
  ruins:  [{ name:"封印の番人",   hp:170, atk:24, exp:120, gold:70 }, { name:"古代の眼",     hp:150, atk:22, exp:110,gold:62 }]
};

/* ★あなたが“配置に使ってきた基準地面” */
const BASE_GROUND_Y = 0.55;

/* ★島は3段。距離で「今いる地面の高さ」を返す(埋まり防止の本体) */
const GROUND_LAYERS = [
  { r: 34, y: 0.95 }, // 上段(radius=34 height=1.2 posY=0.35 → top=0.95)
  { r: 50, y: 0.55 }, // 中段(radius=50 height=1.0 posY=0.05 → top=0.55)
  { r: 62, y: 0.40 }  // 下段(radius=62 height=1.0 posY=-0.1 → top=0.40)
];

function groundYAt(x, z){
  const r = Math.hypot(x, z);
  for(const layer of GROUND_LAYERS){
    if(r <= layer.r) return layer.y;
  }
  return GROUND_LAYERS[GROUND_LAYERS.length - 1].y;
}
function enemyYAt(x, z){
  return groundYAt(x, z) + 0.55; // 敵球半径0.55
}

/* ★「基準0.55で置いた物」を、実地面に合わせて持ち上げる */
function snapElToGround(el){
  if(!el) return;
  const p = el.getAttribute("position");
  if(!p || typeof p.x!=="number" || typeof p.z!=="number" || typeof p.y!=="number") return;
  const gy = groundYAt(p.x, p.z);
  const dy = gy - BASE_GROUND_Y;
  if(Math.abs(dy) < 0.0001) return;
  el.setAttribute("position", { x:p.x, y:p.y + dy, z:p.z });
}
function snapAll(){
  document.querySelectorAll(".snap").forEach(snapElToGround);
}

/* ===== ユーティリティ ===== */
function escapeHtml(s){ return String(s).replace(/[&<>"']/g, m => ({ "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#039;" }[m])); }
function log(msg){
  const el = document.getElementById("log");
  const t = new Date().toLocaleTimeString();
  el.innerHTML = `<div>【${t}】${escapeHtml(msg)}</div>` + el.innerHTML;
}
function clamp(v,a,b){ return Math.max(a, Math.min(b, v)); }

/* ===== UI表示/非表示 ===== */
let hudVisible = true;
function setHUDVisible(visible){
  hudVisible = !!visible;
  document.getElementById("hud").style.display = hudVisible ? "flex" : "none";
  document.getElementById("floatingShowUI").style.display = (!hudVisible && !state.inVR) ? "block" : "none";
  localStorage.setItem("elder_ui_hidden", hudVisible ? "0" : "1");
}
function toggleHUD(){ setHUDVisible(!hudVisible); }

/* ===== UI反映 ===== */
function updateUI(){
  document.getElementById("fieldTag").textContent = FIELD_JP[state.field] || state.field;
  document.getElementById("hpText").textContent = Math.floor(state.hp);
  document.getElementById("manaText").textContent = Math.floor(state.mana);
  document.getElementById("level").textContent = state.level;
  document.getElementById("expText").textContent = state.exp;
  document.getElementById("expNeedText").textContent = state.expNeed;
  document.getElementById("goldText").textContent = state.gold;
  document.getElementById("hpBar").style.width = clamp(state.hp,0,100) + "%";
  document.getElementById("manaBar").style.width = clamp(state.mana,0,100) + "%";
  document.getElementById("expBar").style.width = Math.min(100, (state.exp/state.expNeed)*100) + "%";
}

/* ===== DOMボタンを確実に(多重発火を抑える) ===== */
function bindPress(el, fn){
  let last = 0;
  const handler = (e) => {
    const now = performance.now();
    if(now - last < 180) return; // 連打/多重防止
    last = now;
    try{ e.preventDefault(); }catch(_){}
    unlockAudio();
    fn();
  };
  el.addEventListener("pointerup", handler, { passive:false });
  el.addEventListener("touchend", handler, { passive:false });
  el.addEventListener("click", handler, { passive:false });
}

/* ===== オーディオ ===== */
function unlockAudio(){
  if(state.audioUnlocked) return;
  state.audioUnlocked = true;
  const bgm = document.getElementById("bgm");
  try{
    bgm.components.sound.playSound();
    log("🔊 BGM開始(ユーザー操作で解除)");
  }catch(e){}
}
function setBGMByField(){
  const bgm = document.getElementById("bgm");
  const srcMap = { town:"#bgmTown", castle:"#bgmCastle", cave:"#bgmCave", ruins:"#bgmRuins" };
  const src = srcMap[state.field] || "#bgmTown";
  bgm.setAttribute("sound", `src:${src}; autoplay:false; loop:true; volume:0.65; positional:false`);
  try{
    bgm.components.sound.stopSound();
    if(state.audioUnlocked) bgm.components.sound.playSound();
  }catch(e){}
}

/* ===== フィールド切替 ===== */
function setField(field){
  state.field = field;
  ["town","castle","cave","ruins"].forEach(name=>{
    document.getElementById("field-"+name).setAttribute("visible", name===field);
  });
  document.getElementById("npcGuide").setAttribute("visible", field==="town");
  document.getElementById("npcKnight").setAttribute("visible", field==="castle");
  document.getElementById("npcMiner").setAttribute("visible", field==="cave");
  document.getElementById("npcSage").setAttribute("visible", field==="ruins");

  const sky = document.getElementById("sky");
  const sun = document.getElementById("sun");
  if(field==="town"){ sky.setAttribute("color","#061018"); sun.setAttribute("intensity","1.35"); }
  if(field==="castle"){ sky.setAttribute("color","#071321"); sun.setAttribute("intensity","1.45"); }
  if(field==="cave"){ sky.setAttribute("color","#04070b"); sun.setAttribute("intensity","0.85"); }
  if(field==="ruins"){ sky.setAttribute("color","#050b10"); sun.setAttribute("intensity","1.05"); }

  setBGMByField();
  spawnEnemy();
  updateUI();
  log(`📍 ${FIELD_JP[field]} に移動した`);
}

/* ===== 敵 ===== */
function spawnEnemy(){
  const list = ENEMIES[state.field] || ENEMIES.town;
  const e = list[Math.floor(Math.random()*list.length)];
  state.enemy = { name:e.name, hp:e.hp, maxHp:e.hp, atk:e.atk, exp:e.exp, gold:e.gold };
  document.getElementById("enemyName3D").setAttribute("value", e.name);
  const enemy = document.getElementById("enemy");
  enemy.setAttribute("visible","true");
  enemy.setAttribute("position", { x:0, y:enemyYAt(0, -2), z:-2 });
  enemy.setAttribute("animation__pop","property: scale; from: 0.7 0.7 0.7; to: 1 1 1; dur: 220; easing: easeOutBack");
  log(`⚠️ ${e.name} が現れた`);
}
function enemyCounter(){
  if(Math.random() < 0.18){ log(`💨 ${state.enemy.name} の攻撃は外れた`); return; }
  const raw = state.enemy.atk + Math.floor(Math.random()*6) - Math.floor(state.level/4);
  const dmg = Math.max(2, raw);
  state.hp -= dmg;
  log(`🩸 反撃:${state.enemy.name} から ${dmg} ダメージ`);
  if(state.hp <= 0){ state.hp = 1; log("🧊 倒れかけた…(HP1で踏みとどまった)"); }
  updateUI();
}
function gainRewards(exp, gold){
  state.exp += exp;
  state.gold += gold;
  log(`✅ 報酬:EXP +${exp} / ${gold}G`);
  while(state.exp >= state.expNeed){
    state.exp -= state.expNeed;
    state.level++;
    state.expNeed = Math.floor(state.expNeed*1.25 + 25);
    state.hp = clamp(state.hp + 18, 0, 100);
    state.mana = clamp(state.mana + 12, 0, 100);
    log(`🎉 レベルアップ! Lv.${state.level}`);
  }
  updateUI();
}
function enemyDie(){
  const enemy = document.getElementById("enemy");
  enemy.setAttribute("animation__die","property: scale; to: 0.01 0.01 0.01; dur: 250; easing: easeInQuad");
  setTimeout(()=> enemy.setAttribute("visible","false"), 260);
  gainRewards(state.enemy.exp, state.enemy.gold);
  setTimeout(()=> spawnEnemy(), 1200);
}
function damageEnemy(dmg, by="攻撃"){
  state.enemy.hp -= dmg;
  log(`⚔️ ${by}:${state.enemy.name} に ${dmg} ダメージ(残り ${Math.max(0,state.enemy.hp)})`);
  document.getElementById("enemyModel").setAttribute("animation__hit","property: rotation; dir: alternate; dur: 70; loop: 4; to: 0 0 12");
  if(state.enemy.hp <= 0){ enemyDie(); return; }
  enemyCounter();
}

/* ===== 行動 ===== */
function wave(){ document.getElementById("hero").setAttribute("animation__wave","property: rotation; dir: alternate; dur: 180; loop: 6; to: 0 0 8"); log("👋 Wave!"); }
function cheer(){
  const conf = document.getElementById("confetti");
  conf.setAttribute("visible","true");
  conf.setAttribute("animation__up","property: position; from: 0 2.4 10; to: 0 4.2 10; dur: 520; easing: easeOutQuad");
  conf.setAttribute("animation__fade","property: material.opacity; from: 0.35; to: 0; dur: 520; easing: easeOutQuad");
  setTimeout(()=>{ conf.setAttribute("visible","false"); conf.setAttribute("material","opacity:0.35; transparent:true"); }, 560);
  log("🎉 Cheer!");
}
function rest(){
  const bhp=state.hp, bmn=state.mana;
  state.hp = clamp(state.hp + 40, 0, 100);
  state.mana = clamp(state.mana + 40, 0, 100);
  updateUI();
  log(`🛏 休憩:HP ${bhp}→${state.hp} / 魔力 ${bmn}→${state.mana}`);
}
function attack(){ const dmg = (14 + Math.floor(state.level/2)) + Math.floor(Math.random()*8); damageEnemy(dmg, "通常攻撃"); updateUI(); }
function castSpell(){
  if(state.mana < 18){ log("💤 魔力が足りない"); return; }
  state.mana -= 18;
  const dmg = 24 + Math.floor(state.level*1.2) + Math.floor(Math.random()*10);
  damageEnemy(dmg, "魔法");
  updateUI();
}

/* ===== モーダル ===== */
function openModal(title, body, buttons){
  document.getElementById("modalTitle").textContent = title;
  document.getElementById("modalBody").textContent = body;
  const area = document.getElementById("modalBtns");
  area.innerHTML = "";
  buttons.forEach(b=>{
    const div = document.createElement("div");
    div.className = "btn " + (b.type || "");
    div.textContent = b.label;
    bindPress(div, ()=>{ try{ b.onClick(); }catch(e){} });
    area.appendChild(div);
  });
  document.getElementById("modalBack").style.display = "flex";
}
function closeModal(){ document.getElementById("modalBack").style.display = "none"; unlockAudio(); }
document.getElementById("modalBack").addEventListener("click", (e)=>{ if(e.target && e.target.id === "modalBack") closeModal(); });

/* ===== 会話/クエスト/ショップ ===== */
function talk(){
  const npcName = (state.field==="town") ? "Guide"
               : (state.field==="castle") ? "Castle Knight"
               : (state.field==="cave") ? "Miner"
               : "Ruins Sage";
  openModal(`💬 ${npcName}`, `${npcName}:\nここは ${FIELD_JP[state.field]}。\n準備ができたら戦うか、別の場所へ行け。`, [
    { label:"通常攻撃", type:"primary", onClick:()=>{ closeModal(); attack(); } },
    { label:"魔法", type:"primary", onClick:()=>{ closeModal(); castSpell(); } },
    { label:"閉じる", onClick:()=> closeModal() }
  ]);
}
function quest(){
  openModal("📜 クエスト", "今は簡易クエスト(討伐でEXPとGを稼げ)。\n次段階で固定シナリオを増やせる。", [
    { label:"閉じる", onClick:()=> closeModal() }
  ]);
}
function shop(){
  openModal("🛒 ショップ", "(クライアントのみ簡易)\n街でゴールドを稼いで強化できる拡張に対応。", [
    { label:"閉じる", onClick:()=> closeModal() }
  ]);
}

/* ===== セーブ/ロード ===== */
function saveGame(){
  const data = { ...state, audioUnlocked: state.audioUnlocked };
  localStorage.setItem("elder_social_vr_save", JSON.stringify(data));
  log("💾 セーブ完了");
}
function loadGame(){
  const raw = localStorage.getItem("elder_social_vr_save");
  if(!raw){ log("📂 セーブデータがない"); return; }
  try{
    const data = JSON.parse(raw);
    Object.assign(state, data || {});
    setField(state.field || "town");
    updateUI();
    log("📂 ロード完了");
  }catch(e){
    log("❌ ロード失敗:データ破損");
  }
}

/* ===== VR Enter/Exit ===== */
function enterVR(){
  const scene = document.getElementById("scene");
  state.inVR = true;
  document.body.classList.add("vr");
  document.getElementById("vrUI").setAttribute("visible","true");
  document.getElementById("cam").setAttribute("position","0 1.72 0.05");
  document.getElementById("heroHead").setAttribute("material","opacity:0.0; transparent:true; color:#f4d7bd");
  try{ scene.enterVR(); log("🕶️ VRに入った"); }catch(e){ log("⚠️ WebXRに入れない(疑似VRで続行)"); }
}
function exitApp(){
  const scene = document.getElementById("scene");
  state.inVR = false;
  document.body.classList.remove("vr");
  document.getElementById("vrUI").setAttribute("visible","false");
  document.getElementById("cam").setAttribute("position","0 1.75 3.4");
  document.getElementById("heroHead").setAttribute("material","opacity:1.0; transparent:false; color:#f4d7bd");
  try{ scene.exitVR(); }catch(e){}
  setHUDVisible(hudVisible);
  log("⏏ Exit");
}
function fieldTown(){ setField("town"); }
function fieldCastle(){ setField("castle"); }
function fieldCave(){ setField("cave"); }
function fieldRuins(){ setField("ruins"); }

/* ===== 操作 ===== */
const rig = document.getElementById("playerRig");
const hero = document.getElementById("hero");
const cam  = document.getElementById("cam");
const keys = { w:false,a:false,s:false,d:false, shift:false, space:false };
let vy = 0, grounded = true;

function getYaw(){
  const rot = cam.getAttribute("rotation");
  return (rot && typeof rot.y === "number") ? rot.y : 0;
}
function tickMovement(dt){
  const speedBase = keys.shift ? 7.2 : 4.4;
  const step = (speedBase * dt) / 1000;

  let moveX = 0, moveZ = 0;
  if(keys.w) moveZ -= 1;
  if(keys.s) moveZ += 1;
  if(keys.a) moveX -= 1;
  if(keys.d) moveX += 1;

  const len = Math.hypot(moveX, moveZ);
  if(len > 0){ moveX/=len; moveZ/=len; }

  const yaw = (getYaw() * Math.PI) / 180;
  const cos = Math.cos(yaw), sin = Math.sin(yaw);
  const dx = (moveX * cos - moveZ * sin) * step;
  const dz = (moveX * sin + moveZ * cos) * step;

  const pos = rig.getAttribute("position");
  let nx = pos.x + dx, nz = pos.z + dz;

  const r = Math.hypot(nx, nz);
  const limit = 44;
  if(r > limit){ const k = limit / r; nx *= k; nz *= k; }

  if(keys.space && grounded){ vy = 5.2; grounded = false; }
  if(!grounded){ vy -= 12.0 * (dt/1000); }

  let ny = pos.y + vy * (dt/1000);

  /* ★地面は固定じゃない。今いる場所の地面へクランプ(埋まりゼロ) */
  const gy = groundYAt(nx, nz);
  if(ny <= gy){ ny = gy; vy = 0; grounded = true; }

  rig.setAttribute("position", { x:nx, y:ny, z:nz });

  if(len > 0){ hero.setAttribute("rotation", { x:0, y:getYaw(), z:0 }); }

  // 敵の追従(★yも地面追従)
  const enemy = document.getElementById("enemy");
  const epos = enemy.getAttribute("position");
  const dist = Math.hypot((epos.x - nx), (epos.z - nz));
  if(dist > 18){
    const ez = nz - 2.5;
    enemy.setAttribute("position", { x:nx, y:enemyYAt(nx, ez), z:ez });
  }

  const floating = document.getElementById("floating");
  if(floating){
    const t = performance.now() / 1000;
    floating.setAttribute("rotation", { x:0, y:(t*18)%360, z:0 });
    floating.setAttribute("position", { x:0, y:2.2 + Math.sin(t*1.4)*0.12, z:-10 });
  }
}
function hookThumbstick(){
  const RH = document.getElementById("rightHand");
  const LH = document.getElementById("leftHand");
  const onMove = (e)=>{
    if(!e || !e.detail) return;
    const { x, y } = e.detail;
    keys.w = y < -0.2; keys.s = y > 0.2; keys.a = x < -0.2; keys.d = x > 0.2;
  };
  RH.addEventListener("thumbstickmoved", onMove);
  LH.addEventListener("thumbstickmoved", onMove);
}
window.addEventListener("keydown", (e)=>{
  if(e.repeat) return;
  if(e.code==="KeyW") keys.w = true;
  if(e.code==="KeyA") keys.a = true;
  if(e.code==="KeyS") keys.s = true;
  if(e.code==="KeyD") keys.d = true;
  if(e.code==="ShiftLeft" || e.code==="ShiftRight") keys.shift = true;
  if(e.code==="Space") keys.space = true;
  if(e.code==="KeyJ"){ unlockAudio(); attack(); }
  if(e.code==="KeyK"){ unlockAudio(); castSpell(); }
  if(e.code==="Escape"){ if(!state.inVR) toggleHUD(); }
});
window.addEventListener("keyup", (e)=>{
  if(e.code==="KeyW") keys.w = false;
  if(e.code==="KeyA") keys.a = false;
  if(e.code==="KeyS") keys.s = false;
  if(e.code==="KeyD") keys.d = false;
  if(e.code==="ShiftLeft" || e.code==="ShiftRight") keys.shift = false;
  if(e.code==="Space") keys.space = false;
});

/* ===== DOMボタン配線 ===== */
function wireButtons(){
  bindPress(document.getElementById("btnTown"),   ()=> setField("town"));
  bindPress(document.getElementById("btnCastle"), ()=> setField("castle"));
  bindPress(document.getElementById("btnCave"),   ()=> setField("cave"));
  bindPress(document.getElementById("btnRuins"),  ()=> setField("ruins"));

  bindPress(document.getElementById("btnEnterVR"), ()=> enterVR());
  bindPress(document.getElementById("btnExit"),    ()=> exitApp());
  bindPress(document.getElementById("btnWave"),    ()=> wave());
  bindPress(document.getElementById("btnCheer"),   ()=> cheer());

  bindPress(document.getElementById("btnTalk"),  ()=> talk());
  bindPress(document.getElementById("btnQuest"), ()=> quest());
  bindPress(document.getElementById("btnShop"),  ()=> shop());
  bindPress(document.getElementById("btnRest"),  ()=> rest());

  bindPress(document.getElementById("btnSave"), ()=> saveGame());
  bindPress(document.getElementById("btnLoad"), ()=> loadGame());

  bindPress(document.getElementById("btnHideUI"), ()=> setHUDVisible(false));
  bindPress(document.getElementById("floatingShowUI"), ()=> setHUDVisible(true));

  bindPress(document.getElementById("panel"), ()=>{
    try{ cam.components["look-controls"].pointerLockEnabled = true; }catch(e){}
  });
}

/* ===== 初期化 ===== */
(function init(){
  wireButtons();
  hookThumbstick();

  const hidden = localStorage.getItem("elder_ui_hidden") === "1";
  setHUDVisible(!hidden);

  /* ★まず“基準0.55で置いた物”を全てスナップ(埋まり解消) */
  snapAll();

  /* ★プレイヤーは「その場の地面」に強制一致(埋まりゼロ) */
  const p0 = rig.getAttribute("position");
  if(p0 && typeof p0.x==="number" && typeof p0.z==="number"){
    rig.setAttribute("position", { x:p0.x, y:groundYAt(p0.x, p0.z), z:p0.z });
  }

  updateUI();
  setBGMByField();
  spawnEnemy();
  log("起動。島の段差に合わせて player/NPC/建物を自動補正(埋まりゼロ)。UIは右上で閉じられる。ESCでも切替。");

  let last = performance.now();
  function loop(now){
    const dt = now - last;
    last = now;
    tickMovement(dt);
    requestAnimationFrame(loop);
  }
  requestAnimationFrame(loop);

  const scene = document.getElementById("scene");
  scene.addEventListener("enter-vr", ()=>{
    state.inVR = true;
    document.body.classList.add("vr");
    document.getElementById("vrUI").setAttribute("visible","true");
    document.getElementById("cam").setAttribute("position","0 1.72 0.05");
    document.getElementById("heroHead").setAttribute("material","opacity:0.0; transparent:true; color:#f4d7bd");
    log("🕶️ WebXR: enter-vr");
  });
  scene.addEventListener("exit-vr", ()=>{
    state.inVR = false;
    document.body.classList.remove("vr");
    document.getElementById("vrUI").setAttribute("visible","false");
    document.getElementById("cam").setAttribute("position","0 1.75 3.4");
    document.getElementById("heroHead").setAttribute("material","opacity:1.0; transparent:false; color:#f4d7bd");
    setHUDVisible(hudVisible);
    log("⏏ WebXR: exit-vr");
  });
})();
</script>
</body>
</html>

投稿者: chosuke

趣味はゲームやアニメや漫画などです

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です