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(){});