public/dev_tools.js
分割 2 / 10 · 元ファイル 184〜370行付近
navigator.clipboard.writeText(txt).then(()=>done(true)).catch(()=>{ done(false); manualCopy(txt); });
} else { done(false); manualCopy(txt); }
}
function manualCopy(txt){
const w=document.createElement('div');
w.style.cssText='position:fixed; inset:8% 5%; z-index:999999; background:rgba(12,20,16,.97);'
+'border:1px solid #4d7a5f; border-radius:12px; padding:10px; display:flex; flex-direction:column; gap:8px;';
const ta=document.createElement('textarea');
ta.value=txt; ta.readOnly=true;
ta.style.cssText='flex:1; background:#0b130f; color:#cfe0d5; font:10px/1.4 monospace; border:0; border-radius:8px; padding:8px;';
const bt=document.createElement('button');
bt.textContent='閉じる';
bt.style.cssText='padding:10px; border:0; border-radius:9px; background:#28533f; color:#f7f0dd; font-weight:bold;';
bt.addEventListener('click',()=>w.remove());
w.appendChild(ta); w.appendChild(bt);
document.body.appendChild(w);
ta.focus(); ta.select();
}
function toast(msg){
const t=document.createElement('div');
t.textContent=msg;
t.style.cssText='position:fixed; left:50%; top:14%; transform:translateX(-50%); z-index:999999;'
+'background:rgba(12,20,16,.92); color:#f7f0dd; font:bold 12px/1.4 -apple-system,sans-serif;'
+'padding:9px 14px; border-radius:9px; border:1px solid #4d7a5f; pointer-events:none; white-space:nowrap;';
document.body.appendChild(t);
const a=t.animate([{opacity:0},{opacity:1,offset:.1},{opacity:1,offset:.8},{opacity:0}],{duration:1900});
a.finished.then(()=>t.remove()).catch(()=>t.remove());
}
/* ---------- メモ帳(2026-08-07 K1指示) ----------
開いたら毎回さっきの続き(書きかけは自動保存)。保存=1行目を見出しに一覧へ入れて新規に。
削除=書きかけを捨てて新規に。本文コピーあり。保存済みは見出しタップで続きを書ける */
const MEMO_DRAFT='hzg_memo_draft_v1', MEMO_LIST='hzg_memo_list_v1';
let memoWin=null;
function memoList(){ try{ return JSON.parse(localStorage.getItem(MEMO_LIST)||'[]')||[]; }catch(e){ return []; } }
function openMemo(){
if(memoWin){ memoWin.remove(); memoWin=null; }
const w=document.createElement('div');
memoWin=w;
w.style.cssText='position:fixed; inset:6% 4% 8%; z-index:999999; display:flex; flex-direction:column; gap:7px;'
+'background:rgba(12,20,16,.97); border:1px solid #4d7a5f; border-radius:13px; padding:10px;';
const ta=document.createElement('textarea');
ta.placeholder='メモ(書きかけは勝手に残る)';
ta.value=localStorage.getItem(MEMO_DRAFT)||'';
ta.style.cssText='flex:1; min-height:0; background:#0b130f; color:#eee6d4; font:13px/1.6 -apple-system,sans-serif;'
+'border:1px solid #2c4638; border-radius:9px; padding:9px; resize:none; -webkit-user-select:text; user-select:text;';
ta.addEventListener('input', ()=>localStorage.setItem(MEMO_DRAFT, ta.value));
const row=document.createElement('div');
row.style.cssText='display:flex; gap:6px;';
const mkB=(label,bg,fn)=>{
const b=document.createElement('button');
b.textContent=label;
b.style.cssText='flex:1; padding:11px 4px; border:0; border-radius:9px; background:'+bg+';'
+'color:#f7f0dd; font:bold 12px/1 -apple-system,sans-serif;';
b.addEventListener('click', ev=>{ ev.stopPropagation(); fn(); });
row.appendChild(b);
};
const list=document.createElement('div');
list.style.cssText='flex:0 0 auto; max-height:32%; overflow-y:auto; display:flex; flex-direction:column; gap:4px;';
const renderList=()=>{
list.innerHTML='';
for(const [i,m] of memoList().entries()){
const r=document.createElement('div');
r.style.cssText='display:flex; align-items:center; gap:6px; background:#1a2a21; border:1px solid #2c4638;'
+'border-radius:8px; padding:8px 9px;';
const t=document.createElement('span');
t.textContent=m.t||'(無題)';
t.style.cssText='flex:1; font:bold 11.5px/1.3 -apple-system,sans-serif; color:#cfe0d5;'
+'white-space:nowrap; overflow:hidden; text-overflow:ellipsis;';
t.addEventListener('click', ()=>{ // 見出しタップ=そのメモの続きを書く(一覧からは外す)
const arr=memoList(); const [mm]=arr.splice(i,1);
localStorage.setItem(MEMO_LIST, JSON.stringify(arr));
ta.value=mm.body; localStorage.setItem(MEMO_DRAFT, mm.body);
renderList();
});
const x=document.createElement('button');
x.textContent='✕';
x.style.cssText='flex:0 0 auto; width:30px; height:30px; border:0; border-radius:7px;'
+'background:#7a4040; color:#f7f0dd; font:bold 12px/1 sans-serif;';
x.addEventListener('click', ()=>{
const arr=memoList(); arr.splice(i,1);
localStorage.setItem(MEMO_LIST, JSON.stringify(arr));
renderList();
});
r.appendChild(t); r.appendChild(x); list.appendChild(r);
}
};
mkB('保存','#28533f',()=>{
const v=ta.value.trim();
if(!v){ toast('空のメモは保存しない'); return; }
const arr=memoList();
arr.unshift({ t:v.split('\n')[0].slice(0,40), body:ta.value, at:Date.now() });
localStorage.setItem(MEMO_LIST, JSON.stringify(arr));
ta.value=''; localStorage.setItem(MEMO_DRAFT,'');
renderList(); toast('保存した(新規メモに)');
});
mkB('本文コピー','#3f6f56',()=>{
const v=ta.value;
if(navigator.clipboard && navigator.clipboard.writeText){
navigator.clipboard.writeText(v).then(()=>toast('本文をコピーした')).catch(()=>manualCopy(v));
} else manualCopy(v);
});
mkB('削除','#7a4040',()=>{ ta.value=''; localStorage.setItem(MEMO_DRAFT,''); toast('消して新規メモに'); });
mkB('閉じる','#5d5040',()=>{ w.remove(); memoWin=null; });
w.appendChild(ta); w.appendChild(row); w.appendChild(list);
w.addEventListener('pointerdown', ev=>ev.stopPropagation());
document.body.appendChild(w);
renderList();
}
/* ---------- ④ UIレイアウト編集モード(2026-08-07 K1指示) ----------
対象のUI部品をドラッグで動かし、動かした差分だけをJSONで吐く。
差分はlocalStorage(hzg_uiedit_v1)にも残して次回起動時も適用——
K1がJSONをくれたらコードへ焼き込み、リセットで消す運用 */
const UIED_KEY='hzg_uiedit_v1';
/* 【2026-09-02 K1指摘の根治】K1「デプロイごとのUIの差分をJSONで吐かないといけないところを、
UIレイアウト編集を開くごとの差分にしてないか」——そのとおりだった。
端末に残った調整値はコードの値の上に重ねて掛かるので、こちらが数値を焼き込むと
「焼いた値+端末の値」の二重になり、次に出す差分は**焼き込み後の位置からの差**になっていた。
これでは何度やっても合わない。
直し方: 調整値に**その時の版番号**を付けて持ち、版が変わったら丸ごと捨てる。
つまり調整値は必ず「今デプロイされているコードの値」からの差になる。
部品ごとに手で UIED_BAKED_IDS へ足していく運用も要らなくなる */
function uiedVer(){
try{
const js=document.querySelector('script[src*="raid_battle.js"]');
return String((js && js.getAttribute('src')) || '').split('?')[1] || '?';
}catch(e){ return '?'; }
}
/* 焼き込みの印(2026-08-26 K1報告の根治): K1が調整した値をコードへ焼いた時は、この文字列を変える。
端末に残っている調整値は、焼いた値と二重に掛かって「調整が無かったことにされた」ように見える——
実際に下段ラベルで発生した(焼き込みで11px下げ+端末の11pxが1.2秒後の再適用で乗り、22px下がった)。
印が変わっていたら、起動時に端末の調整値を自動で捨てる(=リセットを押したのと同じ)。
K1に「リセットを押してください」と頼まなくて済むようにするための仕掛け */
const UIED_BAKED='20260901v';
const UIED_BAKED_KEY='hzg_uiedit_baked';
/* armoryRowは armory の中にあるので、**必ずarmoryより先**に並べる——
先に書いた方が掴める(2026-09-02) */
const UIED_IDS=['armoryRow','spPanel','wazaBtn','ctrlFab','pauseTag','dmgLbl','itemBtn','raidTestClose','armory','readyBtn','sleepBtn','goPop','hdr',
'rationBar','amTidy','setBtn',
'partyAsk', // 「誰が行きますか」バー(2026-08-21 K1指示)
/* 下段ラベル(2026-08-26 K1指示「素材に圧迫感がある」)。板と中身を別々に動かせる:
tabBar=ドックの板そのもの / tabRow=アイコンと文字の5個ぶんまとめて。
大きさのつまみ(%)で縮めれば圧迫感を弱められる */
'tabBar','tabRow',
/* 所持金と探索ライブ中継の小窓(2026-09-01 K1指示)。
小窓は**探索に出している間しか出ない**ので、動かす時は隊を出してから編集モードに入る */
'moneyHud','lfChip',
/* インベントリ(2026-09-02 K1指示)。**中身と縁を別々に動かす**:
・armoryRow = 中のアイテム欄(66pxのマスの格子)そのもの
・armory = 外の縁とタブボタンが合体した板ぜんぶ
armoryRowをarmoryより先に並べる——重なっているので、先に書いた方が掴める。
縁(armory)を大きくしても中のマスは大きくならない(下のuiedRowSyncが打ち消す)ので、
「縁だけ広げて、中身は中身で動かす」ができる */
];
/* 【2026-09-02 K1指摘の根治その2】つまみは開くと必ず100%から始まり、
触った瞬間にCSSへ焼いてある値(例 #armory の scale:.86)を**上書き**していた。
つまり編集を始めた瞬間に部品が跳び、K1は「今どこにいるか」を知らないまま調整していた。
位置も同じで、CSSに translate が焼いてある部品は掴んだ瞬間0へ戻っていた。
直し方: 初めて掴んだ時に**今そう見えている値**(computed)を読んで、そこから始める。
これでつまみの数字は常に「今の姿」を指し、出てくる差分は
**そのままCSSへ書ける最終の値**になる(こちらで掛け算しない=ずれる余地が消える) */
function uiedInit(el){
const o={dx:0, dy:0};
try{
const cs=getComputedStyle(el);
const t=cs.translate;
if(t && t!=='none'){ const p=t.split(/\s+/); o.dx=parseFloat(p[0])||0; o.dy=parseFloat(p[1]||'0')||0; }
const sc=cs.scale;
if(sc && sc!=='none'){ const v=parseFloat(sc); if(v>0) o.s=v; }
}catch(e){}
return o;
}
/* 縁(armory)の拡大を中のアイテム欄に効かせない。
armoryRow自身のずらし・大きさは、その上に足す */
function uiedRowSync(){
const el=uiedEl('armoryRow'); if(!el) return;
const S=(uiedOff.armory && uiedOff.armory.s) || 1;
const r=uiedOff.armoryRow || {};
const rs=(r.s!=null? r.s : 1);
el.style.scale=String(rs/S);
el.style.translate=((r.dx||0)/S).toFixed(1)+'px '+((r.dy||0)/S).toFixed(1)+'px';
}
let uiedOn=false, uiedOff={}, uiedBar=null, uiedDrag=null, uiedLast=null, uiedSzInput=null, uiedSzLabel=null;
try{
const raw=JSON.parse(localStorage.getItem(UIED_KEY)||'null');
if(raw && raw.__ver!==undefined){