public/dev_tools.js
分割 7 / 10 · 元ファイル 1105〜1271行付近
try{ if(typeof window.HZG_SNAPSHOT==='function') rep.raid=window.HZG_SNAPSHOT(); }catch(e){ rep.raidErr=String(e).slice(0,200); }
try{ rep.dom=domCount(); }catch(e){}
/* BGMの内部状態(2026-08-21 K1報告「小刻みにリピート」の原因特定用の覗き窓)。
曲名・どちら側が鳴っているか・再生位置・終端との距離・停止/終了/エラーの旗を写す */
try{
const out={wanted:(typeof bgmWanted!=='undefined')?bgmWanted:undefined,
playing:(typeof bgmSrc!=='undefined')?!!bgmSrc:undefined,
ctx:(typeof actx!=='undefined'&&actx)?actx.state:null, tracks:{}};
if(typeof bgmEls!=='undefined') for(const k in bgmEls){
const e2=bgmEls[k]; if(!e2) continue;
const side=s2=>({t:+(s2.el.currentTime||0).toFixed(2), dur:+(s2.el.duration||0).toFixed(2),
paused:s2.el.paused, ended:s2.el.ended, ready:s2.el.readyState,
gain:+(s2.g&&s2.g.gain?s2.g.gain.value:0).toFixed(3),
err:s2.el.error?String(s2.el.error.code):null});
out.tracks[k]={cur:e2.cur?(e2.cur===e2.a?'a':'b'):null, timer:!!e2.timer, a:side(e2.a), b:side(e2.b)};
}
const rb=window.__HZG_BGM_EL||{};
for(const k in rb){ const e3=rb[k]; if(e3&&e3.el) out.tracks[k]={t:+(e3.el.currentTime||0).toFixed(2),
paused:e3.el.paused, ended:e3.el.ended, gain:+(e3.g&&e3.g.gain?e3.g.gain.value:0).toFixed(3)}; }
rep.bgm=out;
}catch(e){ rep.bgmErr=String(e).slice(0,120); }
/* IndexedDBはDOM要素や循環参照を保存できないので、ここで安全な形へ落としてから返す */
try{ return JSON.parse(safeJson(rep)); }
catch(e){ return {id:rep.id, at:rep.at, memo:'', err:'保存できない値が混じっていた: '+String(e).slice(0,120)}; }
}
/* --- 常設ボタンと件数バッジ --- */
const BB_MARU='①②③④⑤⑥⑦⑧⑨⑩';
function bbBuildBtn(){
if(bbBtn) return;
bbBtn=document.createElement('button');
bbBtn.id='hzgBugBtn';
bbBtn.type='button';
/* 置き場所(2026-08-25 K1指示): 画面右の縦置きをやめ、下段ラベルの工作(右端)の真上へ横書きで置く。
左右は工作ボタンの中心(5個並びなので幅の90%)に合わせ、下は下段ラベルのすぐ上(2026-08-25 K1指示でさらに10px下げた。--tabHは index.html の :root) */
bbBtn.style.cssText='position:fixed; left:90%; bottom:calc(var(--tabH, 62px) - 4px); transform:translateX(-50%); z-index:2147483000;'
+'font:bold 11px/1.15 -apple-system,sans-serif; color:#fff2e0; background:rgba(150,40,30,.8);'
+'border:1px solid rgba(255,150,120,.65); border-radius:9px; padding:5px 8px; letter-spacing:.06em;'
+'white-space:nowrap; box-shadow:0 2px 8px rgba(0,0,0,.5);'
+'-webkit-user-select:none; user-select:none; -webkit-tap-highlight-color:transparent;';
/* 押し方で分ける(2026-08-28 K1指示。テスターに使いやすく)。
・軽く押す = 「何が起きた?」だけ書いて送る小さな窓(テスター用)
・長押し = 今までの報告箱(K1用。一覧・選択・書き出し)
どちらの場合も、窓を出す前に必ず状態を確定する——画面を触ると状態が変わるため */
var bbHold=null, bbLong=false;
function bbStart(){ bbLong=false; clearTimeout(bbHold); bbTimeHold(true);
bbHold=setTimeout(function(){ bbLong=true;
const rep=bbCapture();
bbPut(rep).then(function(){ bbRefresh(); bbOpenWin(rep.id); })
.catch(function(e){ toast('報告の保存に失敗: '+e); });
}, 600); }
function bbEnd(ev){ clearTimeout(bbHold);
if(bbLong){ bbLong=false; if(ev){ ev.preventDefault(); ev.stopPropagation(); } return; }
if(ev){ ev.preventDefault(); ev.stopPropagation(); }
bbQuickOpen();
}
bbBtn.addEventListener('pointerdown', function(ev){ ev.stopPropagation(); bbStart(); });
bbBtn.addEventListener('pointerup', bbEnd);
bbBtn.addEventListener('pointercancel', function(){ clearTimeout(bbHold); bbLong=false; });
bbBtn.addEventListener('pointerleave', function(){ clearTimeout(bbHold); });
bbBtn.addEventListener('click', function(ev){ ev.preventDefault(); ev.stopPropagation(); });
document.body.appendChild(bbBtn);
bbRefresh();
}
function bbRefresh(){
bbAll().then(function(list){
bbCount=list.length;
if(bbBtn) bbBtn.textContent='報告'+(bbCount?BB_MARU[Math.min(9,bbCount-1)]:'');
}).catch(function(){});
}
/* --- テスター用の小さな窓(2026-08-28 K1指示) ---
軽く押した時に出る。書くのは「何が起きたか」だけで、裏では今までどおり
押した瞬間の状態をJSONで確定して端末へ保存し、そのままサーバーへ送る。
送った直後だけ「間違えて送った」を出す——消しはせず、印だけ付ける
(K1が一覧で「これは間違いだな」と目で分かればいい、という指示) */
var bbQuickWin=null, bbLastSent=null;
function bbQuickClose(){ if(bbQuickWin){ bbQuickWin.remove(); bbQuickWin=null; } bbHoldSync(); }
function bbQuickOpen(){
if(bbQuickWin) return;
const rep=bbCapture(); /* ★窓を出す前に確定する */
const w=document.createElement('div');
bbQuickWin=w;
w.style.cssText='position:fixed; inset:0; z-index:2147483002; display:flex; align-items:center;'
+'justify-content:center; padding:18px; background:rgba(6,10,8,.72);'
+'font:13px/1.6 -apple-system,"Hiragino Kaku Gothic ProN",sans-serif;';
const box=document.createElement('div');
box.style.cssText='width:100%; max-width:330px; background:#f7f2e2; color:#2e3a2e; border-radius:14px;'
+'padding:16px 15px 14px; box-shadow:0 10px 30px rgba(0,0,0,.6);';
box.innerHTML='<div style="font-size:15px;font-weight:900;letter-spacing:.04em">なにが起こったか教えてください</div>'
+'<div style="margin-top:5px;font-size:11px;color:#6b6250;line-height:1.7">'
+'簡単な説明で大丈夫です。画面の様子は自動で一緒に送られます。</div>';
const ta=document.createElement('textarea');
ta.placeholder='例: 扉を押したら動かなくなった';
ta.style.cssText='width:100%; height:92px; margin-top:10px; padding:9px; border-radius:9px;'
+'border:1px solid #b3a888; background:#fffdf5; color:#2e3a2e;'
+'font:14px/1.6 inherit; resize:none; -webkit-appearance:none;';
box.appendChild(ta);
/* 画面の絵(2026-09-02 K1指示)。文字だけでも送れるし、絵に印を付けても送れる */
const shotRow=document.createElement('div');
shotRow.style.cssText='display:flex; gap:8px; align-items:center; margin-top:10px;';
const shotBtn=document.createElement('button');
shotBtn.type='button'; shotBtn.textContent='画面の絵を付ける';
shotBtn.style.cssText='flex:1; padding:11px 6px; border:1px dashed #a8997a; border-radius:10px;'
+'font-weight:900; font-size:13px; background:#efe8d4; color:#4a4436;';
const shotThumb=document.createElement('img');
shotThumb.style.cssText='display:none; width:46px; height:46px; object-fit:cover;'
+'border-radius:8px; border:1px solid #b3a888;';
const shotDel=document.createElement('button');
shotDel.type='button'; shotDel.textContent='外す';
shotDel.style.cssText='display:none; padding:11px 10px; border:0; border-radius:10px;'
+'font-weight:900; font-size:12px; background:#ddd5c0; color:#4a4436;';
shotRow.appendChild(shotBtn); shotRow.appendChild(shotThumb); shotRow.appendChild(shotDel);
box.appendChild(shotRow);
const shotHint=document.createElement('div');
shotHint.style.cssText='margin-top:5px; font-size:10.5px; color:#8a8069; line-height:1.7;';
shotHint.textContent='押すと今の画面がそのまま出ます。気になる所を指でなぞってください。';
box.appendChild(shotHint);
function shotShow(){
const has=!!rep.shot;
shotThumb.style.display=has?'block':'none';
shotDel.style.display=has?'block':'none';
if(has) shotThumb.src=rep.shot;
shotBtn.textContent=has?'描き直す':'画面の絵を付ける';
shotHint.textContent=has
? ('絵を付けました('+Math.round(rep.shot.length/1024)+'KB)。押すと描き直せます。')
: '押すと今の画面がそのまま出ます。気になる所を指でなぞってください。';
}
shotBtn.onclick=function(ev){ ev.stopPropagation();
shotBtn.disabled=true; shotBtn.textContent='画面を写しています…';
/* 報告の窓が写り込まないよう、写している間だけ隠す */
var w0=bbQuickWin; if(w0) w0.style.visibility='hidden';
bbShotNow().then(function(url){
if(w0) w0.style.visibility='';
shotBtn.disabled=false; shotShow();
if(url) bbShotEdit(url, function(u){ rep.shot=u; shotShow(); });
else toast('画面を写せませんでした');
}).catch(function(e){
if(w0) w0.style.visibility='';
shotBtn.disabled=false; shotShow(); toast('画面を写せなかった: '+e.message);
});
};
shotDel.onclick=function(ev){ ev.stopPropagation(); rep.shot=null; shotShow(); };
const row=document.createElement('div');
row.style.cssText='display:flex; gap:8px; margin-top:12px;';
const mk=function(label,bg,fg){
const b=document.createElement('button'); b.type='button'; b.textContent=label;
b.style.cssText='flex:1; padding:13px 6px; border:0; border-radius:10px; font-weight:900;'
+'font-size:15px; background:'+bg+'; color:'+fg+';';
row.appendChild(b); return b; };
const cancel=mk('キャンセル','#ddd5c0','#4a4436');
const send=mk('報告する','#a94037','#fff4ea');
box.appendChild(row);
const note=document.createElement('div');
note.style.cssText='margin-top:10px; text-align:center; min-height:18px; font-size:11.5px; color:#6b6250;';
box.appendChild(note);
w.appendChild(box); document.body.appendChild(w);
setTimeout(function(){ try{ ta.focus(); }catch(e){} }, 60);
cancel.onclick=function(ev){ ev.stopPropagation(); bbQuickClose(); };
w.addEventListener('click', function(ev){ if(ev.target===w) bbQuickClose(); });
send.onclick=function(ev){
ev.stopPropagation();
send.disabled=true; cancel.disabled=true; send.textContent='送っています…';
rep.memo=ta.value.slice(0,400);
bbPut(rep).then(function(){ bbRefresh(); }).catch(function(){});