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);