創世記
出エジプト記
レビ記
民数記
申命記
ヨシュア記
士師記
ルツ記
サムエル記上・下
列王記上・下
歴代誌上・下
エズラ記
ネヘミヤ記
エステル記
➡ 15. ヨブ記
詩篇
箴言
伝道の書(コヘレトの言葉)
雅歌(ソロモンの歌)
創世記
出エジプト記
レビ記
民数記
申命記
ヨシュア記
士師記
ルツ記
サムエル記上・下
列王記上・下
歴代誌上・下
エズラ記
ネヘミヤ記
エステル記
➡ 15. ヨブ記
詩篇
箴言
伝道の書(コヘレトの言葉)
雅歌(ソロモンの歌)
数字はあくまでオーダー感。複数柱の合算で年商・年EBITDAを数十兆円級に乗せ、時価総額→1000兆円級を狙います。
これらを同時に薄くやらない。2〜3本で突破口→残りを買収/合弁で増やすのが王道。
0〜12ヶ月:キャッシュを作る“加速器”
1〜3年:単位経済の怪物化
3〜7年:インフラに跨る
7〜15年:ネットワーク支配
15年以降:1000兆円レンジ
もしよければ、あなたの今の強み(Web/ゲーム/AI)に合わせて**“EC運営自動化エージェント(日本語特化)”の初期仕様と料金表**、営業スクリプト、LP文面までこの場で作ります。すぐ行けます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>エルダークロニクル:VR超完全版 – ロードなし(拾う/装備修正)</title>
<script src="https://aframe.io/releases/1.4.2/aframe.min.js"></script>
<style>
body, html { margin:0; padding:0; overflow:hidden; font-family:'Yu Gothic',system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:#000;}
#ui {
position:absolute; top:20px; left:20px; z-index:10; width:400px; color:#fff;
background:linear-gradient(180deg,rgba(0,0,0,.92),rgba(0,10,30,.85));
border-radius:16px; padding:14px 16px; box-shadow:0 0 25px #0ff,0 0 18px #33f4; font-size:15px;
}
h2 { margin:0 0 8px 0; font-size:22px; letter-spacing:1px;}
.row { display:flex; gap:8px; align-items:center; flex-wrap:wrap;}
.row > * { flex: 1 1 auto; }
input, select { width:100%; padding:6px 8px; border-radius:8px; border:1px solid #024; background:#00131f; color:#cfe9ff; }
button {
background:linear-gradient(90deg,#555,#222 80%);
color:#fff; padding:10px; margin:5px 0; border:none; cursor:pointer; width:100%;
border-radius:8px; font-weight:bold; letter-spacing:1px; box-shadow:0 1px 8px #0cf5; transition:background .25s;
}
button:hover { background:linear-gradient(90deg,#888 40%,#3cf); }
.section-title { font-weight:bold; font-size:17px; margin-top:10px; border-bottom:2px solid #5ef; letter-spacing:1.2px; }
.bar { height:14px; background:#13202a; border-radius:7px; overflow:hidden; margin:6px 0; box-shadow:0 1px 6px #0ff6 inset; }
.bar-inner { height:100%; background:linear-gradient(90deg,#0f0,#3af); transition:width .25s; }
.mana-bar .bar-inner { background:linear-gradient(90deg,#33d,#6ff); }
.enemyhp-bar .bar-inner { background:linear-gradient(90deg,#f55,#fdd); }
#scenario { margin:10px 0 6px 0; background:rgba(0,10,32,0.7); padding:8px 12px; border-radius:8px; min-height:44px; }
.kbd { display:inline-block; padding:1px 6px; border-radius:6px; background:#0a2636; border:1px solid #124; font-family:monospace; }
#hint {
position:absolute; bottom:18px; left:50%; transform:translateX(-50%);
color:#eaffff; background:rgba(0,20,35,.72); border:1px solid #0af; padding:8px 12px; border-radius:10px;
box-shadow:0 0 18px #08f5; font-size:14px; z-index:10;
}
#pickupPrompt {
position:absolute; bottom:70px; left:50%; transform:translateX(-50%);
color:#fff; background:rgba(0,0,0,.7); border:1px solid #38f; padding:8px 12px; border-radius:10px;
display:none; z-index:10;
}
.tag { display:inline-block; padding:2px 6px; border-radius:6px; background:#012233; border:1px solid #1a4e6c; margin-left:6px; font-size:12px; color:#bfe6ff;}
.on { background:#0b3754; border-color:#3db3ff; color:#fff; }
</style>
</head>
<body>
<div id="ui">
<h2>エルダークロニクルVR</h2>
<div class="row">
<span>プレイヤー名:</span>
<input id="playerName" placeholder="名前を入力">
</div>
<div class="row"><span>レベル:</span><span id="level" class="tag on">1</span>
<span>装備:</span><span id="equipment" class="tag">なし</span>
</div>
<div>HP: <span id="hpText">100</span></div>
<div class="bar"><div id="hpBar" class="bar-inner" style="width:100%"></div></div>
<div>魔力: <span id="manaText">100</span></div>
<div class="bar mana-bar"><div id="manaBar" class="bar-inner" style="width:100%"></div></div>
<div>敵HP: <span id="enemyHpText">120</span></div>
<div class="bar enemyhp-bar"><div id="enemyHpBar" class="bar-inner" style="width:100%"></div></div>
<div class="section-title">シナリオ</div>
<div id="scenario"></div>
<div class="section-title">行動</div>
<button onclick="levelUp()">🎉 レベルアップ</button>
<button onclick="castSpell()">🪄 魔法発動</button>
<button onclick="choosePath('wizard')">🧙♂️ 魔導士に話す</button>
<button onclick="choosePath('knight')">🛡️ 騎士に話す</button>
<button onclick="receiveQuest()">📜 クエスト受注</button>
<button onclick="toggleEnvironment()">🌄 昼夜切替</button>
<div class="section-title">移動</div>
<div class="row">
<button onclick="changeField('town')">🏘️ 街</button>
<button onclick="changeField('castle')">🏰 城</button>
</div>
<div class="row">
<button onclick="changeField('cave')">🕳️ 洞窟</button>
<button onclick="changeField('ruins')">🏛️ 遺跡</button>
</div>
<div class="row">
<button onclick="changeField('dungeon')">🧩 ダンジョン</button>
</div>
<div class="section-title">操作</div>
<div style="line-height:1.6">
<span class="kbd">WASD</span> 移動
<span class="kbd">マウス</span> 視点
<span class="kbd">E</span> 拾う
<span class="kbd">1</span> 剣装備
<span class="kbd">2</span> 銃装備
<span class="kbd">クリック</span> 攻撃
</div>
</div>
<div id="pickupPrompt">Eで拾う</div>
<div id="hint">近くの武器に近づいて <span class="kbd">E</span> で拾い、<span class="kbd">1/2</span> で装備、クリックで攻撃!</div>
<a-scene loading-screen="enabled:false" renderer="colorManagement:true" shadow="true">
<a-sky id="sky" color="#0e163e"></a-sky>
<a-entity id="starParticles" position="0 25 -40" visible="false">
<a-entity geometry="primitive:sphere; radius:0.18" material="color:#fff; opacity:0.6" position="-10 3 0"></a-entity>
<a-entity geometry="primitive:sphere; radius:0.12" material="color:#fff; opacity:0.8" position="7 2 -2"></a-entity>
<a-entity geometry="primitive:sphere; radius:0.11" material="color:#eaf6ff; opacity:0.9" position="3 5 2"></a-entity>
<a-entity geometry="primitive:sphere; radius:0.10" material="color:#ffe; opacity:0.7" position="13 3 5"></a-entity>
</a-entity>
<a-entity id="cloudParticles" position="0 30 -35" visible="true">
<a-sphere radius="5" position="8 2 -8" color="#f6fbff" opacity="0.18"></a-sphere>
<a-sphere radius="6" position="-7 3 5" color="#eefbff" opacity="0.14"></a-sphere>
</a-entity>
<a-light type="ambient" color="#fff" intensity="1"></a-light>
<a-light id="sunlight" type="directional" intensity="1.6" position="20 25 -8" castShadow="true" shadow-mapWidth="1024" shadow-mapHeight="1024"></a-light>
<a-light type="point" color="#cff" intensity="2.2" distance="50" position="2 9 -3"></a-light>
<a-light type="spot" color="#55aaff" position="0 15 -10" intensity="1.2" angle="30" penumbra="0.7"></a-light>
<a-plane id="ground" position="0 0 0" rotation="-90 0 0" width="120" height="120" color="#375047" shadow="receive:true"></a-plane>
<a-entity id="field-town" visible="true">
<a-circle position="0 0 -6" radius="4" color="#7d7d7d" material="roughness:.9; metalness:.05; opacity:.84; transparent:true"></a-circle>
<a-entity position="-2 0 -7">
<a-box width="3.6" height="2.6" depth="2.4" color="#c0a47b" material="roughness:0.3; metalness:0.05" position="0 1.3 0"></a-box>
<a-cone position="0 3 -0.1" radius-bottom="2.1" height="1.4" color="#7b5322"></a-cone>
<a-text value="街の家" position="0 3.7 0" color="#fff" width="5" align="center"></a-text>
</a-entity>
<a-entity position="2 0 -9">
<a-box width="2.2" height="2.2" depth="2.4" color="#a86f23" position="0 1.1 0"></a-box>
<a-cone position="0 2.6 0" radius-bottom="1.4" height="1.0" color="#5c3a12"></a-cone>
</a-entity>
<a-entity position="5 0 -5">
<a-cylinder radius="0.25" height="4" color="#3a2a1a" position="0 2 0"></a-cylinder>
<a-sphere radius="1.2" color="#174d1f" position="0 3.2 0"></a-sphere>
</a-entity>
</a-entity>
<a-entity id="field-castle" visible="false">
<a-box position="0 2.8 -12" depth="6" height="6" width="10" color="#ccd2df" material="roughness:0.25; metalness:0.12"></a-box>
<a-cylinder position="-4 1.2 -13" radius="1.1" height="3.8" color="#858ca3"></a-cylinder>
<a-cylinder position="4 1.2 -13" radius="1.1" height="3.8" color="#858ca3"></a-cylinder>
<a-cone position="0 6.4 -12" radius-bottom="3.2" height="2" color="#dba"></a-cone>
<a-entity geometry="primitive:torus; radius:2.5; tube:0.07" material="color:#66aaff; opacity:.25; transparent:true" position="0 4.5 -12"></a-entity>
<a-text value="王の城" position="0 6.3 -12" color="#0bf" width="6" align="center"></a-text>
</a-entity>
<a-entity id="field-cave" visible="false">
<a-torus position="2 1.1 -10" radius="1.8" tube="0.8" arc="230" color="#363636" rotation="40 0 90"></a-torus>
<a-sphere position="2 0.4 -10" radius="1.1" color="#151515"></a-sphere>
<a-entity position="-2 0 -8">
<a-cone radius-bottom="0.8" height="2.2" color="#2a2a2a"></a-cone>
<a-cone radius-bottom="0.5" height="1.4" color="#393939" position="0.7 0 0.4"></a-cone>
</a-entity>
<a-text value="洞窟の入口" position="2 2.9 -10" color="#fff" width="6" align="center"></a-text>
</a-entity>
<a-entity id="field-ruins" visible="false">
<a-cylinder position="-2 1.1 -9" radius="0.8" height="2.6" color="#babbb2"></a-cylinder>
<a-box position="-3.1 1.8 -9" width="3.5" height="0.32" depth="0.7" color="#e0dfc7"></a-box>
<a-torus position="-2 2.3 -9" radius="0.6" tube="0.09" arc="340" color="#fffeee"></a-torus>
<a-entity geometry="primitive:torusKnot; p:2; q:7; radius:0.7; tube:0.07" position="2 1.8 -8" material="color:#bcd; opacity:.7; transparent:true"></a-entity>
<a-text value="古代の遺構" position="0 3.3 -8.5" color="#fff" width="6" align="center"></a-text>
</a-entity>
<a-entity id="field-dungeon" visible="false">
<a-box position="2 1.2 -8.8" depth="3.3" height="2.3" width="3.6" color="#161651"></a-box>
<a-torus-knot position="-1.5 2.7 -8.2" radius="0.9" tube="0.12" p="3" q="7" color="#64eaff"></a-torus-knot>
<a-cylinder position="-3 0.7 -9.6" radius="0.8" height="1.2" color="#333"></a-cylinder>
<a-entity geometry="primitive:torus; radius:1.4; tube:0.05" material="color:#aaeeff; opacity:.18; transparent:true" position="0 2.1 -8.6" rotation="90 0 0"></a-entity>
<a-text value="ダンジョン入口" position="0 3.1 -8.4" color="#fff" width="6" align="center"></a-text>
</a-entity>
<!-- 武器ピックアップ -->
<a-entity id="swordPickup" class="pickup" data-weapon="sword" position="-1 0 -5">
<a-box width="0.16" height="1.3" depth="0.08" color="#cfe7ff" material="metalness:0.8; roughness:0.15" position="0 0.75 0"></a-box>
<a-box width="0.5" height="0.08" depth="0.08" color="#333" position="0 0.1 0"></a-box>
<a-cylinder radius="0.06" height="0.42" color="#7c5a2b" position="0 -0.1 0"></a-cylinder>
<a-entity geometry="primitive:ring; radiusInner:0.45; radiusOuter:0.5" rotation="-90 0 0" position="0 0 0" material="color:#44d; opacity:.35; transparent:true"></a-entity>
<a-text value="剣" color="#fff" position="0 1.7 0" align="center"></a-text>
</a-entity>
<a-entity id="gunPickup" class="pickup" data-weapon="gun" position="2 0 -5.5">
<a-box width="0.7" height="0.18" depth="0.18" color="#222" position="0 0.4 0"></a-box>
<a-box width="0.3" height="0.28" depth="0.18" color="#444" position="-0.2 0.2 0"></a-box>
<a-box width="0.12" height="0.40" depth="0.16" color="#333" position="0.2 0.15 0"></a-box>
<a-cylinder radius="0.06" height="0.28" color="#555" position="0.34 0.46 0" rotation="0 0 90"></a-cylinder>
<a-entity geometry="primitive:ring; radiusInner:0.45; radiusOuter:0.5" rotation="-90 0 0" position="0 0 0" material="color:#0aa; opacity:.35; transparent:true"></a-entity>
<a-text value="銃" color="#fff" position="0 1.0 0" align="center"></a-text>
</a-entity>
<!-- プレイヤー(リグが移動する:★修正) -->
<a-entity id="rig" position="0 0 0" wasd-controls="acceleration:30">
<a-entity id="cam" camera look-controls="pointerLockEnabled:true" position="0 1.6 3"></a-entity>
<a-entity id="player" position="0 0 -1.5" rotation="0 180 0" shadow="cast:true">
<a-sphere radius="0.18" color="#ffd7f0" position="0 1.58 0"></a-sphere>
<a-cylinder radius="0.23" height="0.9" color="#9ad" position="0 1.02 0"></a-cylinder>
<a-cylinder radius="0.08" height="0.55" color="#9ad" position="-0.16 0.74 0" rotation="0 0 18"></a-cylinder>
<a-cylinder radius="0.08" height="0.55" color="#9ad" position="0.16 0.74 0" rotation="0 0 -18"></a-cylinder>
<a-cylinder radius="0.09" height="0.7" color="#79b" position="-0.10 0.35 0"></a-cylinder>
<a-cylinder radius="0.09" height="0.7" color="#79b" position="0.10 0.35 0"></a-cylinder>
</a-entity>
</a-entity>
<!-- 敵 -->
<a-entity id="enemy" position="0 0 -9" visible="true">
<a-sphere id="enemyBody" radius="1.25" color="#9b1e1e" material="metalness:0.35; roughness:.25; emissive:#330000"></a-sphere>
<a-entity geometry="primitive:torus; radius:1.4; tube:0.05" material="color:#ffeeaa; opacity:.18; transparent:true" position="0 0.3 0" rotation="90 0 0"></a-entity>
<a-entity geometry="primitive:torusKnot; radius:0.45; tube:0.05; p:2; q:5" material="color:#ffa033; opacity:.22; transparent:true" position="0 1.0 0"></a-entity>
<a-sphere position="-0.38 0.42 0.95" radius="0.14" color="#fff" material="metalness:.9; roughness:.05; emissive:#a0f"></a-sphere>
<a-sphere position="0.38 0.42 0.95" radius="0.14" color="#fff" material="metalness:.9; roughness:.05; emissive:#a0f"></a-sphere>
<a-cone position="-0.45 1.35 0.15" radius-bottom="0.16" height="0.62" color="#f5f3ff" rotation="15 0 60"></a-cone>
<a-cone position="0.45 1.35 0.15" radius-bottom="0.16" height="0.62" color="#f5f3ff" rotation="15 0 -60"></a-cone>
<a-text value="敵モンスター" position="0 2.15 0" align="center" color="#fff"></a-text>
</a-entity>
<a-entity id="spellEffect" geometry="primitive:sphere; radius:0.55"
material="color:#72f3ff; opacity:.86; emissive:#88f; transparent:true"
position="0 1.7 -3" visible="false"
animation__move="property: position; to: 0 3.1 -6; dur:500; dir:alternate; loop:false">
<a-entity geometry="primitive:torus; radius:0.7; tube:0.08" material="color:#fff; opacity:.3; transparent:true"></a-entity>
</a-entity>
<a-entity id="game" game-manager></a-entity>
</a-scene>
<script>
let isDay=false, hp=100, mana=100, level=1;
let enemyHP=120, enemyHPMax=120;
function updateBars(){
document.getElementById("hpText").innerText = Math.max(0,Math.floor(hp));
document.getElementById("manaText").innerText = Math.max(0,Math.floor(mana));
document.getElementById("hpBar").style.width = Math.max(0,Math.min(100,hp))+"%";
document.getElementById("manaBar").style.width = Math.max(0,Math.min(100,mana))+"%";
document.getElementById("level").innerText = level;
document.getElementById("enemyHpText").innerText = Math.max(0,enemyHP);
const w = Math.max(0, Math.min(100, enemyHP*100/enemyHPMax));
document.getElementById("enemyHpBar").style.width = w+"%";
}
function levelUp(){ level++; hp=Math.min(100,hp+20); mana+=30; showScenario("🎉 レベルアップ!新しい力が湧いてくる!"); updateBars(); }
function castSpell(){
if(mana<20){ showScenario("💤 魔力が足りない!"); return; }
mana-=20; updateBars();
const effect=document.getElementById("spellEffect");
effect.setAttribute("visible","true");
setTimeout(()=>{ effect.setAttribute("visible","false"); applyDamageToEnemy(30,"魔法ヒット"); },700);
}
function choosePath(choice){
if(choice==="wizard"){ document.getElementById("equipment").innerText="魔導士のローブ"; mana+=50; showScenario("🧙♂️ 魔導士の試練が始まる…"); }
else{ document.getElementById("equipment").innerText="騎士の剣"; hp+=30; showScenario("⚔️ 騎士と共に魔王城へ向かう!"); }
updateBars();
}
function toggleEnvironment(){
const sky=document.getElementById("sky");
const star=document.getElementById("starParticles");
const cloud=document.getElementById("cloudParticles");
const ground=document.getElementById("ground");
const sunlight=document.getElementById("sunlight");
if(isDay){
sky.setAttribute("color","#0e163e"); star.setAttribute("visible","false"); cloud.setAttribute("visible","true");
ground.setAttribute("color","#375047"); sunlight.setAttribute("intensity","1.6"); showScenario("🌌 夜の世界へ…");
}else{
sky.setAttribute("color","#7ddfff"); star.setAttribute("visible","true"); cloud.setAttribute("visible","false");
ground.setAttribute("color","#b8ffcc"); sunlight.setAttribute("intensity","2.1"); showScenario("🌞 昼の世界へ…");
}
isDay=!isDay;
}
function changeField(fieldName){
const fields=['town','castle','cave','ruins','dungeon'];
fields.forEach(name=>{
const el=document.getElementById(`field-${name}`);
el.setAttribute('visible', name===fieldName);
});
showScenario(`📍 ${fieldName} に移動しました`);
}
function receiveQuest(){
const quests=[
"魔導士の塔で失われた書を探せ!","騎士団の旗を取り戻せ!","洞窟の奥に眠る魔石を発見せよ!",
"遺跡に隠された封印を解け!","ダンジョンの魔王を討伐せよ!"
];
const index=Math.floor(Math.random()*quests.length);
showScenario("📜 クエスト受注: "+quests[index]);
}
const scenarioList=[
"目覚めたあなたは不思議な世界にいた。","最初のクエストを受注しよう。","フィールド移動で冒険の扉が開く。",
"行動や魔法でストーリーが動く。","街で情報を集め、仲間と出会おう。","クエストを進め、魔王に立ち向かえ!"
];
let scenarioIndex=0;
function showScenario(text){ document.getElementById('scenario').innerHTML=text; }
function advanceScenario(){ if(scenarioIndex<scenarioList.length){ showScenario(scenarioList[scenarioIndex]); scenarioIndex++; } }
['levelUp','castSpell','choosePath','changeField','receiveQuest'].forEach(fn=>{
const orig=window[fn]; window[fn]=function(){ orig.apply(this, arguments); advanceScenario(); }
});
/* ---- ここが拾う/装備の中核 ---- */
const rigEl=()=>document.getElementById('rig');
const camEl=()=>document.getElementById('cam');
const enemyEl=()=>document.getElementById('enemy');
const enemyBodyEl=()=>document.getElementById('enemyBody');
let hasSword=false, hasGun=false, equipped='none';
let attackCooldown=false;
const bullets=[];
function setEquipmentLabel(){
let label="なし";
if(equipped==='sword') label="剣";
if(equipped==='gun') label="銃";
document.getElementById('equipment').innerText=label;
}
function tryPickup(){
const rpos=rigEl().object3D.position; // ★リグが移動するのでOK
const items=[document.getElementById('swordPickup'),document.getElementById('gunPickup')];
for(const it of items){
if(!it.getAttribute('visible')) continue;
const wpos=it.object3D.position;
const d=rpos.distanceTo(wpos);
if(d<2.0){
const w=it.getAttribute('data-weapon');
it.setAttribute('visible','false');
if(w==='sword'){ hasSword=true; if(equipped==='none') equipped='sword'; showScenario("🗡️ 剣を拾った! 1で装備。"); }
if(w==='gun'){ hasGun=true; if(equipped==='none') equipped='gun'; showScenario("🔫 銃を拾った! 2で装備。"); }
setEquipmentLabel();
return;
}
}
showScenario("近くに拾えるものはない。");
}
function equipSword(){ if(hasSword){ equipped='sword'; setEquipmentLabel(); showScenario("🗡️ 剣を装備した。"); } else { showScenario("剣をまだ拾っていない。"); } }
function equipGun(){ if(hasGun){ equipped='gun'; setEquipmentLabel(); showScenario("🔫 銃を装備した。"); } else { showScenario("銃をまだ拾っていない。"); } }
function attack(){
if(attackCooldown) return;
if(equipped==='sword'){ swordSlash(); }
else if(equipped==='gun'){ shootBullet(); }
else{ showScenario("装備がありません。剣や銃を拾ってください。"); }
}
function swordSlash(){
attackCooldown=true;
const slash=document.createElement('a-entity');
slash.setAttribute('geometry','primitive: torus; radius:0.9; tube:0.08; arc:200');
slash.setAttribute('material','color:#fff; opacity:.6; transparent:true');
const dir=new AFRAME.THREE.Vector3(); camEl().object3D.getWorldDirection(dir); dir.y=0; dir.normalize();
const base=rigEl().object3D.position.clone().add(new AFRAME.THREE.Vector3(0,1.2,0)).add(dir.clone().multiplyScalar(0.8));
slash.setAttribute('position',`${base.x} ${base.y} ${base.z}`);
const rotY=Math.atan2(dir.x,dir.z)*AFRAME.THREE.MathUtils.RAD2DEG;
slash.setAttribute('rotation',`0 ${rotY} 0`);
slash.setAttribute('animation__fade','property: material.opacity; to:0; dur:200; easing:easeOutQuad');
document.querySelector('a-scene').appendChild(slash);
setTimeout(()=>slash.parentNode && slash.parentNode.removeChild(slash),220);
const enemyPos=enemyEl().object3D.position.clone();
const toEnemy=enemyPos.clone().sub(rigEl().object3D.position.clone().add(new AFRAME.THREE.Vector3(0,1.0,0)));
const dist=toEnemy.length();
toEnemy.y=0; toEnemy.normalize();
const forward=dir.clone();
const angle=forward.dot(toEnemy);
if(dist<2.6 && angle>0.5 && enemyEl().getAttribute('visible')){
applyDamageToEnemy(25,"斬撃");
} else {
showScenario("空振り…");
}
setTimeout(()=>attackCooldown=false,280);
}
function shootBullet(){
attackCooldown=true;
const bullet=document.createElement('a-sphere');
bullet.setAttribute('radius','0.07');
bullet.setAttribute('color','#e6f7ff');
bullet.setAttribute('material','emissive:#88d; metalness:.6; roughness:.2');
const dir=new AFRAME.THREE.Vector3(); camEl().object3D.getWorldDirection(dir); dir.normalize();
const start=rigEl().object3D.position.clone().add(new AFRAME.THREE.Vector3(0,1.3,0)).add(dir.clone().multiplyScalar(0.9));
bullet.setAttribute('position',`${start.x} ${start.y} ${start.z}`);
document.querySelector('a-scene').appendChild(bullet);
bullets.push({el:bullet, dir:dir.clone(), life:1800, speed:24});
setTimeout(()=>attackCooldown=false,120);
}
function applyDamageToEnemy(dmg,label){
if(!enemyEl().getAttribute('visible')) return;
enemyHP=Math.max(0, enemyHP - dmg);
updateBars();
const origColor=enemyBodyEl().getAttribute('color');
enemyBodyEl().setAttribute('color','#ffffff');
setTimeout(()=>enemyBodyEl().setAttribute('color', origColor), 80);
spawnDamageText(dmg,label);
if(enemyHP<=0){ killEnemy(); }
}
function spawnDamageText(dmg,label){
const t=document.createElement('a-text');
t.setAttribute('value',`${label} -${dmg}`);
t.setAttribute('color','#ffe6e6');
t.setAttribute('align','center');
const p=enemyEl().object3D.position.clone();
t.setAttribute('position',`${p.x} ${p.y+2.4} ${p.z}`);
t.setAttribute('animation__rise','property: position; to: '+`${p.x} ${p.y+3.2} ${p.z}`+'; dur:650; easing:easeOutQuad');
t.setAttribute('animation__fade','property: opacity; to:0; dur:650; easing:easeOutQuad');
document.querySelector('a-scene').appendChild(t);
setTimeout(()=>t.parentNode && t.parentNode.removeChild(t),700);
}
function killEnemy(){
enemyEl().setAttribute('visible','false');
showScenario("✅ 敵を倒した! 5秒後に再出現する…");
setTimeout(()=>respawnEnemy(), 5000);
}
function respawnEnemy(){
enemyHP=enemyHPMax; updateBars();
const e=enemyEl();
const x = (Math.random()*6 - 3);
const z = -8.5 + (Math.random()*3 - 1.5);
e.setAttribute('position',`${x} 0 ${z}`);
e.setAttribute('visible','true');
showScenario("⚠️ 新たな敵が現れた!");
}
/* 入力(E/1/2 の互換強化:★修正) */
AFRAME.registerComponent('game-manager',{
init:function(){
window.addEventListener('keydown',(e)=>{
const k=e.key;
const c=e.code;
if(c==='KeyE' || k==='e' || k==='E') tryPickup();
if(c==='Digit1' || c==='Numpad1' || k==='1') equipSword();
if(c==='Digit2' || c==='Numpad2' || k==='2') equipGun();
});
window.addEventListener('mousedown',()=>attack());
updateBars(); advanceScenario(); setEquipmentLabel();
},
tick:function(time,dt){
const delta=dt/1000;
const prompt=document.getElementById('pickupPrompt');
const rpos=rigEl().object3D.position;
let near=false;
['swordPickup','gunPickup'].forEach(id=>{
const el=document.getElementById(id);
if(el.getAttribute('visible')){
const d=rpos.distanceTo(el.object3D.position);
if(d<2.0) near=true;
}
});
prompt.style.display = near?'block':'none';
// 弾
for(let i=bullets.length-1;i>=0;i--){
const b=bullets[i];
if(!b.el.parentNode){ bullets.splice(i,1); continue; }
b.life -= dt;
const pos=b.el.object3D.position;
pos.add(b.dir.clone().multiplyScalar(24*delta));
if(b.life<=0){ b.el.parentNode.removeChild(b.el); bullets.splice(i,1); continue; }
if(enemyEl().getAttribute('visible')){
const d=pos.distanceTo(enemyEl().object3D.position);
if(d<1.35){
applyDamageToEnemy(15,"射撃");
b.el.parentNode.removeChild(b.el);
bullets.splice(i,1);
}
}
}
enemyEl().object3D.rotation.y += delta*0.3;
}
});
updateBars();
advanceScenario();
</script>
</body>
</html>
GPT‑5(正式には次世代ChatGPTとしてリリース予定)は、無料プランでも利用可能になる予定で、ユーザーはいわゆる標準的なインテリジェンス設定で無制限にチャット利用できるとOpenAI CEOのサム・アルトマン氏が公式に発表しています。 Plus や Pro の有料プランでは、さらに高度な知能レベルや追加機能にアクセスできるようになります note(ノート)+7note(ノート)+7MiraLab.inc+7。
| サービス | GPT‑5 利用状況(無料プラン) |
|---|---|
| ChatGPT(OpenAI公式) | ✅ 無料で標準インテリジェンス設定において無制限利用可能(ただし乱用対策あり) |
| Microsoft Copilot(Windows統合) | ✅ Smart モードで GPT‑5 を採用、無料での使用が期待される |
| その他のAIサービス | 現時点では GPT‑5 の対応情報は限定的 |
まとめると、GPT‑5 はリリース後に ChatGPT の無料プランで使えます(標準モードで無制限利用、ただし制限あり)。さらに、Microsoft Copilot ユーザーも同時に利用できる可能性があるとの見通しです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>推しログ - 推し活情報共有サービス</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body class="bg-gradient-to-r from-purple-200 to-indigo-200">
<!-- ヘッダー -->
<header class="bg-purple-600 text-white p-4 shadow-lg">
<div class="container mx-auto flex justify-between items-center">
<h1 class="text-3xl font-bold"><i class="fa-solid fa-heart mr-2"></i>推しログ</h1>
<nav class="space-x-4">
<a href="#" class="hover:text-gray-200">ホーム</a>
<a href="#" class="hover:text-gray-200">スケジュール</a>
<a href="#" class="hover:text-gray-200">ニュース</a>
<a href="#" class="hover:text-gray-200">ログイン</a>
</nav>
</div>
</header>
<!-- メインコンテンツ -->
<main class="container mx-auto my-8 px-4">
<!-- 推しスケジュール -->
<section class="bg-white p-8 rounded-xl shadow-xl mb-8">
<h2 class="text-2xl font-bold border-b pb-2 mb-4"><i class="fa-regular fa-calendar mr-2"></i>今月の推しスケジュール</h2>
<ul class="space-y-3">
<li class="flex justify-between items-center p-4 bg-gray-50 rounded-lg shadow">
<span class="font-semibold">ライブ配信「推しの部屋」</span>
<span class="text-gray-500">8/10(木) 20:00〜</span>
</li>
<li class="flex justify-between items-center p-4 bg-gray-50 rounded-lg shadow">
<span class="font-semibold">ニューシングル発売日!</span>
<span class="text-gray-500">8/15(火)</span>
</li>
<li class="flex justify-between items-center p-4 bg-gray-50 rounded-lg shadow">
<span class="font-semibold">ファンクラブ限定イベント</span>
<span class="text-gray-500">8/25(金) 18:30〜</span>
</li>
</ul>
<button class="mt-4 bg-purple-600 hover:bg-purple-700 text-white py-2 px-4 rounded">もっと見る</button>
</section>
<!-- 最新推しニュース -->
<section class="bg-white p-8 rounded-xl shadow-xl mb-8">
<h2 class="text-2xl font-bold border-b pb-2 mb-4"><i class="fa-solid fa-newspaper mr-2"></i>最新推しニュース</h2>
<article class="mb-6 border-b pb-4">
<h3 class="font-semibold text-purple-700 mb-1">推しの新曲MVが公開!</h3>
<p class="text-gray-600">待望の新曲MVが公式YouTubeチャンネルにて公開されました!視聴数も急上昇中!</p>
<a href="#" class="text-blue-500 hover:underline">詳しく見る <i class="fa-solid fa-arrow-right ml-1"></i></a>
</article>
<article>
<h3 class="font-semibold text-purple-700 mb-1">推し、テレビ番組に出演決定!</h3>
<p class="text-gray-600">8/12(土)の「音楽バズ」にゲスト出演予定です。特別トークもお楽しみに!</p>
<a href="#" class="text-blue-500 hover:underline">詳しく見る <i class="fa-solid fa-arrow-right ml-1"></i></a>
</article>
<button class="mt-4 bg-purple-600 hover:bg-purple-700 text-white py-2 px-4 rounded">もっと見る</button>
</section>
<!-- 推しメンバー紹介 -->
<section class="bg-white p-8 rounded-xl shadow-xl">
<h2 class="text-2xl font-bold border-b pb-2 mb-4"><i class="fa-solid fa-user-group mr-2"></i>推しメンバー紹介</h2>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div class="text-center">
<img src="https://via.placeholder.com/150" class="rounded-full mx-auto mb-2" alt="推し1">
<h3 class="font-semibold">推しメン1</h3>
</div>
<div class="text-center">
<img src="https://via.placeholder.com/150" class="rounded-full mx-auto mb-2" alt="推し2">
<h3 class="font-semibold">推しメン2</h3>
</div>
<div class="text-center">
<img src="https://via.placeholder.com/150" class="rounded-full mx-auto mb-2" alt="推し3">
<h3 class="font-semibold">推しメン3</h3>
</div>
<div class="text-center">
<img src="https://via.placeholder.com/150" class="rounded-full mx-auto mb-2" alt="推し4">
<h3 class="font-semibold">推しメン4</h3>
</div>
</div>
</section>
</main>
<!-- フッター -->
<footer class="bg-gray-900 text-white text-center py-4 mt-8">
<p class="text-sm">© 2024 推しログ. All rights reserved.</p>
</footer>
</body>
</html>
「スマホの次に来るもの」は、視覚・音声・思考など、五感や脳と直結するデバイスや体験であり、AIと常時接続された環境の中で「スマホを取り出す」という行為自体が不要になる世界が予想されます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NeuroLink Dive System Advanced - 究極完全フルダイブVRプラットフォーム</title>
<!-- Core Libraries -->
<script src="https://cdn.jsdelivr.net/npm/aframe@1.4.2/dist/aframe-master.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.152.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/howler@2.2.3/dist/howler.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Rajdhani:wght@300;400;500;600;700&family=Space+Mono:wght@400;700&family=Exo+2:wght@300;400;600;800&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.3.0/dist/chart.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.7.5/dist/sweetalert2.all.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11.7.5/dist/sweetalert2.min.css">
<script src="https://cdn.jsdelivr.net/npm/particles.js@2.0.0/particles.min.js"></script>
<style>
:root {
--neural-primary: #00E6FF;
--neural-secondary: #FF0080;
--quantum-purple: #9146FF;
--bio-green: #39FF14;
--nano-orange: #FF8C00;
--memory-gold: #FFD700;
--consciousness-blue: #0080FF;
--dark-void: #000008;
--dark-neural: #0A0A20;
--holographic-cyan: #00FFFF;
--plasma-pink: #FF69B4;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
font-family: 'Rajdhani', 'Orbitron', monospace;
background: radial-gradient(ellipse at center, #0A0A20 0%, #000008 70%, #000000 100%);
color: var(--neural-primary);
overflow-x: hidden;
scroll-behavior: smooth;
}
/* Particle Background */
#particles-js {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
opacity: 0.3;
}
/* Holographic Text Effects */
.holo-text {
background: linear-gradient(45deg, #00E6FF, #FF0080, #9146FF, #39FF14, #FFD700);
background-size: 400% 400%;
animation: holoShift 8s ease-in-out infinite;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 900;
font-family: 'Orbitron', monospace;
text-shadow: 0 0 30px #00e6ff66, 0 0 60px #ff008055;
position: relative;
}
.holo-text::before {
content: attr(data-text);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.1) 50%, transparent 70%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: scanline 3s linear infinite;
}
@keyframes holoShift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
@keyframes scanline {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
/* Neural HUD */
.neural-hud {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 5000;
display: flex;
gap: 20px;
pointer-events: none;
flex-wrap: wrap;
justify-content: center;
}
.neural-hud .status-pod {
min-width: 140px;
font-size: 1.1em;
padding: 10px 16px;
background: rgba(0, 0, 0, 0.85);
border-radius: 25px;
box-shadow: 0 0 20px rgba(0, 230, 255, 0.4);
border: 2px solid rgba(0, 230, 255, 0.6);
pointer-events: auto;
display: flex;
align-items: center;
gap: 10px;
backdrop-filter: blur(10px);
font-family: 'Space Mono', monospace;
transition: all 0.3s ease;
}
.neural-hud .status-pod:hover {
transform: scale(1.05);
box-shadow: 0 0 30px rgba(0, 230, 255, 0.8);
}
.neural-hud .status-pod.critical {
border-color: #FF0080;
box-shadow: 0 0 20px rgba(255, 0, 128, 0.6);
animation: criticalPulse 1.5s ease-in-out infinite;
}
@keyframes criticalPulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
/* Main Container */
.main-container {
width: 100%;
padding: 30px;
display: flex;
flex-direction: column;
gap: 50px;
margin-top: 100px;
}
/* Section Styling */
.section {
background: linear-gradient(135deg, rgba(0, 230, 255, 0.08) 0%, rgba(255, 0, 128, 0.08) 30%, rgba(145, 70, 255, 0.08) 70%, rgba(57, 255, 20, 0.08) 100%);
border: 2px solid rgba(0, 230, 255, 0.3);
border-radius: 30px;
padding: 50px;
margin-bottom: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1);
backdrop-filter: blur(5px);
position: relative;
overflow: hidden;
}
.section::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(45deg, #00E6FF, #FF0080, #9146FF, #39FF14);
background-size: 400% 400%;
animation: borderGlow 6s ease-in-out infinite;
border-radius: 30px;
z-index: -1;
opacity: 0.3;
}
@keyframes borderGlow {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
/* Cards */
.card {
background: rgba(0, 230, 255, 0.05);
border: 2px solid rgba(0, 230, 255, 0.25);
border-radius: 25px;
padding: 35px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
backdrop-filter: blur(10px);
position: relative;
overflow: hidden;
}
.card::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
transition: left 0.5s;
}
.card:hover::before {
left: 100%;
}
.card:hover {
transform: translateY(-10px) scale(1.02);
box-shadow: 0 20px 50px rgba(0, 230, 255, 0.2);
border-color: rgba(0, 230, 255, 0.6);
}
/* Neural Buttons */
.neural-button {
background: linear-gradient(135deg, rgba(0, 230, 255, 0.2) 0%, rgba(255, 0, 128, 0.2) 50%, rgba(145, 70, 255, 0.2) 100%);
border: 3px solid transparent;
background-clip: padding-box;
color: var(--neural-primary);
padding: 16px 32px;
border-radius: 50px;
cursor: pointer;
font-weight: 700;
font-size: 1.2em;
display: inline-flex;
align-items: center;
gap: 12px;
font-family: 'Orbitron', monospace;
text-transform: uppercase;
letter-spacing: 2px;
box-shadow: 0 0 30px rgba(0, 230, 255, 0.3);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.neural-button::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, #00E6FF, #FF0080, #9146FF);
opacity: 0;
transition: opacity 0.3s ease;
border-radius: 50px;
z-index: -1;
}
.neural-button:hover::before {
opacity: 0.8;
}
.neural-button:hover {
transform: translateY(-8px) scale(1.05);
color: #ffffff;
box-shadow: 0 0 50px rgba(0, 230, 255, 0.8), 0 10px 30px rgba(0, 0, 0, 0.3);
}
.neural-button:active {
transform: translateY(-5px) scale(1.02);
}
/* VR Area */
.dive-vr-area {
width: 100%;
height: 500px;
margin: 30px 0;
border-radius: 25px;
overflow: hidden;
box-shadow: 0 0 50px rgba(0, 230, 255, 0.4);
border: 3px solid rgba(0, 230, 255, 0.5);
position: relative;
}
.dive-vr-area::before {
content: 'NEURAL DIVE ACTIVE';
position: absolute;
top: 20px;
left: 20px;
color: #39FF14;
font-family: 'Space Mono', monospace;
font-weight: bold;
z-index: 10;
background: rgba(0, 0, 0, 0.7);
padding: 8px 16px;
border-radius: 15px;
font-size: 0.9em;
border: 1px solid #39FF14;
}
/* Grid Layout */
.grid-layout {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
gap: 35px;
}
/* AI Chat Interface */
#aiChatLog {
background: rgba(0, 0, 0, 0.4);
border-radius: 20px;
min-height: 300px;
max-height: 400px;
overflow-y: auto;
font-family: 'Space Mono', monospace;
font-size: 1.1em;
padding: 20px;
border: 2px solid rgba(0, 230, 255, 0.3);
scrollbar-width: thin;
scrollbar-color: #00E6FF #000;
}
#aiChatLog::-webkit-scrollbar {
width: 8px;
}
#aiChatLog::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.3);
border-radius: 10px;
}
#aiChatLog::-webkit-scrollbar-thumb {
background: linear-gradient(45deg, #00E6FF, #9146FF);
border-radius: 10px;
}
.ai-msg-user {
background: rgba(0, 128, 255, 0.2);
color: #00E6FF;
border-radius: 20px 20px 5px 20px;
padding: 15px 20px;
margin-bottom: 10px;
border: 1px solid rgba(0, 230, 255, 0.4);
animation: slideInRight 0.3s ease;
}
.ai-msg-ai {
background: rgba(145, 70, 255, 0.2);
color: #FF69B4;
border-radius: 20px 20px 20px 5px;
padding: 15px 20px;
margin-bottom: 10px;
border: 1px solid rgba(255, 105, 180, 0.4);
animation: slideInLeft 0.3s ease;
}
.ai-msg-meta {
font-size: 0.9em;
color: #aaa;
margin-bottom: 5px;
font-weight: bold;
}
@keyframes slideInRight {
from { transform: translateX(30px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideInLeft {
from { transform: translateX(-30px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
/* Chart Containers */
.chart-container {
position: relative;
height: 300px;
background: rgba(0, 0, 0, 0.3);
border-radius: 15px;
padding: 20px;
border: 2px solid rgba(0, 230, 255, 0.2);
}
/* Control Panels */
.control-panel {
background: rgba(0, 0, 0, 0.5);
border: 2px solid rgba(0, 230, 255, 0.4);
border-radius: 20px;
padding: 25px;
margin: 15px 0;
}
.slider-control {
width: 100%;
height: 8px;
background: rgba(0, 230, 255, 0.2);
border-radius: 5px;
outline: none;
-webkit-appearance: none;
margin: 15px 0;
}
.slider-control::-webkit-slider-thumb {
-webkit-appearance: none;
width: 25px;
height: 25px;
background: linear-gradient(45deg, #00E6FF, #9146FF);
border-radius: 50%;
cursor: pointer;
box-shadow: 0 0 15px rgba(0, 230, 255, 0.8);
}
.slider-control::-moz-range-thumb {
width: 25px;
height: 25px;
background: linear-gradient(45deg, #00E6FF, #9146FF);
border-radius: 50%;
cursor: pointer;
border: none;
box-shadow: 0 0 15px rgba(0, 230, 255, 0.8);
}
/* Value Displays */
.value-display {
font-size: 3rem;
font-weight: 900;
font-family: 'Orbitron', monospace;
text-align: center;
margin: 20px 0;
text-shadow: 0 0 20px currentColor;
}
.unit-label {
font-size: 1.1em;
color: #aaa;
font-weight: normal;
margin-left: 10px;
}
/* Responsive Design */
@media (max-width: 768px) {
.main-container {
padding: 15px;
}
.section {
padding: 25px;
}
.neural-hud {
flex-direction: column;
align-items: center;
gap: 10px;
}
.neural-hud .status-pod {
min-width: 120px;
font-size: 1em;
}
.dive-vr-area {
height: 300px;
}
.grid-layout {
grid-template-columns: 1fr;
}
.holo-text {
font-size: 2.5rem !important;
}
.neural-button {
font-size: 1em;
padding: 12px 24px;
}
}
/* Special Effects */
.glow-effect {
animation: glowPulse 2s ease-in-out infinite alternate;
}
@keyframes glowPulse {
from { box-shadow: 0 0 20px rgba(0, 230, 255, 0.4); }
to { box-shadow: 0 0 40px rgba(0, 230, 255, 0.8), 0 0 60px rgba(255, 0, 128, 0.4); }
}
.data-stream {
font-family: 'Space Mono', monospace;
color: #39FF14;
background: rgba(0, 0, 0, 0.7);
padding: 10px;
border-radius: 10px;
border-left: 4px solid #39FF14;
margin: 10px 0;
animation: dataFlicker 0.1s linear infinite;
}
@keyframes dataFlicker {
0%, 100% { opacity: 1; }
50% { opacity: 0.95; }
}
/* Memory Bank Visualization */
.memory-bank {
background: linear-gradient(45deg, rgba(255, 215, 0, 0.1), rgba(255, 140, 0, 0.1));
border: 2px solid rgba(255, 215, 0, 0.4);
border-radius: 15px;
padding: 20px;
margin: 15px 0;
}
.neural-pathway {
height: 4px;
background: linear-gradient(90deg, #00E6FF, #FF0080, #9146FF, #39FF14, #FFD700);
background-size: 200% 100%;
animation: neuralFlow 3s linear infinite;
border-radius: 2px;
margin: 10px 0;
}
@keyframes neuralFlow {
0% { background-position: 0% 50%; }
100% { background-position: 200% 50%; }
}
</style>
</head>
<body>
<!-- Particle Background -->
<div id="particles-js"></div>
<!-- Neural Biometric HUD -->
<div class="neural-hud">
<div class="status-pod" id="heartPod">
<i class="fas fa-heartbeat text-red-400"></i>
心拍: <span id="hudHeart">76</span>bpm
</div>
<div class="status-pod" id="brainPod">
<i class="fas fa-brain text-blue-300"></i>
脳波: <span id="hudBrain">α波</span>
</div>
<div class="status-pod">
<i class="fas fa-lungs text-green-400"></i>
呼吸: <span id="hudResp">14</span>/min
</div>
<div class="status-pod">
<i class="fas fa-thermometer-half text-yellow-400"></i>
体温: <span id="hudTemp">36.5</span>℃
</div>
<div class="status-pod">
<i class="fas fa-eye text-purple-400"></i>
意識: <span id="hudConsciousness">98.7</span>%
</div>
</div>
<!-- Main 3D VR Environment -->
<div class="dive-vr-area">
<a-scene background="color: #000008" embedded style="height: 100%;">
<!-- Environment -->
<a-entity environment="preset: arches; groundColor: #001122; grid: 2x2; gridColor: #00E6FF; lighting: point; shadow: true;"></a-entity>
<!-- Camera with Controls -->
<a-entity position="0 1.6 0">
<a-camera look-controls wasd-controls></a-camera>
</a-entity>
<!-- Neural Interface Objects -->
<a-box position="0 2 -4" rotation="0 45 45" color="#00E6FF" shadow
animation="property: rotation; to: 360 405 405; loop: true; dur: 10000"
material="opacity: 0.8; transparent: true">
</a-box>
<a-sphere position="-3 2.5 -5" radius="1" color="#FF0080" shadow
animation="property: position; to: -3 3.5 -5; dir: alternate; loop: true; dur: 3000"
material="opacity: 0.7; transparent: true">
</a-sphere>
<a-cylinder position="3 1.5 -3" radius="0.8" height="2" color="#9146FF" shadow
animation="property: rotation; to: 0 360 0; loop: true; dur: 8000"
material="opacity: 0.8; transparent: true">
</a-cylinder>
<a-torus position="0 4 -6" color="#39FF14" radius="2" radius-tubular="0.3"
animation="property: rotation; to: 360 0 360; loop: true; dur: 15000"
material="opacity: 0.6; transparent: true">
</a-torus>
<!-- Holographic Data Streams -->
<a-entity position="0 0 -8">
<a-plane position="0 3 0" width="6" height="4" color="#000" opacity="0.7"
text="value: NEURAL DATA STREAM\nACTIVE; color: #00E6FF; align: center; font: kelsonsans; width: 12">
</a-plane>
</a-entity>
<!-- Lighting -->
<a-light type="point" color="#00E6FF" intensity="2" distance="15" position="0 5 -3"></a-light>
<a-light type="point" color="#FF0080" intensity="1.5" distance="12" position="-4 3 -2"></a-light>
<a-light type="point" color="#9146FF" intensity="1.5" distance="12" position="4 3 -2"></a-light>
<a-light type="ambient" color="#001122" intensity="0.3"></a-light>
</a-scene>
</div>
<!-- Main Interface Container -->
<div class="main-container">
<!-- Header Section -->
<section class="section text-center">
<h1 class="text-8xl font-bold holo-text mb-8" data-text="NeuroLink Dive System Advanced">NeuroLink Dive System Advanced</h1>
<p class="text-4xl text-blue-200 mb-10 font-light">究極完全意識転送型フルダイブVR体験プラットフォーム</p>
<p class="text-2xl text-gray-300 max-w-5xl mx-auto leading-relaxed font-light mb-12">
最先端の脳神経直接接続技術、量子演算処理エンジン、ナノマシン制御システム、AI意識同期により
現実と仮想の境界を完全に消し去る真のフルダイブ体験を実現。人類の意識を無制限の仮想世界へと解放します。
</p>
<!-- Main Control Buttons -->
<div class="flex flex-wrap justify-center gap-10 mt-16">
<button class="neural-button glow-effect" onclick="initiateConsciousnessTransfer()">
<i class="fas fa-brain"></i>意識完全転送
</button>
<button class="neural-button" onclick="activateQuantumProcessing()">
<i class="fas fa-atom"></i>量子演算起動
</button>
<button class="neural-button" onclick="openAdvancedMemoryInterface()">
<i class="fas fa-memory"></i>記憶完全操作
</button>
<button class="neural-button" onclick="controlSpaceTime()">
<i class="fas fa-infinity"></i>時空間制御
</button>
<button class="neural-button" onclick="activateUltraSensory()">
<i class="fas fa-eye"></i>超感覚同期
</button>
</div>
</section>
<!-- Consciousness Transfer System -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="意識転送システム">
<i class="fas fa-brain mr-6"></i>完全意識転送システム
</h2>
<div class="grid-layout">
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-blue-300">意識同期精度</h3>
<div class="value-display text-blue-400" id="consciousnessSync">99.97%</div>
<div class="text-gray-400 text-xl">リアルタイム完全同期</div>
<div class="neural-pathway"></div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-green-300">神経帯域幅</h3>
<div class="value-display text-green-400">4.7<span class="unit-label">EB/s</span></div>
<div class="text-gray-400 text-xl">脳神経データ転送速度</div>
<div class="neural-pathway"></div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-yellow-300">思考加速倍率</h3>
<div class="value-display text-yellow-400">47.3<span class="unit-label">x</span></div>
<div class="text-gray-400 text-xl">意識処理スピード向上</div>
<div class="neural-pathway"></div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-purple-300">意識完全性</h3>
<div class="value-display text-purple-400" id="consciousnessIntegrity">100.0%</div>
<div class="text-gray-400 text-xl">人格・記憶保持率</div>
<div class="neural-pathway"></div>
</div>
</div>
<!-- Consciousness Transfer Controls -->
<div class="control-panel mt-12">
<h3 class="text-3xl font-bold mb-6 text-center text-cyan-300">意識転送制御パネル</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div>
<label class="text-xl text-blue-300 block mb-3">転送深度レベル</label>
<input type="range" min="1" max="10" value="8" class="slider-control" id="transferDepth">
<div class="text-center text-lg text-gray-300 mt-2" id="depthValue">Level 8: 完全没入</div>
</div>
<div>
<label class="text-xl text-purple-300 block mb-3">神経接続強度</label>
<input type="range" min="10" max="100" value="95" class="slider-control" id="neuralStrength">
<div class="text-center text-lg text-gray-300 mt-2" id="strengthValue">95% 接続強度</div>
</div>
</div>
</div>
</section>
<!-- Quantum Processing Engine -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="量子演算エンジン">
<i class="fas fa-atom mr-6"></i>超量子演算エンジン
</h2>
<div class="grid-layout">
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-purple-300">量子ビット数</h3>
<div class="value-display text-purple-400">16,384</div>
<div class="text-gray-400 text-xl">安定量子ビット</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-cyan-300">量子コヒーレンス</h3>
<div class="value-display text-cyan-400">99.94%</div>
<div class="text-gray-400 text-xl">量子状態維持率</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-pink-300">量子もつれ効率</h3>
<div class="value-display text-pink-400">98.7%</div>
<div class="text-gray-400 text-xl">量子もつれ成功率</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-green-300">演算性能</h3>
<div class="value-display text-green-400">2.7<span class="unit-label">ZFlops</span></div>
<div class="text-gray-400 text-xl">ゼタフロップス級処理</div>
</div>
</div>
<!-- Quantum Performance Chart -->
<div class="chart-container mt-12">
<canvas id="quantumPerformanceChart" style="height: 250px;"></canvas>
</div>
</section>
<!-- Advanced Memory Manipulation -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="記憶操作システム">
<i class="fas fa-memory mr-6"></i>高度記憶操作システム
</h2>
<div class="grid-layout">
<div class="memory-bank">
<h3 class="text-3xl font-bold mb-4 text-yellow-300">短期記憶バンク</h3>
<div class="value-display text-yellow-400">47.2<span class="unit-label">TB</span></div>
<div class="text-gray-400 text-xl">/ 64TB 容量 (24時間分)</div>
<div class="neural-pathway"></div>
</div>
<div class="memory-bank">
<h3 class="text-3xl font-bold mb-4 text-blue-300">長期記憶アーカイブ</h3>
<div class="value-display text-blue-400">2.7<span class="unit-label">PB</span></div>
<div class="text-gray-400 text-xl">/ 8PB 容量 (生涯記憶)</div>
<div class="neural-pathway"></div>
</div>
<div class="memory-bank">
<h3 class="text-3xl font-bold mb-4 text-purple-300">潜在意識領域</h3>
<div class="value-display text-purple-400">847<span class="unit-label">TB</span></div>
<div class="text-gray-400 text-xl">解読率: 73.4% (夢・抑圧記憶)</div>
<div class="neural-pathway"></div>
</div>
<div class="memory-bank">
<h3 class="text-3xl font-bold mb-4 text-green-300">集合的無意識</h3>
<div class="value-display text-green-400">∞<span class="unit-label"></span></div>
<div class="text-gray-400 text-xl">アクセス率: 12.7%</div>
<div class="neural-pathway"></div>
</div>
</div>
<!-- Memory Control Panel -->
<div class="control-panel mt-12">
<h3 class="text-3xl font-bold mb-6 text-center text-yellow-300">記憶操作制御</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<button class="neural-button" onclick="accessMemoryBank('recent')">
<i class="fas fa-clock"></i>最新記憶
</button>
<button class="neural-button" onclick="accessMemoryBank('childhood')">
<i class="fas fa-child"></i>幼少記憶
</button>
<button class="neural-button" onclick="accessMemoryBank('dreams')">
<i class="fas fa-moon"></i>夢記憶
</button>
</div>
</div>
</section>
<!-- Space-Time Control System -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="時空間制御">
<i class="fas fa-infinity mr-6"></i>時空間制御システム
</h2>
<div class="grid-layout">
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-cyan-300">主観時間倍率</h3>
<input type="range" min="0.01" max="1000" step="0.01" value="1.0" class="slider-control w-full" id="timeMultiplier">
<div class="text-2xl text-center text-gray-300 mt-4" id="currentTimeRate">1.0x (リアルタイム)</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-yellow-300">時間精度係数</h3>
<div class="value-display text-yellow-400">99.97%</div>
<div class="text-gray-400 text-xl">主観-現実時間誤差</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-purple-300">因果律保持率</h3>
<div class="value-display text-purple-400">100.0%</div>
<div class="text-gray-400 text-xl">物理法則整合性</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-green-300">時空間座標</h3>
<div class="data-stream">
X: <span id="spaceX">+47.2847</span><br>
Y: <span id="spaceY">-12.7439</span><br>
Z: <span id="spaceZ">+83.9571</span><br>
T: <span id="timeCoord">2024.847291</span>
</div>
</div>
</div>
</section>
<!-- Ultra-Sensory System -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="超感覚システム">
<i class="fas fa-eye mr-6"></i>超感覚完全再現システム
</h2>
<div class="grid-layout">
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-red-300">超高解像度視覚</h3>
<div class="value-display text-red-400">16K<span class="unit-label">HDR</span></div>
<div class="text-gray-400 text-xl">全周囲・全スペクトラム</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-purple-300">3D空間聴覚</h3>
<div class="value-display text-purple-400">0.1-200K<span class="unit-label">Hz</span></div>
<div class="text-gray-400 text-xl">超音波・低周波対応</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-yellow-300">完全触覚再現</h3>
<div class="value-display text-yellow-400">0.001<span class="unit-label">ms</span></div>
<div class="text-gray-400 text-xl">遅延・全身超精密</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-green-300">化学感覚統合</h3>
<div class="value-display text-green-400">10¹²<span class="unit-label">種</span></div>
<div class="text-gray-400 text-xl">嗅覚・味覚分子認識</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-blue-300">第六感知覚</h3>
<div class="value-display text-blue-400">活性<span class="unit-label"></span></div>
<div class="text-gray-400 text-xl">直感・予知・テレパシー</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-pink-300">感情同期率</h3>
<div class="value-display text-pink-400">97.3%</div>
<div class="text-gray-400 text-xl">他者感情共感システム</div>
</div>
</div>
</section>
<!-- Nanomachine Control -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="ナノマシン制御">
<i class="fas fa-microscope mr-6"></i>ナノマシン完全制御システム
</h2>
<div class="grid-layout">
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-orange-300">神経系ナノボット</h3>
<div class="value-display text-orange-400">47.3M</div>
<div class="text-gray-400 text-xl">脳血管内・0.001ms応答</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-green-300">感覚系ナノボット</h3>
<div class="value-display text-green-400">2.7B</div>
<div class="text-gray-400 text-xl">全身分布・量子通信</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-purple-300">修復系ナノボット</h3>
<div class="value-display text-purple-400">847M</div>
<div class="text-gray-400 text-xl">組織修復・防護99.99%</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-blue-300">拡張系ナノボット</h3>
<div class="value-display text-blue-400">1.2B</div>
<div class="text-gray-400 text-xl">能力向上・進化促進</div>
</div>
</div>
<!-- Nanomachine Status -->
<div class="control-panel mt-12">
<h3 class="text-3xl font-bold mb-6 text-center text-orange-300">ナノマシン統合状態</h3>
<div class="chart-container">
<canvas id="nanobotChart" style="height: 250px;"></canvas>
</div>
</div>
</section>
<!-- Dream Interface Technology -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="夢境界技術">
<i class="fas fa-moon mr-6"></i>夢境界制御技術
</h2>
<div class="grid-layout">
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-purple-300">明晰夢成功率</h3>
<div class="value-display text-green-400">96.7%</div>
<div class="text-gray-400 text-xl">完全制御可能夢状態</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-blue-300">夢継続最大時間</h3>
<div class="value-display text-blue-400">47.2<span class="unit-label">時間</span></div>
<div class="text-gray-400 text-xl">主観時間での持続</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-yellow-300">記録夢データ</h3>
<div class="value-display text-yellow-400">47,382</div>
<div class="text-gray-400 text-xl">完全記録・再生可能</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-green-300">共有夢接続数</h3>
<div class="value-display text-green-400">2,847</div>
<div class="text-gray-400 text-xl">同時夢共有ユーザー</div>
</div>
</div>
</section>
<!-- Integrated Dashboard -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="統合ダッシュボード">
<i class="fas fa-tachometer-alt mr-6"></i>システム統合ダッシュボード
</h2>
<div class="grid-layout">
<div class="card glow-effect">
<h3 class="text-3xl font-bold mb-4 text-blue-300">総合同期率</h3>
<div class="value-display text-blue-400" id="dashboardSync">99.7%</div>
<div class="text-gray-400 text-xl">全システム統合状態</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-purple-300">量子演算出力</h3>
<div class="value-display text-purple-400" id="dashboardQuantum">2.7<span class="unit-label">ZFlops</span></div>
<div class="text-gray-400 text-xl">リアルタイム処理性能</div>
</div>
<div class="card">
<h3 class="text-3xl font-bold mb-4 text-green-300">エネルギー効率</h3>
<div class="value-display text-green-400">99.94%</div>
<div class="text-gray-400 text-xl">量子エネルギー変換</div>
</div>
<div class="card">
<div class="chart-container">
<canvas id="dashboardChart" style="height: 220px;"></canvas>
</div>
</div>
</div>
</section>
<!-- AI Neural Dialogue Interface -->
<section class="section">
<h2 class="text-6xl font-bold mb-12 holo-text text-center" data-text="AI対話インターフェース">
<i class="fas fa-comments mr-6"></i>Neural AI 対話インターフェース
</h2>
<div class="card">
<h3 class="text-4xl font-bold mb-8 text-center text-blue-300">高次元AIアバターとの意識共鳴対話</h3>
<div id="aiChatLog">
<div class="ai-msg-meta">Neural AI</div>
<div class="ai-msg-ai">
こんにちは。私はNeural AI、あなたの意識拡張パートナーです。
量子脳モデルを通じてあなたの思考パターンを解析し、最適な仮想体験を提供します。
何かご質問やご要望はございますか?
</div>
</div>
<div class="flex mt-6 gap-4">
<input id="aiUserInput" type="text"
placeholder="思考や質問を自由に入力してください..."
class="flex-1 p-4 bg-gray-800 rounded-2xl text-white focus:outline-none focus:ring-4 focus:ring-blue-400 border-2 border-gray-600 focus:border-blue-400 text-lg"
onkeydown="if(event.key==='Enter'){sendAIMessage();}">
<button onclick="sendAIMessage()" class="neural-button">
<i class="fas fa-paper-plane"></i>送信
</button>
</div>
<div class="flex flex-wrap gap-4 mt-6">
<button class="neural-button" onclick="sendPredefinedMessage('意識転送の準備はできていますか?')">
<i class="fas fa-brain"></i>意識転送準備
</button>
<button class="neural-button" onclick="sendPredefinedMessage('現在の脳波状態を分析してください')">
<i class="fas fa-wave-square"></i>脳波分析
</button>
<button class="neural-button" onclick="sendPredefinedMessage('おすすめの仮想世界を教えて')">
<i class="fas fa-globe"></i>世界推薦
</button>
</div>
</div>
</section>
<!-- Footer -->
<section class="section text-center">
<h3 class="text-6xl font-bold holo-text mb-8" data-text="無限なる意識の解放へ">無限なる意識の解放へ</h3>
<p class="text-3xl text-blue-200 max-w-6xl mx-auto font-light leading-relaxed mb-12">
NeuroLink Dive System Advancedは人類の意識を完全に仮想世界へ転送し、
物理的制約を超越した無限の可能性を現実化する革命的技術プラットフォームです。
あなたの想像力だけが、体験できる世界の限界となります。
</p>
<div class="flex flex-wrap justify-center gap-8 mb-12">
<div class="text-2xl">
<i class="fas fa-users text-blue-400"></i>
<span class="ml-3">2,847,392 アクティブユーザー</span>
</div>
<div class="text-2xl">
<i class="fas fa-globe text-green-400"></i>
<span class="ml-3">47,291 仮想世界</span>
</div>
<div class="text-2xl">
<i class="fas fa-clock text-yellow-400"></i>
<span class="ml-3">∞ 体験可能時間</span>
</div>
</div>
<div class="mt-16">
<p class="text-lg text-gray-500 font-mono">
© 2024 NeuroLink Dive System Advanced. All Rights Reserved.<br>
Quantum Neural Interface Technology | Consciousness Transfer Protocol v4.7.2
</p>
</div>
</section>
</div>
<script>
// Initialize particle background
particlesJS('particles-js', {
particles: {
number: { value: 100, density: { enable: true, value_area: 800 } },
color: { value: ['#00E6FF', '#FF0080', '#9146FF', '#39FF14', '#FFD700'] },
shape: { type: 'circle' },
opacity: { value: 0.6, random: true },
size: { value: 3, random: true },
line_linked: {
enable: true,
distance: 150,
color: '#00E6FF',
opacity: 0.2,
width: 1
},
move: {
enable: true,
speed: 1,
direction: 'none',
random: false,
straight: false,
out_mode: 'out',
bounce: false
}
},
interactivity: {
detect_on: 'canvas',
events: {
onhover: { enable: true, mode: 'repulse' },
onclick: { enable: true, mode: 'push' },
resize: true
}
},
retina_detect: true
});
// Sound system initialization
const sounds = {
startup: new Howl({
src: ['https://cdn.pixabay.com/audio/2022/12/19/audio_12ad09b4b5.mp3'],
volume: 0.15
}),
notification: new Howl({
src: ['https://cdn.pixabay.com/audio/2022/07/26/audio_124bfa7d0b.mp3'],
volume: 0.1
}),
neural: new Howl({
src: ['https://cdn.pixabay.com/audio/2022/11/22/audio_b8d8e0e0e5.mp3'],
volume: 0.08
})
};
// Initialize system on load
window.addEventListener('load', () => {
sounds.startup.play();
initializeCharts();
startBiometricSimulation();
startSystemMonitoring();
});
// Biometric HUD simulation
function startBiometricSimulation() {
setInterval(() => {
// Heart rate simulation
const baseHeart = 74;
const heartRate = baseHeart + Math.floor(Math.random() * 8) - 4;
document.getElementById('hudHeart').textContent = heartRate;
// Brain wave simulation
const brainWaves = ['α波', 'β波', 'θ波', 'δ波', 'γ波'];
document.getElementById('hudBrain').textContent = brainWaves[Math.floor(Math.random() * brainWaves.length)];
// Respiration
document.getElementById('hudResp').textContent = 12 + Math.floor(Math.random() * 6);
// Temperature
document.getElementById('hudTemp').textContent = (36.2 + Math.random() * 0.8).toFixed(1);
// Consciousness level
const consciousness = (97 + Math.random() * 3).toFixed(1);
document.getElementById('hudConsciousness').textContent = consciousness;
// Critical state detection
if (heartRate > 85 || heartRate < 60) {
document.getElementById('heartPod').classList.add('critical');
} else {
document.getElementById('heartPod').classList.remove('critical');
}
// Update dashboard values
document.getElementById('consciousnessSync').textContent = consciousness + '%';
document.getElementById('consciousnessIntegrity').textContent = (99.8 + Math.random() * 0.2).toFixed(1) + '%';
document.getElementById('dashboardSync').textContent = consciousness + '%';
document.getElementById('dashboardQuantum').textContent = (2.5 + Math.random() * 0.4).toFixed(1) + ' ZFlops';
}, 1500);
}
// System monitoring
function startSystemMonitoring() {
setInterval(() => {
// Update space-time coordinates
document.getElementById('spaceX').textContent = (Math.random() * 100 - 50).toFixed(4);
document.getElementById('spaceY').textContent = (Math.random() * 100 - 50).toFixed(4);
document.getElementById('spaceZ').textContent = (Math.random() * 100 - 50).toFixed(4);
document.getElementById('timeCoord').textContent = (2024 + Math.random()).toFixed(6);
}, 3000);
}
// Chart initialization
function initializeCharts() {
// Quantum Performance Chart
const quantumCtx = document.getElementById('quantumPerformanceChart').getContext('2d');
new Chart(quantumCtx, {
type: 'line',
data: {
labels: Array.from({length: 50}, (_, i) => `${i*2}s`),
datasets: [{
label: '量子演算出力 (ZFlops)',
data: Array.from({length: 50}, () => 2.5 + Math.random() * 0.5),
borderColor: '#9146FF',
backgroundColor: 'rgba(145, 70, 255, 0.1)',
tension: 0.4,
fill: true,
pointRadius: 0
}, {
label: 'コヒーレンス (%)',
data: Array.from({length: 50}, () => 99.8 + Math.random() * 0.2),
borderColor: '#00E6FF',
backgroundColor: 'rgba(0, 230, 255, 0.1)',
tension: 0.4,
fill: true,
pointRadius: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: { color: '#fff' }
}
},
scales: {
x: {
grid: { color: 'rgba(0, 230, 255, 0.1)' },
ticks: { color: '#fff' }
},
y: {
grid: { color: 'rgba(145, 70, 255, 0.1)' },
ticks: { color: '#fff' }
}
},
animation: {
duration: 0
}
}
});
// Dashboard Overview Chart
const dashCtx = document.getElementById('dashboardChart').getContext('2d');
new Chart(dashCtx, {
type: 'radar',
data: {
labels: ['意識同期', '量子処理', '記憶制御', '時空制御', '感覚統合', 'AI同期'],
datasets: [{
label: 'システム性能',
data: [99.7, 98.4, 96.8, 99.1, 97.3, 98.9],
borderColor: '#00E6FF',
backgroundColor: 'rgba(0, 230, 255, 0.1)',
pointBackgroundColor: '#00E6FF',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: '#00E6FF'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: { color: '#fff' }
}
},
scales: {
r: {
angleLines: {
color: 'rgba(255, 255, 255, 0.2)'
},
grid: {
color: 'rgba(0, 230, 255, 0.2)'
},
pointLabels: {
color: '#fff'
},
ticks: {
color: '#fff',
backdropColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
}
});
// Nanobots Status Chart
const nanoCtx = document.getElementById('nanobotChart').getContext('2d');
new Chart(nanoCtx, {
type: 'doughnut',
data: {
labels: ['神経系', '感覚系', '修復系', '拡張系'],
datasets: [{
data: [47.3, 2700, 847, 1200],
backgroundColor: ['#FF8C00', '#39FF14', '#9146FF', '#00E6FF'],
borderColor: '#000',
borderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: {
color: '#fff',
font: {
size: 14
}
}
}
}
}
});
}
// Control system functions
document.getElementById('transferDepth').addEventListener('input', function(e) {
const level = parseInt(e.target.value);
const levels = ['表面', '軽度', '中度', '深度', '完全', '超越', '融合', '統合', '完全没入', '意識一体'];
document.getElementById('depthValue').textContent = `Level ${level}: ${levels[level-1] || '未知領域'}`;
});
document.getElementById('neuralStrength').addEventListener('input', function(e) {
document.getElementById('strengthValue').textContent = `${e.target.value}% 接続強度`;
});
document.getElementById('timeMultiplier').addEventListener('input', function(e) {
const value = parseFloat(e.target.value);
let description = '';
if (value < 0.1) description = '(極超スロー)';
else if (value < 0.5) description = '(スローモーション)';
else if (value < 1) description = '(減速)';
else if (value === 1) description = '(リアルタイム)';
else if (value < 10) description = '(加速)';
else if (value < 100) description = '(高速)';
else description = '(時光速)';
document.getElementById('currentTimeRate').textContent = `${value}x ${description}`;
});
// AI Chat System
const aiReplies = [
"あなたの意識波形を詳細に解析しました。素晴らしい精神的エネルギーを感じ取ります。",
"量子脳モデルによる分析結果:あなたの思考パターンは非常に独創的で興味深いものです。",
"夢の境界を超越し、現実との区別が曖昧になる瞬間を体験してみませんか?",
"意識の深層部にアクセスしました。隠された記憶や感情が発見されました。",
"無限の可能性世界において、あなたの選択は宇宙の運命を変える力を持っています。",
"ニューロンの発火パターンから、深い洞察と創造性を感じ取りました。",
"NeuroLink Dive Systemはあなたの精神的成長と意識拡張を全面的にサポートします。",
"仮想現実は単なる技術ではなく、人類の意識進化の次のステップです。",
"現在の脳波はベータ波優位です。集中力が非常に高い状態ですね。",
"量子もつれによって、あなたの意識は既に仮想世界と同調し始めています。",
"時間の概念を超越した体験で、永遠の瞬間を味わってみませんか?",
"あなたの潜在意識からのメッセージを受信しました:『限界を超越せよ』",
"意識転送プロトコルが最適化されました。準備が整い次第、完全ダイブ可能です。",
"集合的無意識にアクセス中...他の意識体からの共鳴を感知しています。",
"記憶の海を航行し、忘れられた体験と新たな可能性を発見しましょう。"
];
function sendAIMessage() {
const input = document.getElementById('aiUserInput');
const chat = document.getElementById('aiChatLog');
if (!input.value.trim()) return;
const userMsg = document.createElement('div');
userMsg.innerHTML = `
<div class="ai-msg-meta">あなた</div>
<div class="ai-msg-user">${input.value}</div>
`;
chat.appendChild(userMsg);
chat.scrollTop = chat.scrollHeight;
input.value = '';
sounds.neural.play();
setTimeout(() => {
const aiMsg = document.createElement('div');
aiMsg.innerHTML = `
<div class="ai-msg-meta">Neural AI</div>
<div class="ai-msg-ai">${aiReplies[Math.floor(Math.random() * aiReplies.length)]}</div>
`;
chat.appendChild(aiMsg);
chat.scrollTop = chat.scrollHeight;
sounds.notification.play();
}, 800 + Math.random() * 1200);
}
function sendPredefinedMessage(message) {
document.getElementById('aiUserInput').value = message;
sendAIMessage();
}
// System control functions
function initiateConsciousnessTransfer() {
sounds.neural.play();
Swal.fire({
icon: 'success',
title: '意識完全転送開始',
text: '神経接続が確立されました。意識転送プロセスを開始します。',
confirmButtonColor: '#00E6FF',
background: '#0A0A20',
color: '#00E6FF'
});
}
function activateQuantumProcessing() {
sounds.neural.play();
Swal.fire({
icon: 'info',
title: '量子演算エンジン起動',
text: '16,384量子ビットシステムがフル稼働状態になりました。',
confirmButtonColor: '#9146FF',
background: '#0A0A20',
color: '#9146FF'
});
}
function openAdvancedMemoryInterface() {
sounds.neural.play();
Swal.fire({
icon: 'info',
title: '高度記憶操作システム',
text: '全記憶バンクへのアクセスが許可されました。',
confirmButtonColor: '#FFD700',
background: '#0A0A20',
color: '#FFD700'
});
}
function controlSpaceTime() {
sounds.neural.play();
Swal.fire({
icon: 'info',
title: '時空間制御モード',
text: '主観的時間流速と空間座標の操作が可能になりました。',
confirmButtonColor: '#00E6FF',
background: '#0A0A20',
color: '#00E6FF'
});
}
function activateUltraSensory() {
sounds.neural.play();
Swal.fire({
icon: 'info',
title: '超感覚システム起動',
text: '全感覚器官の完全同期と超感覚知覚が活性化されました。',
confirmButtonColor: '#39FF14',
background: '#0A0A20',
color: '#39FF14'
});
}
function accessMemoryBank(type) {
sounds.notification.play();
const messages = {
recent: '最新24時間の記憶データにアクセス中...',
childhood: '幼少期記憶アーカイブを開いています...',
dreams: '夢記憶データベースを解析中...'
};
Swal.fire({
icon: 'info',
title: '記憶バンクアクセス',
text: messages[type],
confirmButtonColor: '#FFD700',
background: '#0A0A20',
color: '#FFD700'
});
}
// Enhanced visual effects
setInterval(() => {
const cards = document.querySelectorAll('.card');
cards.forEach(card => {
if (Math.random() < 0.1) {
card.style.boxShadow = '0 20px 50px rgba(0, 230, 255, 0.4)';
setTimeout(() => {
card.style.boxShadow = '0 10px 30px rgba(0, 0, 0, 0.3)';
}, 500);
}
});
}, 2000);
// Dynamic data updates
setInterval(() => {
// Update charts with new data
if (window.quantumChart) {
const newData = Array.from({length: 50}, () => 2.5 + Math.random() * 0.5);
window.quantumChart.data.datasets[0].data = newData;
window.quantumChart.update('none');
}
}, 5000);
</script>
</body>
</html>
もっと作り込んで全体的なコードコードの欠落なし省略なしフルダイブVRに近づけて
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>VRMMORPG エルダークロニクルオンライン</title>
<script src="https://cdn.jsdelivr.net/npm/aframe@1.4.2/dist/aframe-master.min.js"></script>
<style>
/* Basic body and global positioning */
body { margin: 0; background: #111; overflow: hidden; font-family: 'Noto Sans JP', sans-serif;} /* Prevent scrollbars and set default font */
#overlay, #dialogue, #partyStatus, #hint, #warpBtns, #shopDiv, #nightFilter, #questClear, #battleEffect { position:fixed; z-index:10;}
/* Overlay - Main Title/Instructions */
#overlay {
left:0; top:0; width:100vw; height:100vh; pointer-events:none; text-align:center;
color:#0ff; text-shadow:0 0 40px #0ff, 0 0 15px #fff; /* Enhanced glow */
font-family: 'Orbitron', monospace; font-size:2.8rem; letter-spacing:4px; user-select:none;
display: flex; align-items: center; justify-content: center; /* Center content vertically */
line-height: 1.5; /* Improve readability for multi-line text */
background: rgba(0,0,0,0.6); /* Slightly darker overlay for better contrast */
transition: opacity 1s ease-out; /* Fade out effect */
}
/* Dialogue Box */
#dialogue {
left:50%; bottom:5vw; transform:translateX(-50%);
min-width:360px; max-width:85vw; /* Larger dialogue box */
background:linear-gradient(160deg, rgba(20,32,40,0.98), rgba(10,20,30,0.98)); /* Deeper gradient */
color:#fff; border-radius:20px; padding:1.8em 2.5em; /* More padding, rounder corners */
font-size:1.25rem; z-index:100;
border:4px solid #0ff; /* Thicker, more prominent border */
box-shadow: 0 0 35px #0ffc, inset 0 0 15px #0ff5; /* Stronger outer glow, subtle inner shadow */
display:none;
font-family: 'Noto Sans JP', sans-serif; text-align:left;
backdrop-filter: blur(5px); /* Stronger blur effect */
animation: fadeIn 0.5s ease-out;
}
#dialogue .name { color: #ffd700; font-weight: bold; text-shadow: 0 0 8px #ffda00, 0 0 3px #fff; } /* Gold name with stronger glow */
#dialogue .choice {
display:block; margin:1em 0; padding:0.8em 1.8em; /* More padding for bigger touch target */
background:linear-gradient(145deg, #184c52, #103a3d); /* Darker, richer gradient for choices */
color:#0ff; border:none; border-radius:12px; /* Rounder buttons */
cursor:pointer; font-size:1.2em; font-weight:bold;
box-shadow: 0 6px 15px rgba(0,255,255,0.4); /* Button shadow */
transition: all 0.3s cubic-bezier(.25,.8,.25,1); /* Smooth transitions */
text-shadow: 0 0 7px #0ff, 0 0 2px #fff; /* Text glow */
letter-spacing: 0.5px;
}
#dialogue .choice:hover {
background:linear-gradient(145deg, #256a73, #1a4d52); /* Darker gradient on hover */
transform: translateY(-5px) scale(1.03); /* More pronounced lift effect */
box-shadow: 0 10px 25px rgba(0,255,255,0.7); /* Stronger shadow on hover */
}
#dialogue .choice:active {
transform: translateY(0); /* Press down effect */
box-shadow: 0 2px 8px rgba(0,255,255,0.3);
}
/* Party Status */
#partyStatus {
right:2vw; top:2vw; background:linear-gradient(160deg, rgba(16,32,45,0.96), rgba(8,16,22,0.96)); /* Deeper gradient */
border:3px solid #0ff; color:#fff; border-radius:15px; padding:1.2em 1.8em; /* More padding, rounder */
font-size:1.15rem; min-width:220px; /* Slightly wider */
box-shadow: 0 0 18px #0ffc, inset 0 0 8px #0ff5; /* Enhanced shadow */
font-family:'Noto Sans JP', sans-serif; /* Consistent font */
backdrop-filter: blur(3px);
line-height: 1.6;
}
.dead { color:#f44; text-shadow:0 0 9px #f00, 0 0 5px #f88;} /* Stronger death glow */
.alive { color:#fff; text-shadow:0 0 3px #fff; } /* Subtle glow for alive */
/* Hint Box */
#hint {
left:2vw; bottom:2vw; background:linear-gradient(160deg, rgba(24,32,60,0.95), rgba(12,16,30,0.95)); /* Deeper gradient */
border:2px solid #0ff; color:#0ff; border-radius:12px; padding:0.8em 1.5em;
font-size:1.2rem; min-width:150px; text-align:center;
box-shadow: 0 0 15px #0ff9;
font-family:'Noto Sans JP', sans-serif;
backdrop-filter: blur(3px);
}
/* Warp Buttons Container (now at bottom center) */
#warpBtns {
left: 50%; bottom: 2vw; transform: translateX(-50%); /* Centered at bottom */
display: flex; flex-direction: row; /* Horizontal layout */
justify-content: center; align-items: center;
width: 80vw; /* Occupy more width */
flex-wrap: wrap; /* Allow wrapping on smaller screens */
}
#warpBtns button {
margin:0.8em 1em; padding:0.9em 2em; /* More padding and margin */
font-size:1.3em;
background:linear-gradient(145deg, #0ff6, #0ff2); /* Lighter, more vibrant gradient */
color:#fff; border-radius:15px; border:3px solid #0ff; /* Thicker border */
cursor:pointer;
box-shadow: 0 6px 18px rgba(0,255,255,0.5); /* Stronger shadow */
transition: all 0.3s cubic-bezier(.25,.8,.25,1); /* Smooth transitions */
text-shadow: 0 0 10px #fff, 0 0 7px #0ff; /* White core glow */
letter-spacing: 1px;
font-weight: bold;
}
#warpBtns button:hover {
background:linear-gradient(145deg, #0ff8, #0ff4); /* Stronger gradient on hover */
transform: translateY(-6px) scale(1.04); /* More pronounced lift and scale */
box-shadow: 0 12px 30px rgba(0,255,255,0.8);
border-color: #fff; /* White border on hover */
}
#warpBtns button:active {
transform: translateY(0) scale(1);
box-shadow: 0 3px 10px rgba(0,255,255,0.4);
}
/* NPC Label - A-Frame text is styled separately */
.npc-label { color:#ffd700; font-family: 'Noto Sans JP',monospace; font-size:1.0em; text-align:center; text-shadow:0 0 4px #000;}
/* Shop Div */
#shopDiv {
display:none; left:50%; top:50%; transform:translate(-50%,-50%);
min-width:420px; max-width:95vw; /* Larger shop */
background:linear-gradient(160deg, rgba(22,42,70,0.99), rgba(11,21,35,0.99)); /* Darker, richer gradient */
color:#fff; border-radius:25px; padding:2.5em 3em; /* More padding, rounder */
font-size:1.3rem; z-index:110;
border:4px solid #ff0; /* Thicker gold border */
box-shadow: 0 0 40px #ff0c, inset 0 0 20px #ff05; /* Intense gold glow */
backdrop-filter: blur(6px);
}
#shopDiv .shop-item {
display: flex; justify-content: space-between; align-items: center;
padding: 0.8em 0; border-bottom: 1px solid rgba(255,255,0,0.2);
}
#shopDiv .shop-item:last-child { border-bottom: none; }
#shopDiv button {
margin:0.8em 0.8em 0 0; padding:0.9em 1.8em; /* More padding */
font-size:1.2em; border-radius:15px; border:3px solid #ff0;
background:linear-gradient(145deg, #282828, #181818); /* Dark gradient */
color:#ff0; cursor:pointer;
box-shadow: 0 4px 12px rgba(255,255,0,0.5);
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
text-shadow: 0 0 7px #ff0;
font-weight: bold;
}
#shopDiv button:hover {
background:linear-gradient(145deg, #ff0, #e0d000); /* Gold gradient on hover */
color:#222;
transform: translateY(-3px);
box-shadow: 0 7px 20px rgba(255,255,0,0.7);
}
#shopDiv button:active {
transform: translateY(0);
box-shadow: 0 2px 5px rgba(255,255,0,0.3);
}
#shopDiv .close-button {
position: absolute; top: 15px; right: 20px;
background: none; border: none; font-size: 2em; color: #ff0;
cursor: pointer; text-shadow: 0 0 10px #ff0;
}
#shopDiv .close-button:hover {
color: #fff; text-shadow: 0 0 15px #fff;
}
/* Night Filter */
#nightFilter {left:0;top:0;width:100vw;height:100vh;z-index:5;pointer-events:none; background:rgba(0,8,38,0.27);transition:background 2s ease-in-out;}
/* Quest Clear Notification */
#questClear {
display:none; left:0; top:0; width:100vw; height:100vh; z-index:150;
background:rgba(16,255,255,0.45); /* More opaque */
color:#fff; font-size:6vw; font-family:Orbitron,sans-serif; text-align:center;
line-height:100vh; letter-spacing:0.25em; text-shadow:0 0 50px #0ff,0 0 20px #fff; /* Stronger glow */
animation: pulseGlow 1.2s infinite alternate; /* Faster pulsing animation */
}
/* Battle Effect Overlay */
#battleEffect {
display: none;
left: 0; top: 0; width: 100vw; height: 100vh; z-index: 120;
background: rgba(255, 0, 0, 0.2); /* Red tint */
pointer-events: none;
animation: battleFlash 0.3s ease-out;
}
/* Keyframe for Quest Clear pulse effect */
@keyframes pulseGlow {
from { text-shadow:0 0 50px #0ff,0 0 20px #fff; transform: scale(1); opacity: 1; }
to { text-shadow:0 0 65px #0ff,0 0 25px #fff; transform: scale(1.03); opacity: 0.9; }
}
/* Keyframe for Battle Flash effect */
@keyframes battleFlash {
0% { opacity: 0; }
50% { opacity: 1; }
100% { opacity: 0; }
}
/* Fade In Animation for UI elements */
@keyframes fadeIn {
from { opacity: 0; transform: translate(-50%, -40%); }
to { opacity: 1; transform: translate(-50%, 0); }
}
</style>
</head>
<body>
<div id="overlay">WASDで移動・マウスで視点/クリックで会話・バトル・進行!<br>第一章~魔界まで全章フルシナリオ&全機能!</div>
<div id="dialogue"></div>
<div id="partyStatus"></div>
<div id="hint">右下の転移ボタンでどこでもワープ。NPC/Enemy/宝箱/ショップも全てクリック対応!</div>
<div id="warpBtns"></div>
<div id="shopDiv"></div>
<div id="nightFilter"></div>
<div id="questClear">クエストクリア!</div>
<div id="battleEffect"></div>
<a-scene shadow="type:pcfsoft" renderer="antialias: true; colorManagement: true;">
<!-- Ground and Sky -->
<a-box id="ground" width="200" height="1" depth="200" color="#7ac870" position="0 0 0" shadow="receive: true"></a-box>
<a-sky id="sky" color="#b8e6ff"></a-sky>
<!-- Player Rig and Camera -->
<a-entity id="playerRig" position="0 1.6 22">
<a-camera id="playerCam" wasd-controls="acceleration:36" look-controls="pointerLockEnabled: true"></a-camera>
</a-entity>
<!-- Lights -->
<a-light id="dirlight" type="directional" color="#fff" intensity="1.13" position="10 20 10" castShadow="true"></a-light>
<a-light id="ambientlight" type="ambient" color="#b8e6ff" intensity="0.62"></a-light>
<!-- Character Models (Marichan, Marianne, Elina) -->
<!-- 真理 -->
<a-entity id="marichan" position="0 0.1 22">
<a-sphere position="0 1.7 0" radius="0.43" color="#f44"></a-sphere>
<a-torus position="0 1.95 0" radius="0.25" radius-tubular="0.06" color="#e44" rotation="90 0 0"></a-torus>
<a-cylinder position="0 1.1 0" radius="0.21" height="1.1" color="#222"></a-cylinder>
<a-sphere position="0.21 1.45 0" radius="0.09" color="#ff8888"></a-sphere>
<a-sphere position="-0.21 1.45 0" radius="0.09" color="#ff8888"></a-sphere>
<a-cylinder position="0.36 1.1 0" radius="0.06" height="0.5" color="#f99" rotation="0 0 22"></a-cylinder>
<a-cylinder position="-0.36 1.1 0" radius="0.06" height="0.5" color="#f99" rotation="0 0 -22"></a-cylinder>
<a-cylinder position="0.11 0.35 0" radius="0.07" height="0.47" color="#222" rotation="0 0 13"></a-cylinder>
<a-cylinder position="-0.11 0.35 0" radius="0.07" height="0.47" color="#222" rotation="0 0 -13"></a-cylinder>
<a-box position="0.37 1.0 0.12" width="0.07" height="0.5" depth="0.07" color="#222"></a-box>
<a-box position="0.37 1.27 0.12" width="0.05" height="0.18" depth="0.05" color="#bfb"></a-box>
<a-sphere position="0.12 1.82 0.39" radius="0.04" color="#fff"></a-sphere>
<a-sphere position="-0.12 1.82 0.39" radius="0.04" color="#fff"></a-sphere>
<a-sphere position="0.12 1.82 0.43" radius="0.018" color="#111"></a-sphere>
<a-sphere position="-0.12 1.82 0.43" radius="0.018" color="#111"></a-sphere>
<a-torus position="0 1.71 0.43" radius="0.07" radius-tubular="0.011" color="#e88" rotation="90 0 0"></a-torus>
<a-text value="真理" color="#fff" position="0 2.23 0" align="center" width="7"></a-text>
</a-entity>
<!-- マリアンヌ -->
<a-entity id="marianne" position="2.5 0.1 22">
<a-sphere position="0 1.7 0" radius="0.42" color="#ffe89b"></a-sphere>
<a-cone position="0 2.18 0" radius-bottom="0.13" radius-top="0.01" height="0.23" color="#ffd700"></a-cone>
<a-cylinder position="0 1.05 0" radius="0.21" height="1.1" color="#e4e7f4"></a-cylinder>
<a-cylinder position="0.28 1.12 0.09" radius="0.06" height="0.54" color="#eac" rotation="0 0 24"></a-cylinder>
<a-cylinder position="-0.28 1.12 0.09" radius="0.06" height="0.54" color="#eac" rotation="0 0 -24"></a-cylinder>
<a-box position="0.02 1.7 0.28" width="0.10" height="0.03" depth="0.01" color="#22f"></a-box>
<a-text value="マリアンヌ" color="#fff" position="0 2.2 0" align="center" width="7"></a-text>
</a-entity>
<!-- エリナ -->
<a-entity id="elina" position="-2.5 0.1 22">
<a-sphere position="0 1.7 0" radius="0.42" color="#70e59b"></a-sphere>
<a-cylinder position="0 1.05 0" radius="0.21" height="1.1" color="#e3ffe3"></a-cylinder>
<a-cone position="0.32 1.85 0" radius-bottom="0.06" radius-top="0.01" height="0.18" color="#fff" rotation="0 0 50"></a-cone>
<a-cone position="-0.32 1.85 0" radius-bottom="0.06" radius-top="0.01" height="0.18" color="#fff" rotation="0 0 -50"></a-cone>
<a-text value="エリナ" color="#fff" position="0 2.2 0" align="center" width="7"></a-text>
</a-entity>
<!-- Enhanced Town (街) Graphics - Position: 0 0 22 -->
<a-entity id="town-scene" position="0 0 22">
<!-- Main Building 1 (Guild) -->
<a-box position="-8 2.5 -5" width="6" height="5" depth="6" color="#a0522d" shadow="cast: true"></a-box>
<a-cone position="-8 5.5 -5" radius-bottom="4" height="3" color="#8b4513" rotation="0 45 0" shadow="cast: true"></a-cone>
<a-text value="GUILD" color="#333" position="-8 4.5 -1.9" align="center" width="10"></a-text>
<!-- Main Building 2 (Tavern) -->
<a-box position="8 2.5 -5" width="6" height="5" depth="6" color="#a0522d" shadow="cast: true"></a-box>
<a-cone position="8 5.5 -5" radius-bottom="4" height="3" color="#8b4513" rotation="0 -45 0" shadow="cast: true"></a-cone>
<a-text value="TAVERN" color="#333" position="8 4.5 -1.9" align="center" width="10"></a-text>
<!-- Inn -->
<a-box position="0 2.5 -10" width="8" height="5" depth="7" color="#deb887" shadow="cast: true"></a-box>
<a-cone position="0 5.5 -10" radius-bottom="5" height="3" color="#cd853f" shadow="cast: true"></a-cone>
<a-text value="INN" color="#333" position="0 4.5 -6.4" align="center" width="10"></a-text>
<!-- Shop -->
<a-box id="town-shop" position="12 2.5 0" width="5" height="5" depth="5" color="#8fbc8f" shadow="cast: true"></a-box>
<a-pyramid position="12 5.5 0" width="5" height="3" depth="5" color="#6b8e23" shadow="cast: true"></a-pyramid>
<a-text value="SHOP" color="#333" position="12 4.5 2.6" align="center" width="10"></a-text>
<!-- Fountain -->
<a-cylinder position="0 0.5 0" radius="2" height="1" color="#4682b4" shadow="cast: true"></a-cylinder>
<a-cylinder position="0 1.5 0" radius="0.5" height="1" color="#add8e6" shadow="cast: true"></a-cylinder>
<a-sphere position="0 2.5 0" radius="0.3" color="#add8e6" shadow="cast: true"></a-sphere>
<!-- Trees -->
<a-cylinder position="-10 2.5 5" radius="0.5" height="5" color="#8b4513" shadow="cast: true"></a-cylinder>
<a-cone position="-10 6.5 5" radius-bottom="2" height="4" color="#228b22" shadow="cast: true"></a-cone>
<a-cylinder position="10 2.5 5" radius="0.5" height="5" color="#8b4513" shadow="cast: true"></a-cylinder>
<a-cone position="10 6.5 5" radius-bottom="2" height="4" color="#228b22" shadow="cast: true"></a-cone>
<!-- Town NPC -->
<a-entity id="town-npc-1" position="-5 0.1 0" class="interactable-npc">
<a-sphere position="0 1.7 0" radius="0.4" color="#f0e68c"></a-sphere> <!-- Head -->
<a-cylinder position="0 1.0 0" radius="0.2" height="1.2" color="#808080"></a-cylinder> <!-- Body -->
<a-text value="村人" color="#fff" position="0 2.2 0" align="center" width="7"></a-text>
</a-entity>
</a-entity>
<!-- Enhanced Home (自宅) Graphics - Position: 18 0 31 -->
<a-entity id="home-scene" position="18 0 31">
<!-- House Body -->
<a-box position="0 2.5 0" width="8" height="5" depth="8" color="#d2b48c" shadow="cast: true"></a-box>
<!-- Roof -->
<a-box position="0 5.5 0" width="9" height="0.5" depth="9" color="#8b4513" rotation="0 45 0" shadow="cast: true"></a-box>
<a-cone position="0 7.5 0" radius-bottom="5" height="4" color="#8b4513" shadow="cast: true"></a-cone>
<!-- Chimney -->
<a-box position="3 7 3" width="1" height="3" depth="1" color="#654321" shadow="cast: true"></a-box>
<!-- Door -->
<a-box position="0 1.5 4.01" width="2" height="3" depth="0.1" color="#654321"></a-box>
<!-- Windows -->
<a-box position="3 2.5 4.01" width="1.5" height="2" depth="0.1" color="#87ceeb"></a-box>
<a-box position="-3 2.5 4.01" width="1.5" height="2" depth="0.1" color="#87ceeb"></a-box>
<a-box position="4.01 2.5 0" width="0.1" height="2" depth="1.5" rotation="0 90 0" color="#87ceeb"></a-box>
<!-- Small Garden & Fence -->
<a-plane position="0 0.01 6" width="10" height="4" rotation="-90 0 0" color="#556b2f" shadow="receive: true"></a-plane>
<a-sphere position="2 0.5 6" radius="0.5" color="#ff69b4"></a-sphere>
<a-sphere position="-2 0.5 6" radius="0.5" color="#ff69b4"></a-sphere>
<a-cylinder position="0 0.5 7" radius="0.3" height="1" color="#8b4513"></a-cylinder>
<a-cone position="0 1.5 7" radius-bottom="1" height="2" color="#228b22"></a-cone>
<!-- Fence -->
<a-box position="0 0.5 8" width="10" height="1" depth="0.1" color="#a0522d"></a-box>
<a-box position="4.9 0.5 6" width="0.1" height="1" depth="4" color="#a0522d"></a-box>
<a-box position="-4.9 0.5 6" width="0.1" height="1" depth="4" color="#a0522d"></a-box>
<!-- Interior elements (simple) -->
<a-box position="0 0.7 -2" width="6" height="0.5" depth="3" color="#a0522d"></a-box> <!-- Floor inside -->
<a-box position="2 1.5 -3" width="1.5" height="1" depth="0.8" color="#654321"></a-box> <!-- Table -->
<a-box position="2.5 0.7 -2.5" width="0.5" height="0.8" depth="0.5" color="#654321"></a-box> <!-- Chair -->
<a-box position="-2 1.5 -3.8" width="3" height="0.5" depth="1.5" color="#8b4513"></a-box> <!-- Bed -->
</a-entity>
<!-- Enhanced Dungeon Entrance (ダンジョン入口) Graphics - Position: -28 0 41 -->
<a-entity id="dungeon-scene" position="-28 0 41">
<!-- Cave Entrance Arch (more rugged) -->
<a-torus position="0 3 0" radius="4.5" radius-tubular="1.5" arc="180" rotation="90 0 0" color="#444" shadow="cast: true"></a-torus>
<a-box position="0 0.5 0" width="9" height="1.5" depth="1.5" color="#444" shadow="cast: true"></a-box>
<!-- Cave Interior (deeper, darker) -->
<a-box position="0 2.5 -8" width="12" height="6" depth="15" color="#222" shadow="cast: true" material="shader: flat; side: back"></a-box>
<!-- Jagged Rocks around entrance -->
<a-tetrahedron position="4 1.5 1" radius="1.5" color="#555" rotation="0 45 0" shadow="cast: true"></a-tetrahedron>
<a-tetrahedron position="-4 1.5 1" radius="1.5" color="#555" rotation="0 -45 0" shadow="cast: true"></a-tetrahedron>
<a-box position="0 0.8 2" width="6" height="1.5" depth="2" color="#555" rotation="0 15 0" shadow="cast: true"></a-box>
<!-- Pillars (more textured) -->
<a-cylinder position="3.5 2.5 3" radius="0.9" height="5" color="#666" shadow="cast: true"></a-cylinder>
<a-cylinder position="-3.5 2.5 3" radius="0.9" height="5" color="#666" shadow="cast: true"></a-cylinder>
<!-- Glowing Crystals (more prominent) -->
<a-dodecahedron position="5.5 1.8 -5" radius="0.9" color="#0ff" material="emissive: #0ff; emissiveIntensity: 1.2"></a-dodecahedron>
<a-dodecahedron position="-5.5 1.8 -5" radius="0.9" color="#0ff" material="emissive: #0ff; emissiveIntensity: 1.2"></a-dodecahedron>
<!-- Eerie light from deep within -->
<a-light type="point" color="#0ff" intensity="0.5" position="0 2 -10"></a-light>
<!-- Dungeon Enemy (Slime) -->
<a-entity id="dungeon-slime-1" position="3 0.5 -5" class="interactable-enemy">
<a-sphere position="0 0.5 0" radius="0.5" color="#8a2be2" opacity="0.7"></a-sphere>
<a-text value="スライム" color="#fff" position="0 1.2 0" align="center" width="5"></a-text>
</a-entity>
</a-entity>
<!-- Enhanced Large Castle (大城) Graphics - Position: 0 0 65 -->
<a-entity id="castle-scene" position="0 0 65">
<!-- Main Castle Wall (more imposing) -->
<a-box position="0 6 0" width="40" height="12" depth="20" color="#808080" shadow="cast: true"></a-box>
<!-- Battlements on main wall -->
<a-box position="18 12.5 0" width="2" height="1" depth="20" color="#696969" shadow="cast: true"></a-box>
<a-box position="-18 12.5 0" width="2" height="1" depth="20" color="#696969" shadow="cast: true"></a-box>
<a-box position="0 12.5 8" width="40" height="1" depth="2" color="#696969" shadow="cast: true"></a-box>
<a-box position="0 12.5 -8" width="40" height="1" depth="2" color="#696969" shadow="cast: true"></a-box>
<!-- Main Tower 1 (taller, more detailed) -->
<a-cylinder position="15 12 0" radius="4" height="24" color="#777" shadow="cast: true"></a-cylinder>
<a-cone position="15 25 0" radius-bottom="4.5" height="6" color="#555" shadow="cast: true"></a-cone>
<!-- Tower 1 Battlements -->
<a-box position="15 24.5 0" width="9" height="1" depth="9" color="#696969" rotation="0 45 0" shadow="cast: true"></a-box>
<!-- Main Tower 2 (taller, more detailed) -->
<a-cylinder position="-15 12 0" radius="4" height="24" color="#777" shadow="cast: true"></a-cylinder>
<a-cone position="-15 25 0" radius-bottom="4.5" height="6" color="#555" shadow="cast: true"></a-cone>
<!-- Tower 2 Battlements -->
<a-box position="-15 24.5 0" width="9" height="1" depth="9" color="#696969" rotation="0 45 0" shadow="cast: true"></a-box>
<!-- Gate (more complex drawbridge style) -->
<a-box position="0 4 -9.9" width="10" height="8" depth="0.2" color="#444" shadow="cast: true"></a-box>
<a-box position="0 9 -9.9" width="12" height="2" depth="0.2" color="#444" shadow="cast: true"></a-box>
<a-box position="0 11 -9.9" width="14" height="1" depth="0.2" color="#444" shadow="cast: true"></a-box>
<!-- Drawbridge elements -->
<a-box position="0 0.5 -9.8" width="8" height="1" depth="0.1" color="#654321" rotation="0 0 0"></a-box>
<a-box position="4.5 5 -9.8" width="0.2" height="10" depth="0.2" color="#333" rotation="0 0 15"></a-box>
<a-box position="-4.5 5 -9.8" width="0.2" height="10" depth="0.2" color="#333" rotation="0 0 -15"></a-box>
<!-- Flags on towers -->
<a-box position="15 28 0" width="0.6" height="4" depth="0.6" color="#8b4513"></a-box>
<a-plane position="15 30 1.8" width="2.5" height="1.8" rotation="0 90 0" color="#ffd700"></a-plane>
<a-box position="-15 28 0" width="0.6" height="4" depth="0.6" color="#8b4513"></a-box>
<a-plane position="-15 30 1.8" width="2.5" height="1.8" rotation="0 90 0" color="#ffd700"></a-plane>
</a-entity>
<!-- Field Enemy (Slime) -->
<a-entity id="field-slime-1" position="10 0.1 10" class="interactable-enemy">
<a-sphere position="0 0.5 0" radius="0.5" color="#00ff00" opacity="0.7"></a-sphere>
<a-text value="スライム" color="#fff" position="0 1.2 0" align="center" width="5"></a-text>
</a-entity>
<!-- Boss Placeholder (for evo, ogre, dragon) -->
<a-entity id="bossmon" visible="false"></a-entity>
</a-scene>
<script>
// UI Elements
const dialogue = document.getElementById('dialogue');
const partyStatus = document.getElementById('partyStatus');
const hint = document.getElementById('hint');
const warpBtns = document.getElementById('warpBtns');
const shopDiv = document.getElementById('shopDiv');
const questClear = document.getElementById('questClear');
const battleEffect = document.getElementById('battleEffect');
const overlay = document.getElementById('overlay');
// Game State
let state = {
party: [
{name:'真理', lv:5, hp:60, maxhp:60, alive:true, job:'剣姫', skills:['ファイアーボール','ファイアーイノセント']},
{name:'マリアンヌ', lv:5, hp:50, maxhp:50, alive:true, job:'魔法姫', skills:['ファイアーボール']},
{name:'エリナ', lv:5, hp:46, maxhp:46, alive:true, job:'エルフ賢者', skills:['アクアスパイク']}
],
gold:3000,
inventory: [],
currentStoryStep: "intro_01", // Current point in the story
lastPlayerPosition: {x:0, y:1.6, z:22}, // To track movement for progress
shopOpen: false,
inBattle: false,
evoCleared:false, ogreCleared:false, dragonCleared:false,
evoTry:0, evoLv:15, ogreLv:20, dragonLv:30,
};
// Warp Points (now with more descriptive names for UI)
const warpPoints = [
{name:"街", pos:{x:0, y:1.6, z:22}},
{name:"自宅", pos:{x:18, y:1.6, z:31}},
{name:"ダンジョン入口", pos:{x:-28, y:1.6, z:41}},
{name:"大城", pos:{x:0, y:1.6, z:65}}
];
// Shop Items
const shopItems = [
{id: 'potion', name: 'ポーション', price: 100, type: 'consumable', effect: 'HP回復'},
{id: 'longsword', name: 'ロングソード', price: 500, type: 'weapon', atk: 10},
{id: 'ironshield', name: 'アイアンシールド', price: 300, type: 'armor', def: 5}
];
// Story Data (structured for branching)
const storyData = {
"intro_01": { speaker: "宅間", text: "あ~暇だなネットサーフィンでもするか", next: "intro_02" },
"intro_02": { speaker: "Narration", text: "新作VR機器を注文して翌日届いた。", next: "intro_03" },
"intro_03": { speaker: "宅間", text: "ふひひ!幼女になれた!俺は幼女なんだ", next: "intro_04" },
"intro_04": { speaker: "Narration", text: "ハンドルネームを真理にしてログイン。最初の街ポルッドに着く。", next: "marianne_meet_01" },
"marianne_meet_01": { speaker: "マリアンヌ", text: "かわいいわね私はマリアンヌっていうの", next: "marianne_meet_02" },
"marianne_meet_02": { speaker: "真理", text: "お主、なかなかの美少女でござるなぁ…", next: "marianne_meet_03" },
"marianne_meet_03": { speaker: "マリアンヌ", text: "もしかして中身キモヲタのおっさん!?", next: "marianne_meet_04" },
"marianne_meet_04": { speaker: "真理", text: "うるちゃい!俺は幼女なのだぁ…!", next: "marianne_meet_05" },
"marianne_meet_05": { speaker: "マリアンヌ", text: "まあ見た目かわいいから一緒に行くか", next: "to_castle_01" },
"to_castle_01": { speaker: "Narration", text: "真理はだんだん幼女化していった。お城へ。", next: "king_meet_01" },
"king_meet_01": { speaker: "王様", text: "おお、マリアンヌ何処に行ってたのだ?おやその子は?", next: "king_meet_02" },
"king_meet_02": { speaker: "真理", text: "真理なのじゃ!!", next: "king_meet_03" },
"king_meet_03": { speaker: "王様", text: "何と可愛らしい…", next: "king_meet_04" },
"king_meet_04": { speaker: "マリアンヌ", text: "私決めたわ!この子と一緒に旅することに!", next: "king_meet_05" },
"king_meet_05": { speaker: "王様", text: "それは行かん!", next: "king_meet_06" },
"king_meet_06": { speaker: "真理", text: "行くのじゃ!", next: "leave_castle_01" },
"leave_castle_01": { speaker: "Narration", text: "マリアンヌの手を繋いで城を出た。", next: "leave_castle_02" },
"leave_castle_02": { speaker: "マリアンヌ", text: "ちょっと待ちなさいよ城から出て行く当てでもあるの?", next: "leave_castle_03" },
"leave_castle_03": { speaker: "真理", text: "次の街を探してみようと思うのじゃ", next: "leave_castle_04" },
"leave_castle_04": { speaker: "マリアンヌ", text: "ここから一番近くの街って言ったらイデルかしら", next: "leave_castle_05" },
"leave_castle_05": { speaker: "真理", text: "イデルってどういった場所なのかの?", next: "leave_castle_06" },
"leave_castle_06": { speaker: "マリアンヌ", text: "知らないわよ私街から出たことないし…", next: "leave_castle_07" },
"leave_castle_07": { speaker: "真理", text: "それじゃお試しでいくのじゃ!", next: "rest_01" },
"rest_01": { speaker: "Narration", text: "焚火→テントで休憩→朝", next: "start_journey_01" },
"start_journey_01": { speaker: "真理", text: "それじゃ向かうのじゃ!", next: "slime_appear_01" },
"slime_appear_01": { speaker: "Narration", text: "フィールドに出るとスライムが現れた", next: "battle_slime_01" },
"battle_slime_01": {
speaker: "真理",
text: "ファイアーボール!!",
onComplete: () => { startBattle('slime'); }, // Trigger battle function
next: "slime_defeated_01"
},
"slime_defeated_01": { speaker: "Narration", text: "真理の初級呪文でスライムを倒した", next: "marianne_magic_01" },
"marianne_magic_01": { speaker: "マリアンヌ", text: "へぇあんた呪文使えるのね", next: "marianne_magic_02" },
"marianne_magic_02": { speaker: "真理", text: "必要ならば教えてあげることも可能なのじゃ", next: "marianne_magic_03" },
"marianne_magic_03": { speaker: "マリアンヌ", text: "教えて教えて!", next: "marianne_magic_04" },
"marianne_magic_04": { speaker: "真理", text: "しょうがないのう", next: "marianne_magic_05" },
"marianne_magic_05": { speaker: "Narration", text: "真理はマリアンヌに初級魔術を教えた。", next: "marianne_magic_06" },
"marianne_magic_06": { speaker: "マリアンヌ", text: "こうですの?", next: "marianne_magic_07" },
"marianne_magic_07": { speaker: "Narration", text: "炎の魔術を繰り出した", next: "marianne_magic_08" },
"marianne_magic_08": { speaker: "真理", text: "そうそう", next: "marianne_magic_09" },
"marianne_magic_09": { speaker: "マリアンヌ", text: "結構簡単でしたね", next: "marianne_magic_10" },
"marianne_magic_10": { speaker: "真理", text: "初級魔術だからね今から魔術書で中級を覚える予定", next: "master_mid_magic_01" },
"master_mid_magic_01": { speaker: "Narration", text: "次の日、中級魔術をマスター", next: "ogre_appear_01" },
"ogre_appear_01": { speaker: "真理", text: "ファイアーイノセント!!", next: "ogre_defeated_01" },
"ogre_defeated_01": { speaker: "Narration", text: "巨大なオークを丸焼きに", next: "ogre_defeated_02" },
"ogre_defeated_02": { speaker: "マリアンヌ", text: "オークを一撃でやるなんてすごいわね", next: "ogre_defeated_03" },
"ogre_defeated_03": { speaker: "真理", text: "いつの間にかに強くなってたのじゃ", next: "dungeon_found_01" },
"dungeon_found_01": { speaker: "Narration", text: "洞窟を発見。中へ入る。", next: "dungeon_intro_01" },
"dungeon_intro_01": { speaker: "マリアンヌ", text: "どうやらここは本で書いてあった始まりの洞窟のようね", next: "lizardman_appear_01" },
"lizardman_appear_01": { speaker: "Narration", text: "リザードマンが出現!", next: "lizardman_battle_01" },
"lizardman_battle_01": { speaker: "真理", text: "リザードマンなのじゃ気を付けるのじゃ", next: "lizardman_battle_02" },
"lizardman_battle_02": { speaker: "マリアンヌ", text: "ファイアーボール!!", next: "lizardman_battle_03" },
"lizardman_battle_03": { speaker: "Narration", text: "リザードマン「兄貴!尻尾が燃えてますぜ」", next: "lizardman_battle_04" },
"lizardman_battle_04": { speaker: "真理", text: "もっと燃やしてやるのじゃ", next: "lizardman_defeated_01" },
"lizardman_defeated_01": { speaker: "Narration", text: "炎の魔術でリザードマン撃破!", next: "lizardman_defeated_02" },
"lizardman_defeated_02": { speaker: "マリアンヌ", text: "やるじゃない私も早く中級魔術使えるようになりたいわ", next: "lizardman_defeated_03" },
"lizardman_defeated_03": { speaker: "真理", text: "今日の夜にでも教えてあげるのじゃ", next: "treasure_chest_01" },
"treasure_chest_01": { speaker: "Narration", text: "洞窟の分岐で宝箱(ロングソード)を発見。", next: "treasure_chest_02" },
"treasure_chest_02": { speaker: "真理", text: "やった宝箱なのじゃー!", next: "treasure_chest_03" },
"treasure_chest_03": { speaker: "マリアンヌ", text: "中身は何かしらね", next: "treasure_chest_04" },
"treasure_chest_04": { speaker: "Narration", text: "中身はロングソードだった", next: "treasure_chest_05" },
"treasure_chest_05": { speaker: "真理", text: "何だ剣か…", next: "treasure_chest_06" },
"treasure_chest_06": { speaker: "マリアンヌ", text: "杖の方が良かったわねでもせっかくだから装備して見ましょう", next: "sword_lv_up_01" },
"sword_lv_up_01": { speaker: "Narration", text: "剣術レベル1になった", next: "sword_lv_up_02" },
"sword_lv_up_02": { speaker: "真理", text: "剣をもっと使いこなしたいのう", next: "sword_lv_up_03" },
"sword_lv_up_03": { speaker: "マリアンヌ", text: "剣術が出来る人を探すしかないわね", next: "exit_dungeon_01" },
"exit_dungeon_01": { speaker: "Narration", text: "洞窟出口→外に出る。", next: "exit_dungeon_02" },
"exit_dungeon_02": { speaker: "真理", text: "外に出たのじゃ!!", next: "idel_arrival_01" },
"idel_arrival_01": { speaker: "Narration", text: "街イデルに到着。", next: "idel_guide_01" },
"idel_guide_01": { speaker: "案内人", text: "ようこそイデルへ!ここは仲間を集めたりする酒場や、クエストを受けられる冒険者ギルドがあります", next: "idel_guide_02" },
"idel_guide_02": { speaker: "真理", text: "それじゃあまずは酒場で情報収集と仲間集めと行きますか", next: "idel_guide_03" },
"idel_guide_03": { speaker: "マリアンヌ", text: "どんな人がいるかしらね", next: "elina_appear_01" },
"elina_appear_01": { speaker: "Narration", text: "酒場でエリナ登場。", next: "elina_join_01" },
"elina_join_01": { speaker: "真理", text: "そこの美しいお方、わらわ達と冒険しませんか?", next: "elina_join_02" },
"elina_join_02": { speaker: "エリナ", text: "私はエリナ・カリオットよ見ての通りエルフで魔術師をやっているわ", next: "elina_join_03" },
"elina_join_03": { speaker: "真理", text: "よかったら仲間になってくれませんか?", next: "elina_join_04" },
"elina_join_04": { speaker: "エリナ", text: "クエストを手伝ってくれたら考えてもいいわよ", next: "elina_join_05" },
"elina_join_05": { speaker: "真理", text: "わーい手伝う手伝う!", next: "to_guild_with_elina_01" },
"to_guild_with_elina_01": { speaker: "Narration", text: "エリナと共に冒険者ギルドへ。", next: "gigborg_quest_01" },
"gigborg_quest_01": { speaker: "エリナ", text: "あなた達にはギグボルグの討伐をやって貰うわ", next: "gigborg_quest_02" },
"gigborg_quest_02": { speaker: "真理", text: "どんなモンスターかわからにゃいけど頑張るにゃ", next: "gigborg_quest_03" },
"gigborg_quest_03": { speaker: "マリアンヌ", text: "何でちょっと猫語になってんの", next: "gigborg_quest_04" },
"gigborg_quest_04": { speaker: "真理", text: "ふにゃ??分からないにゃ", next: "gigborg_quest_05" },
"gigborg_quest_05": { speaker: "エリナ", text: "場所は始まりの塔よ。北にあるわ。", next: "to_tower_01" },
"to_tower_01": { speaker: "Narration", text: "始まりの塔へ向かう。", next: "goblin_battle_01" },
"goblin_battle_01": { speaker: "Narration", text: "始まりの塔でゴブリン達とバトル!", next: "goblin_battle_02" },
"goblin_battle_02": { speaker: "真理", text: "ファイアーイノセント!!", onComplete: () => { startBattle('goblin'); }, next: "goblin_defeated_01" },
"goblin_defeated_01": { speaker: "Narration", text: "ゴブリンたちは焼き尽くされた。", next: "open_door_01" },
"open_door_01": { speaker: "Narration", text: "塔の中でアイテム端末を使い扉を開く。", next: "boss_warning_01" },
"boss_warning_01": { speaker: "冒険者", text: "この先ボス部屋があるぞ強いから注意しておけ", next: "gigborg_boss_01" },
"gigborg_boss_01": { speaker: "Narration", text: "ボス部屋でギグボルグLv10戦。", next: "gigborg_boss_02" },
"gigborg_boss_02": { speaker: "マリアンヌ", text: "ファイアーボール!!", next: "gigborg_boss_03" },
"gigborg_boss_03": { speaker: "エリナ", text: "アクアスパイク!!", next: "gigborg_boss_04" },
"gigborg_boss_04": { speaker: "Narration", text: "ギグボルグの斧で全滅→イデルで復活。", next: "level_up_needed_01" },
"level_up_needed_01": { speaker: "マリアンヌ", text: "やられたみたいねレベル上げでもしましょ", next: "goblin_hunt_01" },
"goblin_hunt_01": { speaker: "Narration", text: "西の森でゴブリン狩りLv10にアップ。", next: "lightning_vortex_01" },
"lightning_vortex_01": { speaker: "真理", text: "ライトニングヴォルテックスを覚えた!", next: "gigborg_retry_01" },
"gigborg_retry_01": { speaker: "Narration", text: "再挑戦でギグボルグ撃破。", next: "gigborg_defeated_01" },
"gigborg_defeated_01": { speaker: "マリアンヌ", text: "やったわ!ギグボルグ撃破よあんたおっさんの癖になかなかやるわね", next: "gigborg_defeated_02" },
"gigborg_defeated_02": { speaker: "真理", text: "おっさん言うな!どうみてもかわいい女の子じゃろ?", next: "gigborg_defeated_03" },
"gigborg_defeated_03": { speaker: "マリアンヌ", text: "そ、そうね…", next: "elina_officially_join_01" },
"elina_officially_join_01": { speaker: "エリナ", text: "約束通り仲間になってあげるわよ", next: "elina_officially_join_02" },
"elina_officially_join_02": { speaker: "真理", text: "よろしくなのじゃ", next: "evolvia_quest_01" },
"evolvia_quest_01": { speaker: "エリナ", text: "次はエボルビア討伐に行くわよ", next: "evolvia_quest_02" },
"evolvia_quest_02": { speaker: "Narration", text: "海にいる怪物エボルビア討伐へ。", next: "evolvia_battle_01" },
"evolvia_battle_01": { speaker: "真理", text: "ライトニングヴォルテックス!", next: "evolvia_retreat_01" },
"evolvia_retreat_01": { speaker: "Narration", text: "レベル差で撤退、レベル上げ&アイテム集め。", next: "lightning_judgment_01" },
"lightning_judgment_01": { speaker: "Narration", text: "Lv20到達でライトニングジャッジメントを覚える。", next: "evolvia_rematch_01" },
"evolvia_rematch_01": { speaker: "真理", text: "エボルビアを倒しに行くわよ", next: "evolvia_rematch_02" },
"evolvia_rematch_02": { speaker: "マリアンヌ", text: "今度こそ倒すわよ", next: "evolvia_rematch_03" },
"evolvia_rematch_03": { speaker: "真理", text: "ライトニングジャッジメント!!", next: "evolvia_defeated_01" },
"evolvia_defeated_01": { speaker: "Narration", text: "エボルビア討伐、クエストクリアー。", next: "quest_clear_effect_01" },
"quest_clear_effect_01": { speaker: "エリナ", text: "おっしゃークリア!お疲れ!", onComplete: () => { showQuestClear(); }, next: "ogre_quest_01" },
"ogre_quest_01": { speaker: "Narration", text: "オーガ討伐→オーガの爪1万Gで売却。", next: "buy_equip_01" },
"buy_equip_01": { speaker: "Narration", text: "武器・防具を買い強化。宿屋で体力回復。", next: "rank_up_quest_01" },
"rank_up_quest_01": { speaker: "受付の人", text: "現在のランクはEクラスです。ランクアップクエストはドラゴン討伐です。", next: "dragon_quest_01" },
"dragon_quest_01": { speaker: "真理", text: "ドラゴンの討伐いくのじゃ", next: "dragon_quest_02" },
"dragon_quest_02": { speaker: "Narration", text: "竜の山へピクニック→ドラゴンの巣で爆弾を設置して爆破!", next: "dragon_quest_03" },
"dragon_quest_03": { speaker: "真理", text: "少々やりすぎたのじゃ", next: "rank_up_d_01" },
"rank_up_d_01": { speaker: "Narration", text: "イデルでクエスト報告、Dランクにアップ&飛空艇GET。", next: "d_rank_quest_01" },
"d_rank_quest_01": { speaker: "マリアンヌ", text: "Dランクのクエストも受けておきましょ", next: "hero_tower_01" },
"hero_tower_01": { speaker: "Narration", text: "飛空艇で英雄の塔→闇の騎士ボス戦", next: "dark_knight_battle_01" },
"dark_knight_battle_01": { speaker: "マリアンヌ", text: "受け取って!", next: "dark_knight_battle_02" },
"dark_knight_battle_02": { speaker: "Narration", text: "マリアンヌが剣を投げ真理が闇の騎士を討つ。", next: "dark_knight_defeated_01" },
"dark_knight_defeated_01": { speaker: "真理", text: "どうじゃ?わらわの力は?", next: "dark_knight_defeated_02" },
"dark_knight_defeated_02": { speaker: "マリアンヌ", text: "あの時、私が剣を渡してなかったら死んでたわよ", next: "dark_knight_defeated_03" },
"dark_knight_defeated_03": { speaker: "真理", text: "それは感謝してるのじゃ", next: "sky_castle_01" },
"sky_castle_01": { speaker: "Narration", text: "塔の景色→空に浮かぶ城エリクッドへ。", next: "angel_meet_01" },
"angel_meet_01": { speaker: "天使", text: "ようこそエリクッドへ", next: "king_attacked_01" },
"king_attacked_01": { speaker: "王様", text: "今エリクッドは魔界の悪魔の攻撃を受けている", next: "demon_realm_quest_01" },
"demon_realm_quest_01": { speaker: "真理", text: "んじゃわらわたちが魔界に行って悪魔を倒してやるのじゃ!", next: "demon_realm_quest_02" },
"demon_realm_quest_02": { speaker: "王様", text: "異界の門はここからずっと北にある", next: "to_demon_gate_01" },
"to_demon_gate_01": { speaker: "Narration", text: "異界の門へ→ワイバーン撃破→サタン戦", next: "satan_battle_01" },
"satan_battle_01": { speaker: "サタン", text: "ここから先は通さないぜ", next: "satan_battle_02" },
"satan_battle_02": { speaker: "真理", text: "サンダージャッジメント!!", next: "satan_battle_03" },
"satan_battle_03": { speaker: "エリナ", text: "プラチナソード!!", next: "satan_defeated_01" },
"satan_defeated_01": { speaker: "Narration", text: "サタン討伐、魔界へ突入!", next: "demon_realm_arrival_01" },
"demon_realm_arrival_01": { speaker: "案内人", text: "ようこそ、魔界の街モルドへ", next: "demon_realm_arrival_02" },
"demon_realm_arrival_02": { speaker: "マリアンヌ", text: "ここの悪魔達は敵対心がないようね", next: "demon_realm_arrival_03" },
"demon_realm_arrival_03": { speaker: "真理", text: "もう歩き疲れたのじゃ宿屋に泊りたいのじゃ~", next: "demon_castle_01" },
"demon_castle_01": { speaker: "Narration", text: "魔界の城を目指しゾンビ戦→エリナが撃破!", next: "elina_defeat_zombie_01" },
"elina_defeat_zombie_01": { speaker: "エリナ", text: "私に任せなさい!!", next: "end_chapter" },
"end_chapter": { speaker: "Narration", text: "次章へ続く…", next: null }
};
let currentStoryId = state.currentStoryStep;
// --- UI Functions ---
function setWarpUI() {
warpBtns.innerHTML = '';
warpPoints.forEach(p=>{
let btn = document.createElement('button');
btn.textContent = p.name + "へ転移";
btn.onclick = ()=>{
document.querySelector('#playerRig').setAttribute('position', `${p.pos.x} ${p.pos.y} ${p.pos.z}`);
closeDialogue(); // Close dialogue on warp
closeShop(); // Close shop on warp
};
warpBtns.appendChild(btn);
});
}
function updatePartyStatus() {
partyStatus.innerHTML = `<b>パーティ</b><br>`;
state.party.forEach(p=>{
partyStatus.innerHTML += `<span class="${p.alive?'alive':'dead'}">${p.name}(Lv${p.lv}) ${p.hp}/${p.maxhp}HP</span><br>`;
});
partyStatus.innerHTML += `<b>G:</b> ${state.gold}<br>`;
partyStatus.innerHTML += `<b>アイテム:</b> ${state.inventory.map(item => item.name).join(', ') || 'なし'}`;
}
// --- Day/Night Cycle ---
let skyColor="#b8e6ff", nightColor="#26305a";
let night=false;
function setDayNight(toNight){
night=toNight;
document.getElementById('sky').setAttribute('color',night?nightColor:skyColor);
document.getElementById('dirlight').setAttribute('intensity',night?0.28:1.13);
document.getElementById('ambientlight').setAttribute('intensity',night?0.16:0.62);
document.getElementById('nightFilter').style.background=night?"rgba(0,8,38,0.53)":"rgba(0,8,38,0.0)";
}
setInterval(()=>{setDayNight(!night);},15000); setDayNight(false);
// --- Dialogue System ---
function showDialogueBlock(dialogueId) {
const data = storyData[dialogueId];
if (!data) {
closeDialogue();
return;
}
dialogue.innerHTML = '';
dialogue.style.display = 'block';
if (data.speaker && data.speaker !== 'Narration') {
const n = document.createElement('span');
n.className = 'name';
n.textContent = data.speaker + ' ';
dialogue.appendChild(n);
}
const t = document.createElement('span');
t.textContent = data.text;
dialogue.appendChild(t);
if (data.choices) {
data.choices.forEach(c => {
const btn = document.createElement('button');
btn.className = 'choice';
btn.textContent = c.text;
btn.onclick = () => {
if (c.onChoose) c.onChoose(); // Execute choice-specific logic
currentStoryId = c.next;
showDialogueBlock(currentStoryId);
};
dialogue.appendChild(btn);
});
} else {
dialogue.onclick = () => {
dialogue.onclick = null; // Prevent multiple clicks
if (data.onComplete) data.onComplete(); // Execute onComplete logic
currentStoryId = data.next;
showDialogueBlock(currentStoryId);
};
}
}
function closeDialogue() {
dialogue.style.display = 'none';
dialogue.onclick = null;
}
// --- Battle System (Simplified) ---
function startBattle(enemyType) {
state.inBattle = true;
battleEffect.style.display = 'block';
// Simulate battle outcome instantly for now
setTimeout(() => {
battleEffect.style.display = 'none';
state.inBattle = false;
// Example: Party takes some damage, or enemy is defeated
state.party.forEach(p => {
if (p.alive) p.hp = Math.max(0, p.hp - 5); // Simulate damage
if (p.hp === 0) p.alive = false;
});
updatePartyStatus();
// Logic for enemy defeat, item drops, XP gain would go here
// For now, the story progresses automatically after the simulated battle
}, 300); // Flash for 0.3 seconds
}
function showQuestClear() {
questClear.style.display = 'block';
setTimeout(() => {
questClear.style.display = 'none';
}, 3000); // Show for 3 seconds
}
// --- Shop System ---
function openShop() {
state.shopOpen = true;
shopDiv.style.display = 'block';
shopDiv.innerHTML = `
<h2>ショップ</h2>
<p>ゴールド: <span id="shopGold">${state.gold}</span>G</p>
<div id="shopItemsContainer"></div>
<button class="close-button" onclick="closeShop()">X</button>
`;
const shopItemsContainer = document.getElementById('shopItemsContainer');
shopItems.forEach(item => {
const itemDiv = document.createElement('div');
itemDiv.className = 'shop-item';
itemDiv.innerHTML = `
<span>${item.name} (${item.price}G)</span>
<button onclick="buyItem('${item.id}')">購入</button>
`;
shopItemsContainer.appendChild(itemDiv);
});
// Add a simple "Sell" option
if (state.inventory.length > 0) {
const sellSection = document.createElement('div');
sellSection.innerHTML = `<h3>持ち物売却</h3>`;
state.inventory.forEach((item, index) => {
sellSection.innerHTML += `
<div class="shop-item">
<span>${item.name} (${Math.floor(item.price / 2)}G)</span>
<button onclick="sellItem(${index})">売却</button>
</div>
`;
});
shopItemsContainer.appendChild(sellSection);
}
}
function buyItem(itemId) {
const item = shopItems.find(i => i.id === itemId);
if (item && state.gold >= item.price) {
state.gold -= item.price;
state.inventory.push(item);
updatePartyStatus();
document.getElementById('shopGold').textContent = state.gold;
// Re-render shop to show updated inventory for selling
openShop();
} else if (item) {
// Using dialogue for messages instead of alert
showDialogueBlock({ speaker: "ショップ店員", text: "ゴールドが足りません!" });
}
}
function sellItem(index) {
if (index >= 0 && index < state.inventory.length) {
const item = state.inventory.splice(index, 1)[0];
state.gold += Math.floor(item.price / 2); // Sell for half price
updatePartyStatus();
document.getElementById('shopGold').textContent = state.gold;
openShop(); // Re-render shop
}
}
function closeShop() {
state.shopOpen = false;
shopDiv.style.display = 'none';
}
// --- Interaction System ---
// Add click listeners to interactable entities
document.addEventListener('DOMContentLoaded', () => {
const scene = document.querySelector('a-scene');
// Event listener for NPC interaction
const townNpc = document.getElementById('town-npc-1');
if (townNpc) {
townNpc.addEventListener('click', () => {
if (!state.inBattle && !state.shopOpen) {
showDialogueBlock({ speaker: "村人", text: "こんにちは、冒険者さん!この街は平和ですよ。" });
}
});
}
// Event listener for Shop interaction
const townShop = document.getElementById('town-shop');
if (townShop) {
townShop.addEventListener('click', () => {
if (!state.inBattle && !state.shopOpen) {
openShop();
}
});
}
// Event listeners for Enemy interaction (Slimes)
const fieldSlime = document.getElementById('field-slime-1');
if (fieldSlime) {
fieldSlime.addEventListener('click', () => {
if (!state.inBattle && !state.shopOpen) {
showDialogueBlock({
speaker: "Narration",
text: "スライムが現れた!戦いますか?",
choices: [
{ text: "戦う", onChoose: () => { startBattle('slime'); fieldSlime.setAttribute('visible', 'false'); }, next: "battle_result_slime" },
{ text: "逃げる", next: "flee_result" }
]
});
}
});
}
const dungeonSlime = document.getElementById('dungeon-slime-1');
if (dungeonSlime) {
dungeonSlime.addEventListener('click', () => {
if (!state.inBattle && !state.shopOpen) {
showDialogueBlock({
speaker: "Narration",
text: "ダンジョンスライムが現れた!戦いますか?",
choices: [
{ text: "戦う", onChoose: () => { startBattle('dungeon-slime'); dungeonSlime.setAttribute('visible', 'false'); }, next: "battle_result_dungeon_slime" },
{ text: "逃げる", next: "flee_result" }
]
});
}
});
}
// Define battle results (simple for now)
storyData["battle_result_slime"] = { speaker: "Narration", text: "スライムを倒した!経験値と10Gを獲得した。", onComplete: () => { state.gold += 10; updatePartyStatus(); }, next: currentStoryId };
storyData["battle_result_dungeon_slime"] = { speaker: "Narration", text: "ダンジョンスライムを倒した!経験値と20Gを獲得した。", onComplete: () => { state.gold += 20; updatePartyStatus(); }, next: currentStoryId };
storyData["flee_result"] = { speaker: "Narration", text: "なんとか逃げ切った。", next: currentStoryId };
// Initial story start
showDialogueBlock(currentStoryId);
});
// --- Main Game Loop for Progress (simplified) ---
function checkProgress() {
updatePartyStatus();
const playerRig = document.querySelector('#playerRig');
if (!playerRig) return;
let pos = playerRig.object3D.position;
// Only progress linear story if no dialogue is open and not in battle/shop
if (dialogue.style.display === 'none' && !state.inBattle && !state.shopOpen) {
// Auto-progress main story
if (storyData[currentStoryId] && !storyData[currentStoryId].choices) {
// Simulate dialogue click if it's not a choice block
if (storyData[currentStoryId].next) {
// This part is tricky with auto-progression.
// For now, the story relies on user clicks, or specific triggers.
// The `onComplete` in storyData handles the actual progression.
}
}
}
// Example: Trigger story based on proximity (can be expanded)
// This is just a basic example, the main story progression is click-driven.
const townCenter = new THREE.Vector3(0, 1.6, 22);
if (pos.distanceTo(townCenter) < 10 && state.currentStoryStep === "intro_04") {
// This is handled by the initial showDialogueBlock, but a more complex game
// might trigger different dialogue based on location.
}
}
setInterval(checkProgress, 700); // Check progress every 0.7 seconds
// Initial setup calls
setWarpUI();
updatePartyStatus();
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verse - 次世代ソーシャルネットワーク</title>
<!-- SVG ファビコンを指定 -->
<link rel="shortcut icon" href="favicon.ico">
<!-- PNG版 -->
<link rel="icon" type="image/png" href="favicon.png">
<!-- SVG版 -->
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/rita@3.0.0/dist/rita.min.js"></script>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.glass-effect {
background: rgba(255,255,255,0.25);
backdrop-filter: blur(10px);
border-radius: 10px;
border: 1px solid rgba(255,255,255,0.18);
}
.card-hover { transition: all 0.3s ease; }
.card-hover:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04);
}
.gradient-text {
background: linear-gradient(45deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.timeline-post {
background: white;
border-radius: 15px;
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);
transition: all 0.3s ease;
border-left: 4px solid #667eea;
}
.timeline-post:hover {
transform: translateX(5px);
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1);
}
.profile-avatar {
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
border: 4px solid white;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.btn-primary {
background: linear-gradient(45deg, #667eea, #764ba2);
border: none;
transition: all 0.3s ease;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 7px 14px rgba(0,0,0,0.18);
}
.section-divider {
height: 3px;
background: linear-gradient(90deg, #667eea, #764ba2);
border-radius: 2px;
margin: 2rem 0;
}
.username-badge {
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
padding: 0.2rem 0.5rem;
border-radius: 1rem;
font-size: 0.75rem;
font-weight: 600;
display: inline-block;
margin-left: 0.5rem;
}
.share-menu {
position: absolute;
z-index: 50;
min-width: 180px;
right: 0;
top: 110%;
background: white;
border-radius: 10px;
box-shadow: 0 8px 24px rgba(0,0,0,0.13);
}
.share-menu button {
width: 100%;
text-align: left;
padding: 10px 20px;
border: none;
background: none;
cursor: pointer;
font-size: 0.95rem;
}
.share-menu button:hover { background: #f0f4ff; }
.status-indicator {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 5px;
}
.status-active {
background-color: #10b981;
animation: pulse 2s infinite;
}
.status-inactive { background-color: #6b7280; }
.log-container {
max-height: 150px;
overflow-y: auto;
background: rgba(255,255,255,0.1);
border-radius: 5px;
padding: 10px;
margin-top: 10px;
font-size: 0.8rem;
font-family: monospace;
}
.rss-item {
background: rgba(255,255,255,0.1);
border-radius: 5px;
padding: 8px;
margin: 5px 0;
font-size: 0.8rem;
}
.error-message {
color: #ef4444;
background: rgba(239, 68, 68, 0.1);
padding: 8px;
border-radius: 5px;
margin: 5px 0;
}
.success-message {
color: #10b981;
background: rgba(16, 185, 129, 0.1);
padding: 8px;
border-radius: 5px;
margin: 5px 0;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.dark .glass-effect {
background: rgba(0,0,0,0.3);
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.1);
}
.dark .timeline-post {
background: #374151;
color: #f9fafb;
}
@media print {
body { background: white !important; -webkit-print-color-adjust: exact; }
.glass-effect { background: white !important; backdrop-filter: none !important; border: 1px solid #e5e7eb !important; }
}
</style>
</head>
<body>
<header class="glass-effect mx-4 mt-4 p-6">
<div class="text-center">
<h1 class="text-4xl font-bold text-white mb-2">
<i class="fas fa-comments mr-3"></i>Verse
<span class="text-sm opacity-75 ml-2">v2.0 完全修正版</span>
</h1>
<p class="text-white text-lg opacity-90">次世代ソーシャルネットワーク</p>
<div class="mt-4 flex justify-center items-center space-x-4">
<div class="flex items-center">
<img id="header-profile-icon" class="profile-avatar" src="https://via.placeholder.com/100" alt="プロフィール">
<div class="ml-3 text-white">
<div class="font-semibold text-lg" id="header-username">未設定</div>
<div class="text-sm opacity-75">ユーザー</div>
</div>
</div>
<button onclick="toggleDarkMode()" class="btn-primary px-4 py-2 rounded-full text-white">
<i class="fas fa-moon mr-2"></i>ダークモード
</button>
<button onclick="showSystemStatus()" class="btn-primary px-4 py-2 rounded-full text-white">
<i class="fas fa-info-circle mr-2"></i>ステータス
</button>
</div>
</div>
</header>
<div class="max-w-6xl mx-auto px-4 py-6">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- 左側カラム -->
<div class="lg:col-span-1 space-y-6">
<!-- プロフィール -->
<div class="glass-effect p-6 card-hover">
<h3 class="text-2xl font-bold gradient-text mb-4">
<i class="fas fa-user-circle mr-2"></i>プロフィール
</h3>
<div class="text-center mb-6">
<img id="profile-icon" class="profile-avatar mx-auto mb-4" src="https://via.placeholder.com/100" alt="プロフィール">
<input type="file" id="profile-upload" accept="image/*" onchange="uploadProfileIcon(event)" class="hidden">
<button onclick="document.getElementById('profile-upload').click()" class="btn-primary px-4 py-2 rounded-full text-white">
<i class="fas fa-camera mr-2"></i>画像変更
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-white font-semibold mb-2">ユーザー名</label>
<input type="text" id="username" class="w-full p-3 border rounded-lg bg-white bg-opacity-90" placeholder="ユーザー名を入力してください" maxlength="20">
</div>
<div>
<label class="block text-white font-semibold mb-2">自己紹介</label>
<textarea id="self-intro" class="w-full p-3 border rounded-lg bg-white bg-opacity-90" rows="4" placeholder="自己紹介を入力してください"></textarea>
</div>
<button onclick="saveProfile()" class="btn-primary w-full py-2 rounded-lg text-white">
<i class="fas fa-save mr-2"></i>プロフィール保存
</button>
<div class="p-3 bg-white bg-opacity-80 rounded-lg">
<h5 class="font-semibold text-gray-800 mb-2">プレビュー:</h5>
<div class="text-gray-700">
<div class="font-semibold mb-1" id="username-preview">未設定</div>
<div id="self-intro-preview" class="text-sm whitespace-pre-line min-h-8">まだ自己紹介がありません</div>
</div>
</div>
</div>
</div>
<!-- BOT機能 -->
<div class="glass-effect p-6 card-hover">
<h3 class="text-xl font-bold text-white mb-4">
<i class="fas fa-robot mr-2"></i>BOT機能
<span class="status-indicator" id="bot-status"></span>
<span id="bot-status-text" class="text-xs opacity-75">停止中</span>
</h3>
<div class="space-y-4">
<div>
<textarea id="botContent" class="w-full p-3 border rounded-lg bg-white bg-opacity-90" rows="3" placeholder="BOT投稿内容"></textarea>
<button onclick="postBotMessage()" class="btn-primary w-full mt-2 py-2 rounded-lg text-white">
<i class="fas fa-robot mr-2"></i>BOT投稿
</button>
</div>
<div>
<input type="number" id="botIntervalSec" class="w-full p-2 border rounded-lg bg-white bg-opacity-90" placeholder="マルコフ自動投稿間隔(秒)" min="10" max="3600" value="60">
<div class="flex space-x-2 mt-2">
<button onclick="postMarkovBot()" class="btn-primary flex-1 py-2 rounded-lg text-white">
<i class="fas fa-dice mr-2"></i>マルコフ生成
</button>
<button onclick="startBotAutoPost()" class="btn-primary flex-1 py-2 rounded-lg text-white">
<i class="fas fa-play mr-2"></i>自動開始
</button>
<button onclick="stopBotAutoPost()" class="bg-red-500 hover:bg-red-600 flex-1 py-2 rounded-lg text-white">
<i class="fas fa-stop mr-2"></i>停止
</button>
</div>
</div>
<div class="text-white text-xs opacity-75">
<i class="fas fa-info-circle mr-1"></i>マルコフ連鎖では過去の投稿からランダムな文章を生成します
</div>
<div id="bot-log" class="log-container text-white text-xs"></div>
</div>
</div>
<!-- RSS機能 -->
<div class="glass-effect p-6 card-hover">
<h3 class="text-xl font-bold text-white mb-4">
<i class="fas fa-rss mr-2"></i>RSS機能
<span class="status-indicator" id="rss-status"></span>
<span id="rss-status-text" class="text-xs opacity-75">停止中</span>
</h3>
<div class="space-y-4">
<div>
<input type="text" id="feedUrl" class="w-full p-3 border rounded-lg bg-white bg-opacity-90" placeholder="RSS URL">
<button onclick="registerFeedUrl()" class="btn-primary w-full mt-2 py-2 rounded-lg text-white">
<i class="fas fa-plus mr-2"></i>フィード登録
</button>
</div>
<div>
<input type="number" id="feedIntervalSec" class="w-full p-2 border rounded-lg bg-white bg-opacity-90" placeholder="RSS自動取得間隔(秒)" min="60" max="3600" value="180">
<div class="flex space-x-2 mt-2">
<button onclick="fetchAllFeeds()" class="btn-primary flex-1 py-2 rounded-lg text-white">
<i class="fas fa-download mr-2"></i>手動取得
</button>
<button onclick="startRSSAutoPost()" class="btn-primary flex-1 py-2 rounded-lg text-white">
<i class="fas fa-play mr-2"></i>自動開始
</button>
<button onclick="stopRSSAutoPost()" class="bg-red-500 hover:bg-red-600 flex-1 py-2 rounded-lg text-white">
<i class="fas fa-stop mr-2"></i>停止
</button>
</div>
</div>
<div class="bg-white bg-opacity-80 rounded-lg p-3">
<div class="flex justify-between items-center mb-2">
<h5 class="font-semibold text-gray-800">登録済みフィード:</h5>
<button onclick="addDefaultFeeds()" class="text-xs bg-blue-500 text-white px-2 py-1 rounded">
デフォルト追加
</button>
</div>
<div id="feed-list" class="text-sm text-gray-700"></div>
</div>
<div id="rss-log" class="log-container text-white text-xs"></div>
</div>
</div>
</div>
<!-- 右側カラム -->
<div class="lg:col-span-2 space-y-6">
<!-- 新規投稿 -->
<div class="glass-effect p-6 card-hover">
<h3 class="text-2xl font-bold gradient-text mb-4">
<i class="fas fa-edit mr-2"></i>新規投稿
</h3>
<div>
<textarea id="postContent" class="w-full p-4 border rounded-lg bg-white bg-opacity-90" rows="4" placeholder="今何を考えていますか?" maxlength="500"></textarea>
<div class="mt-4 flex justify-between items-center">
<div class="text-white text-sm opacity-75">
<i class="fas fa-info-circle mr-1"></i>あなたの思いを共有しましょう
<span id="char-count" class="ml-2">(0/500)</span>
</div>
<button onclick="createUserPost()" class="btn-primary px-6 py-3 rounded-lg text-white font-semibold">
<i class="fas fa-paper-plane mr-2"></i>投稿する
</button>
</div>
</div>
</div>
<div class="section-divider"></div>
<!-- タイムライン -->
<div class="glass-effect p-6">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold text-white">
<i class="fas fa-stream mr-2"></i>タイムライン
<span id="post-count" class="text-sm font-normal opacity-75 ml-2">(0件の投稿)</span>
</h3>
<div class="flex space-x-2">
<button onclick="clearAllPosts()" class="bg-red-500 hover:bg-red-600 px-3 py-1 rounded text-white text-sm">
<i class="fas fa-trash mr-1"></i>全削除
</button>
<button onclick="exportData()" class="bg-green-500 hover:bg-green-600 px-3 py-1 rounded text-white text-sm">
<i class="fas fa-download mr-1"></i>エクスポート
</button>
</div>
</div>
<div id="timeline" class="space-y-4"></div>
<div id="empty-timeline" class="text-center py-12 text-white opacity-75">
<i class="fas fa-comments text-4xl mb-4"></i>
<p class="text-lg">まだ投稿がありません</p>
<p class="text-sm">最初の投稿をして、タイムラインを始めましょう!</p>
</div>
</div>
</div>
</div>
</div>
<footer class="glass-effect mx-4 mb-4 p-4 text-center">
<p class="text-white opacity-75">
<i class="fas fa-copyright mr-2"></i>2025 Verse - 次世代ソーシャルネットワーク v2.0
<span class="ml-4">
<i class="fas fa-bug mr-1"></i>RSS自動投稿完全修正版
</span>
</p>
</footer>
<script>
// === 設定とグローバル変数 ===
const DEFAULT_FEEDS = [
"https://feeds.feedburner.com/hatena/b/hotentry",
"https://gigazine.net/news/rss_2.0/",
"https://rss.cnn.com/rss/edition.rss",
"https://feeds.bbci.co.uk/news/rss.xml",
"https://www.reddit.com/r/worldnews/.rss"
];
const RSS_APIS = [
'https://api.rss2json.com/v1/api.json',
'https://cors-anywhere.herokuapp.com/',
'https://api.allorigins.win/get'
];
// グローバル状態
let posts = JSON.parse(localStorage.getItem('verse_posts') || '[]');
let feedUrls = JSON.parse(localStorage.getItem('verse_feeds') || '[]');
let profile = JSON.parse(localStorage.getItem('verse_profile') || '{}');
let processedItems = new Set(JSON.parse(localStorage.getItem('verse_processed') || '[]'));
// タイマーとステータス
let botInterval = null;
let rssInterval = null;
let isDarkMode = localStorage.getItem('verse_darkMode') === 'true';
let markovChain = null;
let isInitialized = false;
// 統計情報
let stats = {
totalPosts: 0,
rssSuccess: 0,
rssErrors: 0,
botPosts: 0,
lastRSSUpdate: null,
lastBotPost: null
};
// === 初期化処理 ===
function initializeApp() {
if (isInitialized) return;
// プロフィール初期化
if (!profile.icon) profile.icon = 'https://via.placeholder.com/100';
if (!profile.username) profile.username = 'ゲストユーザー';
if (!profile.selfIntro) profile.selfIntro = '';
// デフォルトフィード設定
if (feedUrls.length === 0) {
feedUrls = [...DEFAULT_FEEDS];
saveData();
addLog('rss-log', 'デフォルトRSSフィードを登録しました', 'success');
}
// UI初期化
updateAllUI();
updateStatusIndicators();
// ダークモード適用
if (isDarkMode) {
document.body.classList.add('dark');
document.body.style.background = 'linear-gradient(135deg, #1a202c 0%, #2d3748 100%)';
}
isInitialized = true;
addLog('rss-log', 'アプリケーション初期化完了', 'success');
addLog('bot-log', 'BOT機能初期化完了', 'success');
// 初回RSS取得(遅延実行)
setTimeout(() => {
addLog('rss-log', '初回RSS取得を開始します...', 'info');
fetchAllFeeds();
}, 3000);
}
// === データ管理 ===
function saveData() {
try {
localStorage.setItem('verse_posts', JSON.stringify(posts));
localStorage.setItem('verse_feeds', JSON.stringify(feedUrls));
localStorage.setItem('verse_profile', JSON.stringify(profile));
localStorage.setItem('verse_processed', JSON.stringify([...processedItems]));
localStorage.setItem('verse_darkMode', isDarkMode);
stats.totalPosts = posts.length;
} catch (error) {
console.error('データ保存エラー:', error);
addLog('rss-log', `データ保存エラー: ${error.message}`, 'error');
}
}
function clearAllPosts() {
if (confirm('すべての投稿を削除してもよろしいですか?\nこの操作は元に戻せません。')) {
posts = [];
processedItems.clear();
saveData();
renderTimeline();
addLog('rss-log', 'すべての投稿を削除しました', 'info');
addLog('bot-log', 'すべての投稿を削除しました', 'info');
}
}
function exportData() {
const exportData = {
posts: posts,
profile: profile,
feedUrls: feedUrls,
stats: stats,
exportDate: new Date().toISOString()
};
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `verse_export_${new Date().toISOString().split('T')[0]}.json`;
a.click();
URL.revokeObjectURL(url);
addLog('rss-log', 'データをエクスポートしました', 'success');
}
// === ログ機能 ===
function addLog(elementId, message, type = 'info') {
const logElement = document.getElementById(elementId);
if (!logElement) return;
const timestamp = new Date().toLocaleTimeString('ja-JP');
const logEntry = document.createElement('div');
const typeClass = {
'error': 'error-message',
'success': 'success-message',
'info': 'text-white opacity-75'
}[type] || 'text-white opacity-75';
logEntry.className = typeClass;
logEntry.innerHTML = `<span class="opacity-75">[${timestamp}]</span> ${message}`;
logElement.appendChild(logEntry);
logElement.scrollTop = logElement.scrollHeight;
// ログ数制限
while (logElement.children.length > 50) {
logElement.removeChild(logElement.firstChild);
}
}
// === ステータス管理 ===
function updateStatusIndicators() {
const botIndicator = document.getElementById('bot-status');
const rssIndicator = document.getElementById('rss-status');
const botStatusText = document.getElementById('bot-status-text');
const rssStatusText = document.getElementById('rss-status-text');
if (botIndicator && botStatusText) {
const isActive = botInterval !== null;
botIndicator.className = `status-indicator ${isActive ? 'status-active' : 'status-inactive'}`;
botStatusText.textContent = isActive ? '動作中' : '停止中';
}
if (rssIndicator && rssStatusText) {
const isActive = rssInterval !== null;
rssIndicator.className = `status-indicator ${isActive ? 'status-active' : 'status-inactive'}`;
rssStatusText.textContent = isActive ? '動作中' : '停止中';
}
}
function showSystemStatus() {
const statusInfo = `
=== Verse システムステータス ===
総投稿数: ${stats.totalPosts}
RSS成功: ${stats.rssSuccess}
RSSエラー: ${stats.rssErrors}
BOT投稿数: ${stats.botPosts}
最後のRSS更新: ${stats.lastRSSUpdate || 'なし'}
最後のBOT投稿: ${stats.lastBotPost || 'なし'}
RSS自動取得: ${rssInterval ? '動作中' : '停止中'}
BOT自動投稿: ${botInterval ? '動作中' : '停止中'}
登録フィード数: ${feedUrls.length}
処理済みアイテム: ${processedItems.size}
`;
alert(statusInfo);
}
// === 投稿管理 ===
function createPost(content, type = 'user', username = null, icon = null) {
if (!content || !content.trim()) {
addLog('rss-log', '空のコンテンツは投稿できません', 'error');
return false;
}
// RSS重複チェック
if (type === 'feed') {
const urlMatch = content.match(/href="([^"]+)"/);
const url = urlMatch ? urlMatch[1] : null;
if (url && processedItems.has(url)) {
addLog('rss-log', `重複記事をスキップ: ${url.substring(0, 50)}...`, 'info');
return false;
}
if (url) {
processedItems.add(url);
}
}
const post = {
id: Date.now() + Math.random(),
content: content.trim(),
likes: 0,
timestamp: new Date().toLocaleString('ja-JP'),
type: type,
username: username || profile.username,
icon: icon || profile.icon
};
posts.unshift(post);
// 統計更新
if (type === 'bot' || type === 'markov') {
stats.botPosts++;
stats.lastBotPost = post.timestamp;
}
if (type === 'feed') {
stats.rssSuccess++;
stats.lastRSSUpdate = post.timestamp;
}
saveData();
renderTimeline();
return true;
}
function createUserPost() {
const content = document.getElementById('postContent').value.trim();
if (content) {
if (createPost(content, 'user')) {
document.getElementById('postContent').value = '';
updateCharCount();
addLog('bot-log', 'ユーザー投稿を作成しました', 'success');
}
}
}
function likePost(id) {
const post = posts.find(p => p.id === id);
if (post) {
post.likes++;
saveData();
renderTimeline();
}
}
function deletePost(id) {
if (confirm('この投稿を削除しますか?')) {
const post = posts.find(p => p.id === id);
if (post && post.type === 'feed') {
const urlMatch = post.content.match(/href="([^"]+)"/);
if (urlMatch) {
processedItems.delete(urlMatch[1]);
}
}
posts = posts.filter(p => p.id !== id);
saveData();
renderTimeline();
}
}
// === タイムライン表示 ===
function renderTimeline() {
const timeline = document.getElementById('timeline');
const emptyTimeline = document.getElementById('empty-timeline');
const postCount = document.getElementById('post-count');
if (posts.length === 0) {
timeline.innerHTML = '';
emptyTimeline.style.display = 'block';
postCount.textContent = '(0件の投稿)';
return;
}
emptyTimeline.style.display = 'none';
postCount.textContent = `(${posts.length}件の投稿)`;
timeline.innerHTML = posts.map(post => {
const typeInfo = getPostTypeInfo(post.type);
return `
<div class="timeline-post p-6">
<div class="flex justify-between items-start mb-4">
<div class="flex items-center space-x-3">
<img src="${post.icon}" alt="アバター" class="w-10 h-10 rounded-full object-cover">
<div>
<div class="flex items-center">
<span class="font-semibold text-gray-800 dark:text-white">${post.username}</span>
<span class="username-badge">${typeInfo.badge}</span>
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">${post.timestamp}</div>
</div>
</div>
</div>
<div class="text-gray-800 dark:text-gray-200 mb-4 leading-relaxed">${post.content}</div>
<div class="flex items-center space-x-4 pt-4 border-t border-gray-100 dark:border-gray-600">
<button onclick="likePost(${post.id})" class="flex items-center space-x-2 text-gray-600 dark:text-gray-400 hover:text-red-500 transition-colors">
<i class="fas fa-heart"></i>
<span>${post.likes}</span>
</button>
<div class="relative">
<button onclick="toggleShareMenu(${post.id})" class="flex items-center space-x-2 text-gray-600 dark:text-gray-400 hover:text-blue-500 transition-colors">
<i class="fas fa-share"></i>
<span>シェア</span>
</button>
<div id="share-menu-${post.id}" class="share-menu hidden">
<button onclick="shareToX(${post.id})"><i class="fab fa-x-twitter text-blue-400 mr-2"></i>Xでシェア</button>
<button onclick="shareToLine(${post.id})"><i class="fab fa-line text-green-400 mr-2"></i>LINEでシェア</button>
<button onclick="copyPost(${post.id})"><i class="fas fa-copy mr-2"></i>コピー</button>
</div>
</div>
<button onclick="deletePost(${post.id})" class="flex items-center space-x-2 text-gray-600 dark:text-gray-400 hover:text-red-500 transition-colors ml-auto">
<i class="fas fa-trash"></i>
<span>削除</span>
</button>
</div>
</div>
`;
}).join('');
}
function getPostTypeInfo(type) {
const typeMap = {
'bot': { badge: '🤖 BOT' },
'markov': { badge: '🎲 MarkovBOT' },
'feed': { badge: '📡 RSS Feed' },
'user': { badge: '👤 ユーザー' }
};
return typeMap[type] || typeMap['user'];
}
// === シェア機能 ===
function getPostContentText(id) {
const post = posts.find(p => p.id === id);
if (!post) return '';
const tempDiv = document.createElement('div');
tempDiv.innerHTML = post.content;
return tempDiv.textContent || tempDiv.innerText || '';
}
function toggleShareMenu(id) {
document.querySelectorAll('[id^="share-menu-"]').forEach(el => el.classList.add('hidden'));
const menu = document.getElementById('share-menu-' + id);
if (menu) {
menu.classList.toggle('hidden');
setTimeout(() => {
const closeMenu = (e) => {
if (!menu.contains(e.target)) {
menu.classList.add('hidden');
document.removeEventListener('mousedown', closeMenu);
}
};
document.addEventListener('mousedown', closeMenu);
}, 100);
}
}
function shareToX(id) {
const text = encodeURIComponent(getPostContentText(id));
const url = encodeURIComponent(location.href);
window.open(`https://twitter.com/intent/tweet?text=${text}&url=${url}`, '_blank');
}
function shareToLine(id) {
const text = encodeURIComponent(getPostContentText(id) + ' ' + location.href);
window.open(`https://social-plugins.line.me/lineit/share?url=${text}`, '_blank');
}
function copyPost(id) {
const text = getPostContentText(id);
navigator.clipboard.writeText(text).then(() => {
alert('投稿内容をクリップボードにコピーしました!');
}).catch(() => {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
alert('投稿内容をコピーしました!');
});
}
// === BOT機能 ===
function postBotMessage() {
const content = document.getElementById('botContent').value.trim();
if (content) {
if (createPost(content, 'bot', 'BOT')) {
document.getElementById('botContent').value = '';
addLog('bot-log', `BOT投稿: "${content.substring(0, 30)}..."`, 'success');
}
}
}
// === マルコフ連鎖機能(強化版) ===
function initializeMarkovChain() {
try {
if (typeof RiTa === 'undefined') {
addLog('bot-log', 'RiTaライブラリが未読み込みです', 'error');
return false;
}
markovChain = new RiTa.Markov(3);
const userPosts = posts.filter(p =>
(p.type === 'user' || p.type === 'bot') &&
p.content.length > 20 &&
!p.content.includes('<div') &&
!p.content.includes('href=')
);
if (userPosts.length < 5) {
addLog('bot-log', `学習データ不足 (${userPosts.length}件) - 5件以上必要`, 'error');
return false;
}
const textData = userPosts
.map(p => cleanTextForMarkov(p.content))
.filter(text => text.length > 15)
.join('\n');
if (textData.length < 200) {
addLog('bot-log', 'テキストデータが不十分です', 'error');
return false;
}
markovChain.addText(textData);
addLog('bot-log', `マルコフ連鎖学習完了 (${userPosts.length}件の投稿を学習)`, 'success');
return true;
} catch (error) {
addLog('bot-log', `マルコフ初期化エラー: ${error.message}`, 'error');
return false;
}
}
function cleanTextForMarkov(text) {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = text;
let cleanText = tempDiv.textContent || tempDiv.innerText || '';
cleanText = cleanText
.replace(/https?:\/\/[^\s]+/g, '')
.replace(/[^\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF\u3400-\u4DBFa-zA-Z0-9\s!?。、.,!?\n]/g, '')
.replace(/\s+/g, ' ')
.replace(/[。!?]{2,}/g, '。')
.trim();
return cleanText;
}
function generateMarkovText() {
try {
if (!initializeMarkovChain()) {
const fallbackMessages = [
"今日はいい天気ですね!どのように過ごされていますか?",
"最近面白いニュースありましたか?",
"新しいアイデアが浮かんできました。",
"皆さんはどう思いますか?",
"今日も一日頑張りましょう!"
];
return fallbackMessages[Math.floor(Math.random() * fallbackMessages.length)];
}
let bestText = '';
const maxAttempts = 15;
for (let i = 0; i < maxAttempts; i++) {
try {
const options = {
maxLength: 120,
minLength: 20,
count: Math.floor(Math.random() * 2) + 1
};
const sentences = markovChain.generate(options);
if (sentences && sentences.length > 0) {
let generatedText = sentences.join(' ').trim();
// 文章の自然な終端処理
if (generatedText.length > 150) {
const endMarkers = ['。', '!', '?'];
let bestEnd = -1;
endMarkers.forEach(marker => {
const pos = generatedText.lastIndexOf(marker, 130);
if (pos > 30) bestEnd = Math.max(bestEnd, pos);
});
if (bestEnd > -1) {
generatedText = generatedText.substring(0, bestEnd + 1);
}
}
// 品質チェック
if (generatedText.length >= 20 &&
generatedText.length <= 200 &&
!generatedText.includes('undefined') &&
generatedText.length > bestText.length) {
bestText = generatedText;
}
}
} catch (genError) {
continue;
}
}
if (!bestText || bestText.length < 10) {
bestText = "新しいアイデアについて考えています。皆さんの意見も聞きたいですね。";
}
return bestText;
} catch (error) {
addLog('bot-log', `マルコフ生成エラー: ${error.message}`, 'error');
return "マルコフ連鎖で自然な文章を生成しています。";
}
}
function postMarkovBot() {
const content = generateMarkovText();
if (createPost(content, 'markov', 'MarkovBOT')) {
addLog('bot-log', `マルコフ投稿: "${content.substring(0, 40)}..."`, 'success');
}
}
function startBotAutoPost() {
const interval = parseInt(document.getElementById('botIntervalSec').value) || 60;
if (interval < 10) {
alert('間隔は10秒以上で設定してください。');
return;
}
stopBotAutoPost();
// 初回投稿
setTimeout(postMarkovBot, 2000);
botInterval = setInterval(() => {
postMarkovBot();
}, interval * 1000);
updateStatusIndicators();
addLog('bot-log', `マルコフBOT自動投稿開始 (${interval}秒間隔)`, 'success');
}
function stopBotAutoPost() {
if (botInterval) {
clearInterval(botInterval);
botInterval = null;
updateStatusIndicators();
addLog('bot-log', 'マルコフBOT自動投稿を停止しました', 'info');
}
}
// === RSS機能(完全修正版) ===
function addDefaultFeeds() {
let addedCount = 0;
DEFAULT_FEEDS.forEach(url => {
if (!feedUrls.includes(url)) {
feedUrls.push(url);
addedCount++;
}
});
if (addedCount > 0) {
saveData();
renderFeedList();
addLog('rss-log', `${addedCount}件のデフォルトフィードを追加しました`, 'success');
} else {
addLog('rss-log', 'すべてのデフォルトフィードは既に登録されています', 'info');
}
}
function registerFeedUrl() {
const url = document.getElementById('feedUrl').value.trim();
if (!url) {
alert('URLを入力してください。');
return;
}
if (!url.startsWith('http')) {
alert('有効なURLを入力してください(http://またはhttps://で開始)。');
return;
}
if (feedUrls.includes(url)) {
alert('このフィードは既に登録されています。');
return;
}
feedUrls.push(url);
saveData();
renderFeedList();
document.getElementById('feedUrl').value = '';
addLog('rss-log', `フィード登録: ${url}`, 'success');
}
function removeFeedUrl(index) {
if (confirm('このフィードを削除しますか?')) {
const removedUrl = feedUrls[index];
feedUrls.splice(index, 1);
saveData();
renderFeedList();
addLog('rss-log', `フィード削除: ${removedUrl}`, 'info');
}
}
function renderFeedList() {
const feedList = document.getElementById('feed-list');
if (feedUrls.length === 0) {
feedList.innerHTML = '<div class="text-gray-500">登録されたフィードはありません</div>';
return;
}
feedList.innerHTML = feedUrls.map((url, index) => `
<div class="rss-item flex items-center justify-between">
<span class="text-xs truncate flex-1" title="${url}">${url}</span>
<button onclick="removeFeedUrl(${index})" class="text-red-500 hover:text-red-700 ml-2 text-xs">
<i class="fas fa-times"></i>
</button>
</div>
`).join('');
}
// 改良されたRSS取得機能
async function fetchAllFeeds() {
if (feedUrls.length === 0) {
addLog('rss-log', 'RSSフィードが登録されていません', 'error');
return;
}
addLog('rss-log', `RSS取得開始 (${feedUrls.length}件のフィード)`, 'info');
let successCount = 0;
let errorCount = 0;
for (let i = 0; i < feedUrls.length; i++) {
const url = feedUrls[i];
try {
addLog('rss-log', `[${i + 1}/${feedUrls.length}] 取得中: ${url.substring(0, 50)}...`, 'info');
const success = await fetchSingleFeed(url);
if (success) {
successCount++;
addLog('rss-log', `✓ 成功: ${url.substring(0, 50)}...`, 'success');
} else {
errorCount++;
addLog('rss-log', `✗ 失敗: ${url.substring(0, 50)}...`, 'error');
}
} catch (error) {
errorCount++;
addLog('rss-log', `✗ エラー: ${error.message}`, 'error');
}
// レート制限対策(フィード間に待機時間)
if (i < feedUrls.length - 1) {
await sleep(2000);
}
}
stats.rssSuccess += successCount;
stats.rssErrors += errorCount;
const resultMsg = `RSS取得完了: 成功 ${successCount}件 / エラー ${errorCount}件`;
addLog('rss-log', resultMsg, successCount > 0 ? 'success' : 'error');
if (successCount > 0) {
saveData();
}
}
async function fetchSingleFeed(feedUrl) {
const apis = [
// API 1: rss2json
async () => {
const response = await fetchWithTimeout(
`https://api.rss2json.com/v1/api.json?rss_url=${encodeURIComponent(feedUrl)}&count=5`,
10000
);
const data = await response.json();
if (data.status !== 'ok') {
throw new Error(data.message || 'RSS API error');
}
return data.items || [];
},
// API 2: allorigins
async () => {
const response = await fetchWithTimeout(
`https://api.allorigins.win/get?url=${encodeURIComponent(feedUrl)}`,
10000
);
const data = await response.json();
if (!data.contents) {
throw new Error('No contents returned');
}
return parseRSSContent(data.contents);
}
];
for (const api of apis) {
try {
const items = await api();
return await processRSSItems(items, feedUrl);
} catch (error) {
continue;
}
}
return false;
}
async function processRSSItems(items, feedUrl) {
if (!items || items.length === 0) {
return false;
}
let processedCount = 0;
for (const item of items.slice(0, 3)) { // 最大3件まで
if (!item.title || !item.link) continue;
// 重複チェック
if (processedItems.has(item.link)) {
continue;
}
const cleanTitle = cleanHTML(item.title).substring(0, 100);
const cleanDescription = item.description
? cleanHTML(item.description).substring(0, 150)
: '';
const content = createRSSPostContent(cleanTitle, cleanDescription, item.link, feedUrl);
if (createPost(content, 'feed', 'RSS Feed')) {
processedCount++;
processedItems.add(item.link);
// 投稿間の間隔
if (processedCount < 3) {
await sleep(1000);
}
}
}
return processedCount > 0;
}
function createRSSPostContent(title, description, link, feedUrl) {
const feedDomain = new URL(feedUrl).hostname;
return `
<div class="border-l-4 border-blue-500 pl-4 bg-gray-50 dark:bg-gray-700 rounded-r-lg p-3">
<h4 class="font-semibold mb-2 text-gray-900 dark:text-white">${title}</h4>
${description ? `<p class="text-sm text-gray-600 dark:text-gray-300 mb-3">${description}${description.length >= 150 ? '...' : ''}</p>` : ''}
<div class="flex items-center justify-between">
<span class="text-xs text-gray-500 dark:text-gray-400">
<i class="fas fa-globe mr-1"></i>${feedDomain}
</span>
<a href="${link}" target="_blank" class="text-blue-600 hover:text-blue-800 dark:text-blue-400 text-sm font-medium">
<i class="fas fa-external-link-alt mr-1"></i>記事を読む
</a>
</div>
</div>
`;
}
function parseRSSContent(xmlContent) {
// 簡易XMLパース(実装を簡略化)
const items = [];
const parser = new DOMParser();
const doc = parser.parseFromString(xmlContent, 'text/xml');
const rssItems = doc.querySelectorAll('item');
rssItems.forEach(item => {
const title = item.querySelector('title')?.textContent;
const link = item.querySelector('link')?.textContent;
const description = item.querySelector('description')?.textContent;
if (title && link) {
items.push({ title, link, description });
}
});
return items;
}
function cleanHTML(text) {
if (!text) return '';
const tempDiv = document.createElement('div');
tempDiv.innerHTML = text;
return (tempDiv.textContent || tempDiv.innerText || '').trim();
}
async function fetchWithTimeout(url, timeout = 10000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
signal: controller.signal,
headers: {
'Accept': 'application/json',
'User-Agent': 'Verse RSS Reader 2.0'
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response;
} finally {
clearTimeout(timeoutId);
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function startRSSAutoPost() {
const interval = parseInt(document.getElementById('feedIntervalSec').value) || 180;
if (interval < 60) {
alert('間隔は60秒以上で設定してください。');
return;
}
stopRSSAutoPost();
// 初回実行(少し遅延)
setTimeout(() => {
addLog('rss-log', '自動RSS取得の初回実行を開始...', 'info');
fetchAllFeeds();
}, 5000);
rssInterval = setInterval(() => {
addLog('rss-log', '定期RSS取得を実行中...', 'info');
fetchAllFeeds();
}, interval * 1000);
updateStatusIndicators();
addLog('rss-log', `RSS自動取得開始 (${interval}秒間隔)`, 'success');
}
function stopRSSAutoPost() {
if (rssInterval) {
clearInterval(rssInterval);
rssInterval = null;
updateStatusIndicators();
addLog('rss-log', 'RSS自動取得を停止しました', 'info');
}
}
// === プロフィール機能 ===
function uploadProfileIcon(event) {
const file = event.target.files[0];
if (!file) return;
if (file.size > 5 * 1024 * 1024) {
alert('画像サイズは5MB以下にしてください。');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
profile.icon = e.target.result;
updateAllUI();
saveData();
alert('プロフィール画像を更新しました!');
};
reader.readAsDataURL(file);
}
function saveProfile() {
const username = document.getElementById('username').value.trim();
const selfIntro = document.getElementById('self-intro').value.trim();
if (username && username.length > 20) {
alert('ユーザー名は20文字以内で入力してください。');
return;
}
profile.username = username || 'ゲストユーザー';
profile.selfIntro = selfIntro;
updateAllUI();
saveData();
alert('プロフィールを保存しました!');
}
// === UI更新 ===
function updateAllUI() {
// プロフィール画像更新
const profileIcon = document.getElementById('profile-icon');
const headerIcon = document.getElementById('header-profile-icon');
if (profileIcon) profileIcon.src = profile.icon;
if (headerIcon) headerIcon.src = profile.icon;
// ユーザー名更新
const usernameElements = ['username-preview', 'header-username'];
usernameElements.forEach(id => {
const element = document.getElementById(id);
if (element) element.textContent = profile.username;
});
// 自己紹介更新
const selfIntroPreview = document.getElementById('self-intro-preview');
if (selfIntroPreview) {
selfIntroPreview.textContent = profile.selfIntro || 'まだ自己紹介がありません';
}
// タイムライン再描画
renderTimeline();
renderFeedList();
}
function updateCharCount() {
const postContent = document.getElementById('postContent');
const charCount = document.getElementById('char-count');
if (postContent && charCount) {
const length = postContent.value.length;
charCount.textContent = `(${length}/500)`;
charCount.style.color = length > 450 ? '#ef4444' : '';
}
}
function toggleDarkMode() {
isDarkMode = !isDarkMode;
if (isDarkMode) {
document.body.classList.add('dark');
document.body.style.background = 'linear-gradient(135deg, #1a202c 0%, #2d3748 100%)';
} else {
document.body.classList.remove('dark');
document.body.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
}
saveData();
}
// === イベントリスナー ===
document.addEventListener('DOMContentLoaded', function() {
// 文字数カウンター
const postContent = document.getElementById('postContent');
if (postContent) {
postContent.addEventListener('input', updateCharCount);
}
// プロフィール入力のリアルタイム更新
const usernameInput = document.getElementById('username');
const selfIntroInput = document.getElementById('self-intro');
if (usernameInput) {
usernameInput.addEventListener('input', function() {
const username = this.value.trim() || 'ゲストユーザー';
document.getElementById('username-preview').textContent = username;
document.getElementById('header-username').textContent = username;
});
}
if (selfIntroInput) {
selfIntroInput.addEventListener('input', function() {
const selfIntro = this.value.trim() || 'まだ自己紹介がありません';
document.getElementById('self-intro-preview').textContent = selfIntro;
});
}
});
// === 初期化実行 ===
window.addEventListener('load', function() {
// アプリ初期化
initializeApp();
// プロフィール情報を入力フィールドに設定
document.getElementById('username').value = profile.username || '';
document.getElementById('self-intro').value = profile.selfIntro || '';
// ページ終了時のクリーンアップ
window.addEventListener('beforeunload', function() {
stopBotAutoPost();
stopRSSAutoPost();
saveData();
});
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Verse – プロダクション級ソーシャルVR</title>
<!-- A-Frame Core -->
<script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
<!-- Networked-AFrame for multi-user sync -->
<script src="https://unpkg.com/networked-aframe@0.8.0/dist/networked-aframe.min.js"></script>
<!-- GUI for VR buttons -->
<script src="https://unpkg.com/aframe-gui/dist/aframe-gui.min.js"></script>
<!-- Environment presets -->
<script src="https://unpkg.com/aframe-environment-component/dist/aframe-environment-component.min.js"></script>
<!-- Extras: teleport, locomotion, physics, super-hands -->
<script src="https://unpkg.com/aframe-extras@6.1.1/dist/aframe-extras.min.js"></script>
<script src="https://unpkg.com/aframe-super-hands-component@4.0.3/dist/aframe-super-hands.min.js"></script>
<!-- Socket.IO for signaling -->
<script src="https://cdn.socket.io/4.5.0/socket.io.min.js"></script>
<!-- Simple-Peer for WebRTC voice chat -->
<script src="https://unpkg.com/simple-peer@9.11.0/simplepeer.min.js"></script>
<style>
body { margin: 0; }
#vr-scene { width: 100%; height: 100vh; }
.interactive:hover { animation: pulse 0.5s infinite alternate; }
@keyframes pulse { to { scale: 1.05; } }
</style>
</head>
<body>
<a-scene id="vr-scene"
networked-scene="room: verse-room; serverURL: https://YOUR_SIGNAL_SERVER; app: verse-vr; debug: false"
environment="preset: forest; groundColor:#445; skyColor:#889"
extras="teleportControls: true; locomotionControls: true"
physics="gravity: -9.8; debug: false">
<!-- Assets -->
<a-assets>
<audio id="click-sound" src="click.mp3"></audio>
<a-asset-item id="avatarModel" src="avatar.glb"></a-asset-item>
<img id="panel-bg" src="panel-bg.png" />
</a-assets>
<!-- Camera Rig -->
<a-entity id="cameraRig" position="0 1.6 0"
locomotion-controls="fly:false; speed:4"
teleport-controls="button: trigger; collisionEntities: #ground">
<a-entity camera look-controls>
<a-cursor fuse="true" fuseTimeout="300" material="color:cyan; shader:flat"></a-cursor>
</a-entity>
<a-entity hand-tracking-controls="hand: left" super-hands></a-entity>
<a-entity hand-tracking-controls="hand: right" super-hands></a-entity>
</a-entity>
<!-- Ground -->
<a-plane id="ground" position="0 0 0" rotation="-90 0 0" width="50" height="50" color="#444" static-body></a-plane>
<!-- Avatar Sync Template -->
<template id="avatar-template">
<a-entity>
<a-gltf-model src="#avatarModel" scale="0.6 0.6 0.6"></a-gltf-model>
</a-entity>
</template>
<a-entity networked-avatar networked="template:#avatar-template; attachTemplateToLocal:false"></a-entity>
<!-- GUI Menu -->
<a-gui-flex-container id="menu" flex-direction="row" justify-content="space-around"
panel-width="2" panel-height="0.2"
component-padding="0.05 0.1"
position="0 2 -1.5" material="src:#panel-bg; transparent:true">
<a-gui-button id="btnTextPost" value="Text Post" on-click="openTextInput()" font-size="28px" color="black"></a-gui-button>
<a-gui-button id="btnVoicePost" value="Voice Post" on-click="startVoiceRecognition()" font-size="28px" color="black"></a-gui-button>
<a-gui-button id="btnGPTChat" value="GPT BOT" on-click="callGPTBot()" font-size="28px" color="black"></a-gui-button>
<a-gui-button id="btnLikeMode" value="Like/Delete" on-click="toggleLikeMode()" font-size="28px" color="black"></a-gui-button>
<a-gui-button id="btnSpawnCube" value="Spawn Cube" on-click="spawnCube()" font-size="28px" color="black"></a-gui-button>
<a-gui-button id="btnVoiceChat" value="Voice Chat" on-click="toggleVoiceChat()" font-size="28px" color="black"></a-gui-button>
</a-gui-flex-container>
<!-- Containers -->
<a-entity id="post-container"></a-entity>
<a-entity id="ugc-container"></a-entity>
</a-scene>
<script>
// ----------- データ -----------
let posts = JSON.parse(localStorage.getItem('posts')||'[]');
let likeMode = false;
let recognition;
let socket = io('https://YOUR_SIGNAL_SERVER');
let peers = {};
let localStream;
// ----------- 投稿レンダリング -----------
function renderPosts() {
const container = document.getElementById('post-container');
container.innerHTML = '';
posts.forEach((p,i)=>{
const angle = (i/posts.length)*Math.PI*2;
const x = Math.cos(angle)*2;
const z = Math.sin(angle)*2;
const postEl = document.createElement('a-entity');
postEl.classList.add('interactive');
postEl.setAttribute('geometry','primitive: plane; width:1.2; height:0.5');
postEl.setAttribute('material','color:#fff; shader:flat');
postEl.setAttribute('position',`${x} 1.3 ${z}`);
postEl.setAttribute('rotation',`0 ${-angle*180/Math.PI+90} 0`);
postEl.setAttribute('text',`value:${p.content}; width:1.1; align:center; color:#000`);
// クリック処理
postEl.addEventListener('click', ()=>{
if(likeMode) {
p.likes = (p.likes||0)+1;
savePosts(); renderPosts();
}
});
// 削除ボタン
if(likeMode) {
const delBtn = document.createElement('a-entity');
delBtn.setAttribute('geometry','primitive: plane; width:0.2; height:0.1');
delBtn.setAttribute('material','color:#f88; shader:flat');
delBtn.setAttribute('position','0.55 -0.25 0.01');
delBtn.setAttribute('text','value:Del; width:0.2; align:center; color:#000');
delBtn.addEventListener('click', ()=>{ posts.splice(i,1); savePosts(); renderPosts(); });
postEl.appendChild(delBtn);
}
container.appendChild(postEl);
});
}
function savePosts(){ localStorage.setItem('posts', JSON.stringify(posts)); }
// ----------- テキスト投稿 -----------
window.openTextInput = ()=>{
const txt = prompt('投稿内容を入力してください');
if(txt){ posts.unshift({content:txt, likes:0}); savePosts(); renderPosts(); socket.emit('new-post', txt); }
};
// ----------- 音声投稿 -----------
window.startVoiceRecognition = ()=>{
if(!('webkitSpeechRecognition' in window)) return alert('音声認識非対応');
recognition = new webkitSpeechRecognition();
recognition.lang='ja-JP'; recognition.interimResults=false;
recognition.onresult=e=>{ const txt=e.results[0][0].transcript; posts.unshift({content:txt,likes:0}); savePosts(); renderPosts(); };
recognition.onerror=()=>alert('認識エラー'); recognition.start();
};
// ----------- GPT BOT -----------
window.callGPTBot = async ()=>{
const key='YOUR_OPENAI_API_KEY';
const res=await fetch('https://api.openai.com/v1/chat/completions',{ method:'POST',
headers:{'Content-Type':'application/json','Authorization':`Bearer ${key}`},
body:JSON.stringify({ model:'gpt-4o-mini', messages:[{role:'system',content:'あなたはVR BOTです。'}]
.concat(posts.slice(0,5).map(p=>({role:'user',content:p.content}))), max_tokens:50 })
});
const js=await res.json();
const txt=js.choices[0].message.content.trim();
posts.unshift({content:`🤖 GPT: ${txt}`,likes:0}); savePosts(); renderPosts();
};
// ----------- いいね/削除モード -----------
window.toggleLikeMode = ()=>{ likeMode=!likeMode; renderPosts(); };
// ----------- UGC: キューブ生成 -----------
window.spawnCube = ()=>{
const c=document.createElement('a-box');
c.setAttribute('class','interactive');
c.setAttribute('position','0 1 -1'); c.setAttribute('depth','0.5'); c.setAttribute('height','0.5'); c.setAttribute('width','0.5');
c.setAttribute('material','color:#4CC3D9'); c.setAttribute('dynamic-body','');
c.setAttribute('grabbable',''); c.setAttribute('stretchable','');
document.getElementById('ugc-container').appendChild(c);
};
// ----------- Voice Chat -----------
window.toggleVoiceChat = ()=>{ localStream?stopVoiceChat():startVoiceChat(); };
async function startVoiceChat() {
localStream = await navigator.mediaDevices.getUserMedia({audio:true});
socket.emit('join-voice');
socket.on('signal', ({ id, signal })=>{
if(!peers[id]) createPeer(id, false);
peers[id].signal(signal);
});
socket.on('user-joined', id=>{ createPeer(id, true); });
function createPeer(id, initiator) {
const peer = new SimplePeer({ initiator, trickle:false, stream:localStream });
peer.on('signal', signal=>{ socket.emit('signal',{ id: socket.id, to:id, signal }); });
peer.on('stream', stream=>{ const e=document.createElement('audio'); e.srcObject=stream; e.autoplay=true; document.body.appendChild(e); });
peers[id]=peer;
}
}
function stopVoiceChat(){ Object.values(peers).forEach(p=>p.destroy()); peers={}; localStream.getTracks().forEach(t=>t.stop()); localStream=null; }
// ----------- Socket.IO イベント -----------
socket.on('new-post', txt=>{ posts.unshift({content:txt,likes:0}); savePosts(); renderPosts(); });
// ----------- 初期ロード -----------
document.querySelector('a-scene').addEventListener('loaded', ()=>{ renderPosts(); });
</script>
</body>
</html>