public/dev_tools.js

分割 9 / 10 · 元ファイル 1467〜1637行付近

原文の連続部分です。長い圧縮行は行の途中で分割する場合があります。全分割を順に連結するとファイル全文になります。

      g.clearRect(0,0,W,H);
      for(var i=0;i<strokes.length;i++){
        var st=strokes[i]; if(st.p.length<2) continue;
        g.strokeStyle=st.c; g.lineWidth=st.w;
        g.beginPath(); g.moveTo(st.p[0].x, st.p[0].y);
        for(var q=1;q<st.p.length;q++) g.lineTo(st.p[q].x, st.p[q].y);
        g.stroke();
      }
    }
    function pt(ev){ var r=pen.getBoundingClientRect();
      return {x:(ev.clientX-r.left)*(W/r.width), y:(ev.clientY-r.top)*(H/r.height)}; }
    pen.addEventListener('pointerdown', function(ev){
      if(id!==null) return; id=ev.pointerId;
      try{ pen.setPointerCapture(id); }catch(e){}
      cur={c:col, w:Math.max(3, Math.round(W/110)), p:[pt(ev)]};
      strokes.push(cur); ev.preventDefault();
    });
    pen.addEventListener('pointermove', function(ev){
      if(ev.pointerId!==id || !cur) return;
      cur.p.push(pt(ev)); redraw(); ev.preventDefault();
    });
    function up(ev){ if(ev.pointerId!==id) return; id=null; cur=null; }
    pen.addEventListener('pointerup', up);
    pen.addEventListener('pointercancel', up);

    bUndo.onclick=function(){ strokes.pop(); redraw(); };
    bClr.onclick=function(){ strokes.length=0; redraw(); };
    bCancel.onclick=function(){ w.remove(); };
    bOk.onclick=function(){
      var out=document.createElement('canvas'); out.width=W; out.height=H;
      var o=out.getContext('2d');
      o.drawImage(base,0,0); o.drawImage(pen,0,0);
      var q=0.72, url=out.toDataURL('image/jpeg', q);
      while(url.length>BB_SHOT_BYTES && q>0.35){ q-=0.12; url=out.toDataURL('image/jpeg', q); }
      w.remove();
      cb(url);
    };
  };
  im.onerror=function(){ toast('その絵は読めませんでした'); };
  im.src=src;
}
/* 送った後の姿。ここで初めて「間違えて送った」を出す */
function bbQuickDone(box, note, ok){
  box.innerHTML='';
  const h=document.createElement('div');
  h.style.cssText='font-size:15px;font-weight:900;letter-spacing:.04em;text-align:center;';
  h.textContent = ok ? 'ありがとうございます。送りました。' : '送れませんでした';
  box.appendChild(h);
  if(!ok){   // 失敗した時だけ、どうすればいいかを1行だけ出す
    const p=document.createElement('div');
    p.style.cssText='margin-top:7px;font-size:11.5px;color:#6b6250;line-height:1.7;text-align:center;';
    p.textContent='電波の届く所でもう一度お願いします。';
    box.appendChild(p);
  }
  const row=document.createElement('div');
  row.style.cssText='display:flex; gap:8px; margin-top:13px;';
  const close=document.createElement('button');
  close.type='button'; close.textContent='閉じる';
  close.style.cssText='flex:1; padding:13px 6px; border:0; border-radius:10px; font-weight:900;'
    +'font-size:15px; background:#28533f; color:#f7f0dd;';
  close.onclick=function(ev){ ev.stopPropagation(); bbQuickClose(); };
  row.appendChild(close);
  box.appendChild(row);
  if(ok && bbLastSent){
    const oops=document.createElement('button');
    oops.type='button'; oops.textContent='間違えて送ってしまった';
    oops.style.cssText='width:100%; margin-top:9px; padding:11px 6px; border:0; border-radius:10px;'
      +'font-weight:700; font-size:13px; background:#e6dcc4; color:#7a3b32;';
    oops.onclick=function(ev){
      ev.stopPropagation();
      oops.disabled=true; oops.textContent='印を付けています…';
      fetch('/api/bug?oops=1&id='+encodeURIComponent(bbLastSent), {cache:'no-store'})
        .then(function(){ oops.textContent='「間違い」と印を付けました'; })
        .catch(function(){ oops.textContent='印を付けられませんでした'; });
    };
    box.appendChild(oops);
  }
}

/* --- 一覧の画面 --- */
function bbOpenWin(focusId){
  if(bbWin){ bbWin.remove(); bbWin=null; }
  bbWin=document.createElement('div');
  bbWin.style.cssText='position:fixed; inset:4% 3%; z-index:2147483001; background:rgba(10,16,13,.97);'
    +'border:1px solid #4d7a5f; border-radius:12px; padding:9px; display:flex; flex-direction:column; gap:7px;'
    +'font:12px/1.5 -apple-system,sans-serif; color:#e6f2ea;';
  const head=document.createElement('div');
  head.style.cssText='display:flex; align-items:center; gap:6px;';
  head.innerHTML='<b style="font-size:13px">バグ報告箱</b>'
    +'<span style="font-size:10px;opacity:.7">押した瞬間の状態を保存しました</span>';
  const close=document.createElement('button');
  close.textContent='閉じる';
  close.style.cssText='margin-left:auto; padding:6px 10px; border:0; border-radius:8px; background:#28533f; color:#f7f0dd; font-weight:bold;';
  close.onclick=function(){ bbWin.remove(); bbWin=null; bbHoldSync(); };
  head.appendChild(close);
  const listEl=document.createElement('div');
  listEl.style.cssText='flex:1; overflow:auto; display:flex; flex-direction:column; gap:6px; -webkit-overflow-scrolling:touch;';
  /* 計器の入切(2026-09-06 K1指示「規定は全部オフ」「完全にオフのモードも作れ」)。
     追う時だけ点けて、終わったら消す。切ってある計器は記録を作る所へ入らないので、
     点けていない間は本当に何もしない */
  const met=document.createElement('div');
  met.style.cssText='display:flex; gap:4px; flex-wrap:wrap; align-items:center;'
    +'padding:6px 0 2px; border-top:1px solid #3d5c4c; margin-top:4px;';
  const NAMES=['効果音','床の物','必殺技','絵の大きさ','起動','音の口','曲','画面','コマ仕事','物理','スポット'];
  function metDraw(){
    met.innerHTML='<b style="font-size:10.5px; color:#ffd88a; flex:0 0 100%">計器'
      +(HZG_METERS.全部切る?'(全部切っています)':'')+'</b>';
    const all=document.createElement('button');
    all.textContent=HZG_METERS.全部切る?'全部入れる':'全部切る';
    all.style.cssText='font-size:10.5px; font-weight:bold; padding:6px 9px; border-radius:8px;'
      +'border:1px solid #b0523f; background:'+(HZG_METERS.全部切る?'#c2503a':'#2a1a16')+';'
      +'color:#f7f0dd;';
    all.onclick=function(){ HZG_METERS.set('全部切る', !HZG_METERS.全部切る); metDraw(); };
    met.appendChild(all);
    NAMES.forEach(function(nm){
      const b=document.createElement('button');
      const on=HZG_METERS.on(nm);
      b.textContent=nm;
      b.style.cssText='font-size:10.5px; font-weight:bold; padding:6px 9px; border-radius:8px;'
        +'border:1px solid #4d7a5f; background:'+(on?'#d98d2b':'#22322a')+';'
        +'color:'+(on?'#2b1a06':'#7f8f99')+';'
        +(HZG_METERS.全部切る?'opacity:.45;':'');
      b.onclick=function(){ HZG_METERS.set(nm, !HZG_METERS[nm]); metDraw(); };
      met.appendChild(b);
    });
    const note=document.createElement('div');
    note.style.cssText='flex:0 0 100%; font-size:9.5px; color:#8fa2ab; line-height:1.6; margin-top:3px;';
    note.textContent='橙=点いている。既定は「効果音・床の物・必殺技・絵の大きさ」が消えています'
      +'(毎回DOMを触ったり入れ物を作るので重い)。追う時だけ点けてください。';
    met.appendChild(note);
  }
  metDraw();
  const foot=document.createElement('div');
  foot.style.cssText='display:flex; gap:5px; flex-wrap:wrap;';
  bbWin.appendChild(head); bbWin.appendChild(listEl); bbWin.appendChild(met); bbWin.appendChild(foot);
  document.body.appendChild(bbWin);

  const sel={};
  function draw(){
    return bbAll().then(function(list){
      list.sort(function(a,b){ return a.at<b.at?1:-1; });
      listEl.innerHTML='';
      if(!list.length){ listEl.innerHTML='<div style="opacity:.6">まだ1件もありません</div>'; return; }
      list.forEach(function(rep){
        const row=document.createElement('div');
        row.style.cssText='background:#182821; border:1px solid #33564a; border-radius:9px; padding:7px;'
          +(rep.id===focusId?'outline:2px solid #d98d2b;':'');
        const t=new Date(rep.at);
        const hh=String(t.getHours()).padStart(2,'0')+':'+String(t.getMinutes()).padStart(2,'0');
        const r=rep.raid||{};
        const where=rep.screen ? (rep.screen.day+'日目 '+(rep.screen.phase||'')) : '(教室の外)';
        const info=[hh, where, (rep.draw==='webgl'?'GPU':'標準'),
          (r.zombies?('敵'+r.zombies.length):''),
          (rep.fps?(rep.fps.fps+'fps 最悪'+rep.fps.最悪+'ms'):''),
          ((rep.errors&&rep.errors.length)?('エラー'+rep.errors.length):'')].filter(Boolean).join(' / ');
        const line=document.createElement('div');
        line.style.cssText='display:flex; align-items:center; gap:6px;';
        const cb=document.createElement('input'); cb.type='checkbox'; cb.checked=!!sel[rep.id];
        cb.onchange=function(){ sel[rep.id]=cb.checked; };
        const lab=document.createElement('span'); lab.textContent=info;
        lab.style.cssText='flex:1; font-size:11px;';
        const del=document.createElement('button'); del.textContent='削除';
        del.style.cssText='padding:5px 8px; border:0; border-radius:7px; background:#7a2f23; color:#ffe9e5; font-size:11px;';
        del.onclick=function(){ bbDel(rep.id).then(function(){ delete sel[rep.id]; bbRefresh(); draw(); }); };
        line.appendChild(cb); line.appendChild(lab); line.appendChild(del);
        const memo=document.createElement('input');
        memo.placeholder='何が起きた?(後から書ける)'; memo.value=rep.memo||'';
        memo.style.cssText='width:100%; margin-top:5px; padding:6px; border-radius:7px; border:1px solid #3d6b58;'
          +'background:#0e1a15; color:#e6f2ea; font-size:12px;';
        memo.onchange=function(){ rep.memo=memo.value; bbPut(rep); };
        row.appendChild(line); row.appendChild(memo);