
MTG統率者戦ライフカウンタ(設定不要版)
前回の記事はこちら
前回の記事のライフカウンタは初期設定が面倒だったので、その設定をなくしたバージョンを作りました。(以下、リンクです)
https://samura162.github.io/life-counter-3/
前回からの変更点
・使う前の設定が面倒だった点を、URLを開くだけで使えるように
前回と同じ点
・無料
・複数の端末で同時にデータを共有できる
・PC / Andoroid / iPhone / タブレット相互対応
・速い
・好きに改造可能(オープンソース)
デメリット
・改造するときはちょっと時間かかる(github等が必要)
導入の前の注意点
・このツールを使用したことによるデータの消失や不具合について、制作者は責任を負いません
・商用利用可、改変可
・サポートはやってません
・リモート統率者戦を遊ぶにはDiscodeなどチャットツールが別途必要です
導入の方法
①誰か一人がURLを開く
②他に使う人がいれば、「部屋URLをコピー」ボタンを押して、メール等で生成されたURLを貼り付けて連絡(他の人が使う必要なければこの手順は不要)
③名前の欄に好きな文字を入れる(この情報はブラウザに保存されるだけなので、本名を入れてもいいですが、万が一URLが公開されてもいいように、個人情報は入れないようにしてください)
以上。
※もし「毎回URL生成するのが面倒」「ブラウザを閉じるたび名前を毎回入力するのが面倒」な方は、前回の記事のほうを利用してください。
Q&A
・動かない
まじすか
・ここをこう変えたい
→AIに要望と下記コードを張り付けて聞く→回答コードをメモ帳に張り付けてindex.htmlという名前で保存→GitHub等で公開
・ダイスの振り方は?
「振」ボタンを押してください。ダイスの面はデフォルトで1d20になってますが、変更できます。1d6で6面ダイス。振る機会は少ないと思いますが、その時はがんばって(精神論)入力して振ってください。
・画面の配置を変えたい
→AIに要望と下記コードを張り付けて聞く→回答コードをメモ帳に張り付けてindex.htmlという名前で保存→GitHub等で公開
※PCのChromeブラウザであれば、F12キー→Dimensionsでスマホの見え方が確認できます
技術的な面
サーバを立てたら面倒な設定が回避できなかったので、ブラウザ内で完結できるようにした。(Geminiが)
改造用コード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>MTG統率者戦ライフカウンタ</title>
<link rel="icon" href="favicon.png" type="image/png">
<style>
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100vh; width: 100vw; color: #fff ; font-family: sans-serif; overflow: hidden; }
body.state-none { background: #121212 ; color: #fff ; }
body.state-none .app-header { background: #121212 ; }
body.state-none .player-box { background: #1f1f1f ; border-color: #2a2a2a ; }
body.state-day { background: #4a3a05 ; color: #fff ; }
body.state-day .app-header { background: #4a3a05 ; }
body.state-day .player-box { background: rgba(0, 0, 0, 0.3); border-color: #5a4a15 ; }
body.state-night { background: #1a1b3a ; color: #fff ; }
body.state-night .app-header { background: #1a1b3a ; }
body.state-night .player-box { background: rgba(0, 0, 0, 0.4); border-color: #2a2b4a ; }
.cmd-box.is-self { opacity: 0.3; }
.grid { display: grid; grid-template-columns: 50% 50%; grid-template-rows: calc(50% - 1vh) calc(50% - 1vh); height: 96vh; width: 100%; gap: 1px; padding: 1px; }
.player-box { background: #111 ; border: 1px solid #333 ;display: flex; flex-direction: column; padding: 2px; position: relative; }
#slot -P1 { grid-area: 1 / 1 / 2 / 2; }
#slot -P2 { grid-area: 1 / 2 / 2 / 3; }
#slot -P3 { grid-area: 2 / 2 / 3 / 3; }
#slot -P4 { grid-area: 2 / 1 / 3 / 2; }
.header-area { height: 4vh; display: flex; align-items: center; gap: 1px; flex-shrink: 0; white-space: nowrap; }
.name-wrapper { position: relative; display: inline-block; }
.name-input { background: #222 ; color: #fff ; border: 1px solid #444 ; font-size: 4vmin; width: 35vw; height: 5vh; text-align: center; border-radius: 3px; }
.partner-wrapper label { font-size: 3vmin; }
.main-display-wrapper { height: 57%; display: flex; align-items: center; gap: 1px; flex-shrink: 0; }
.life-center-unit { flex: 1.5; display: flex; flex-direction: column; align-items: center; }
.life { font-size: 17vmin; font-weight: bold; color: #4caf50 ; border: none; background: transparent; text-align: center; width: 100%; padding: 0; height: 14vmin; }
.life-btn-group { display: flex; gap: 2px; }
.btn-life { width: 8vw; height: 4vh; font-size: 4vmin; border: none; cursor: pointer; color: #fff ; border-radius: 3px; }
.num-select { font-size: 3vmin; text-align-last: center; margin-top: 1vh; background: #1f1f1f ; color: #dcdcdc ; }
.sidebar-counters { width: 25vw; display: flex; flex-direction: column; gap: 1px; }
.side-item { background: #1a1a1a ; padding: 1px 1px; display: flex; justify-content: space-between; font-size: 2.5vmin; border-radius: 2px; align-items: center }
.side-label { font-size: 2.5vmin; }
.side-val { margin-left: 1vw; margin-right: 1vw; }
.btn-side, .btn-cmd { width: 4vw; height: 3vh; font-size: 4vmin; display: flex; align-items: center; background: #444 ; color: #fff ; border: none; }
.side-dice-wrapper { background: #1a1a1a ; height: 8vh; display: grid; grid-template-rows: 3.7vh 3.5vh; }
.side-dice-input-row { flex-wrap: nowrap; display: flex; justify-content: space-around; align-items: center; width: 100%; align-self: start; }
.dice-input { width: 10vw; height: 3vh; font-size: 2.5vmin; }
.dice-btn { font-size: 2.5vmin ; background: #ffc107 ; color: #000 ; width: 10vw; height: 3.5vh; text-align: center; display: flex; align-items: center; justify-content: center; }
.dice-res-row { flex-wrap: nowrap; display: flex; justify-content: space-around; align-items: center; width: 100%; }
.dice-result, .dice-format { flex: 1; text-align: center; display: flex; align-items: center; justify-content: center; }
.dice-result { font-size: 4vmin ; color: #ffeb3b ; font-weight: bold; }
.dice-format { font-size: 1.5vmin ; color: #ffffff ; }
.commander-area { flex-grow: 1; display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; gap: 0px; min-height: 0; }
.cmd-box { background: #1a1a1a ; border: 1px solid #222 ; padding: 1px; display: flex; flex-direction: column; justify-content: center; }
.commander-area .cmd-header { font-size: 1.5vmin; color: #ffffff ; text-align: center; }
.cmd-body { display: flex; justify-content: space-around; align-items: center; }
.cmd-val { font-size: 3.5vmin; font-weight: bold; color: #ff9800 ; }
.cmd-side-label { font-size: 1vmin; color: #ffffff ; display: block; width: 100%; text-align: center; margin-bottom: 0px; }
.cmd-unit { display: flex; flex-direction: column; align-items: center; width: 100%; }
.cmd-val-wrap { display: flex; align-items: center; justify-content: center; width: 100%; justify-content: space-around;}
.app-header { height: 5vh; display: flex; justify-content: space-between; align-items: center; padding: 0 10px; background: #000 ; }
.header-btns button, .day-night-select { padding: 1px 4px; height: 4vh; align-items: center; margin-left: 2px; }
.btn-plus { background: #2e7d32 ; color: #fff ; }
.btn-minus { background: #c62828 ; color: #fff ; }
.order-btn { background: #0277bd ; color: #fff ; font-size: 2.5vmin; border: none; }
.reset-btn { background: #c62828 ; color: #fff ; font-size: 2.5vmin; border: none; }
.day-night-select { background: #444 ; color: #fff ; font-size: 2.5vmin; }
.room-share-btn { background: #e91e63 ; color: #fff ; font-size: 2.5vmin; border: none; padding: 1px 4px; height: 4vh; cursor: pointer; }
.custom-dropdown {
position: absolute; top: 5.5vh; left: 0; width: 35vw; background: #2a2a2a ;
border: 1px solid #555 ; border-radius: 4px; z-index: 999; max-height: 25vh;
overflow-y: auto; display: none; box-shadow: 0px 4px 10px rgba(0,0,0,0.5);
}
.custom-dropdown div {
padding: 8px; font-size: 3.5vmin; text-align: center; border-bottom: 1px solid #3a3a3a ; cursor: pointer; color: #fff ;
}
.custom-dropdown div:active, .custom-dropdown div:hover {
background: #0277bd ;
}
</style>
</head>
<body class="state-none">
<div class="app-header">
<span id="app-title" style="font-size:0.8rem; font-weight:bold; color:#777;">MTG統率者戦ライフカウンタ</span>
<div class="header-btns">
<button class="room-share-btn" onclick="copyRoomUrl()">部屋URLをコピー</button>
<select id="day-night-ctrl" class="day-night-select" onchange="directInput('dayNight', this.value)">
<option value="none">昼夜:無</option>
<option value="day">昼</option>
<option value="night">夜</option>
</select>
<button class="order-btn" onclick="shuffleNames()">ターン順シャッフル</button>
<button class="reset-btn" onclick="resetGame()">リセット</button>
</div>
</div>
<div class="grid" id="main-grid"></div>
<script src="https://unpkg.com/peerjs@1.4.7/dist/peerjs.min.js"></script>
<script>
let savedMembers = JSON.parse(localStorage.getItem('edh_member_list')) || [];
const playerSlots = ['P1', 'P2', 'P3', 'P4'];
const defaultState = {
dayNight: "none",
P1: { name: "Player 1", isPartner: false, life: 40, poison: 0, rad: 0, tax1: 0, tax2: 0, diceFormat: "", diceResult: "--", cmd: { P1:{v1:0,v2:0}, P2:{v1:0,v2:0}, P3:{v1:0,v2:0}, P4:{v1:0,v2:0} } },
P2: { name: "Player 2", isPartner: false, life: 40, poison: 0, rad: 0, tax1: 0, tax2: 0, diceFormat: "", diceResult: "--", cmd: { P1:{v1:0,v2:0}, P2:{v1:0,v2:0}, P3:{v1:0,v2:0}, P4:{v1:0,v2:0} } },
P3: { name: "Player 3", isPartner: false, life: 40, poison: 0, rad: 0, tax1: 0, tax2: 0, diceFormat: "", diceResult: "--", cmd: { P1:{v1:0,v2:0}, P2:{v1:0,v2:0}, P3:{v1:0,v2:0}, P4:{v1:0,v2:0} } },
P4: { name: "Player 4", isPartner: false, life: 40, poison: 0, rad: 0, tax1: 0, tax2: 0, diceFormat: "", diceResult: "--", cmd: { P1:{v1:0,v2:0}, P2:{v1:0,v2:0}, P3:{v1:0,v2:0}, P4:{v1:0,v2:0} } }
};
let gameState = JSON.parse(localStorage.getItem('edh_counter_state')) || JSON.parse(JSON.stringify(defaultState));
const grid = document.getElementById('main-grid');
playerSlots.forEach((slot, index) => {
let html = `
<div class="player-box" id="slot-${slot}">
<div class="header-area">
<span class="order-num">${index + 1}</span>
<div class="name-wrapper">
<input type="text" id="name-${slot}" class="name-input" value="" autocomplete="off"
onclick="showDropdown('${slot}')"
onchange="handleNameChange('${slot}', this.value)">
<div id="drop-${slot}" class="custom-dropdown"></div>
</div>
<div class="partner-wrapper">
<input type="checkbox" id="check-${slot}" class="partner-check" onchange="directInput('${slot}/isPartner', this.checked)">
<label for="check-${slot}">共闘</label>
</div>
</div>
<div class="main-display-wrapper">
<div class="life-center-unit">
<div class="life-wrapper"><input type="number" id="life-${slot}" class="life" value="40" onchange="directInput('${slot}/life', parseInt(this.value))"></div>
<div class="life-btn-group">
<button class="btn-life btn-minus" onclick="changeValue('${slot}/life', -1)">-1</button>
<button class="btn-life btn-plus" onclick="changeValue('${slot}/life', 1)">+1</button>
</div>
<select class="num-select" onchange="if(this.value !== '') { directInput('${slot}/life', parseInt(this.value)); this.value = ''; }">
<option value="">リストから</option>
${Array.from({length:150}, (_,i)=>`<option value="${i+1}">${i+1}</option>`).join('')}
</select>
</div>
<div class="sidebar-counters">
<div id="side-main-${slot}"></div>
<div class="side-dice-wrapper">
<div class="side-dice-input-row">
<input type="text" id="dice-in-${slot}" class="dice-input" value="1d20" placeholder="1d20">
<button class="dice-btn" onclick="triggerDice('${slot}')">振</button>
</div>
<div class="dice-res-row">
<div id="dice-fmt-${slot}" class="dice-format"></div>
<div id="dice-res-${slot}" class="dice-result">--</div>
</div>
</div>
</div>
</div>
<div class="commander-area" id="cmd-area-${slot}"></div>
</div>`;
grid.insertAdjacentHTML('beforeend', html);
});
function updateDropdownMenus() {
playerSlots.forEach(slot => {
const dropBox = document.getElementById(`drop-${slot}`);
if (savedMembers.length === 0) {
dropBox.innerHTML = '<div style="color:#888; font-size:2.5vmin;">履歴なし</div>';
} else {
dropBox.innerHTML = savedMembers.map(name => `<div onclick="selectPresetName('${slot}', '${name}')">${name}</div>`).join('');
}
});
}
window.showDropdown = (slot) => {
playerSlots.forEach(s => document.getElementById(`drop-${s}`).style.display = 'none');
document.getElementById(`drop-${slot}`).style.display = 'block';
};
window.selectPresetName = (slot, name) => {
document.getElementById(`name-${slot}`).value = name;
window.directInput(`${slot}/name`, name);
document.getElementById(`drop-${slot}`).style.display = 'none';
};
window.handleNameChange = (slot, value) => {
const trimmedValue = value.trim();
if (trimmedValue !== "" && !trimmedValue.startsWith("Player ")) {
if (!savedMembers.includes(trimmedValue)) {
savedMembers.push(trimmedValue);
localStorage.setItem('edh_member_list', JSON.stringify(savedMembers));
updateDropdownMenus();
}
}
window.directInput(`${slot}/name`, trimmedValue);
};
document.addEventListener('click', (e) => {
if (!e.target.classList.contains('name-input')) {
playerSlots.forEach(s => document.getElementById(`drop-${s}`).style.display = 'none');
}
});
function getDynamicColor(val, type) {
if (type === 'life') {
if (val <= 0) return "#f44336";
return `hsl(${Math.min(1, val / 40) * 120}, 100%, 60%)`;
} else if (type === 'cmd') {
if (val >= 21) return "#ff00ff";
return `hsl(${120 - (Math.min(1, val / 21) * 120)}, 100%, 60%)`;
} else if (type === 'poison') {
if (val <= 0) return "#888";
if (val >= 10) return "#ff00ff";
return `hsl(${Math.max(0, 120 - (val * 12))}, 100%, 60%)`;
}
return "#888";
}
const urlParams = new URLSearchParams(window.location.search);
let roomId = urlParams.get('room');
let peer = null;
let connections = [];
if (!roomId) {
roomId = Math.random().toString(36).substring(2, 8);
window.history.replaceState(null, null, `?room=${roomId}`);
initPeer(roomId, true);
} else {
initPeer(null, false);
}
function initPeer(id, isHost) {
peer = id ? new Peer(id) : new Peer();
peer.on('open', (myId) => {
document.getElementById('app-title').innerText = `Room: ${roomId}`;
if (!isHost) {
const conn = peer.connect(roomId);
setupConnection(conn);
}
});
peer.on('connection', (conn) => {
setupConnection(conn);
setTimeout(() => {
conn.send({ type: 'SYNC', state: gameState });
}, 500);
});
}
function setupConnection(conn) {
connections.push(conn);
conn.on('data', (data) => {
if (data.type === 'SYNC') {
gameState = data.state;
saveAndRender();
} else if (data.type === 'UPDATE') {
setDeepValue(gameState, data.path, data.value);
saveAndRender();
if (window.location.search.includes('room') && peer.id === roomId) {
broadcast({ type: 'SYNC', state: gameState });
}
}
});
conn.on('close', () => {
connections = connections.filter(c => c !== conn);
});
}
function broadcast(data) {
connections.forEach(conn => {
if (conn.open) conn.send(data);
});
}
function setDeepValue(obj, path, value) {
const parts = path.split('/');
let current = obj;
for (let i = 0; i < parts.length - 1; i++) {
if (!current[parts[i]]) current[parts[i]] = {};
current = current[parts[i]];
}
current[parts[parts.length - 1]] = value;
}
function getDeepValue(obj, path) {
return path.split('/').reduce((o, p) => (o ? o[p] : null), obj);
}
function saveAndRender() {
localStorage.setItem('edh_counter_state', JSON.stringify(gameState));
renderUI();
}
window.directInput = (path, value) => {
setDeepValue(gameState, path, value);
saveAndRender();
broadcast({ type: 'UPDATE', path: path, value: value });
};
window.changeValue = (path, delta) => {
let currentVal = getDeepValue(gameState, path);
if (currentVal === undefined || currentVal === null) {
currentVal = path.includes('life') ? 40 : 0;
}
let newVal = currentVal + delta;
window.directInput(path, newVal);
};
window.resetGame = () => {
if (!confirm("初期化しますか?")) return;
playerSlots.forEach(slot => { setDeepValue(gameState, `${slot}/life`, 40); setDeepValue(gameState, `${slot}/poison`, 0); setDeepValue(gameState, `${slot}/rad`, 0); setDeepValue(gameState, `${slot}/tax1`, 0); setDeepValue(gameState, `${slot}/tax2`, 0); setDeepValue(gameState, `${slot}/diceFormat`, ""); setDeepValue(gameState, `${slot}/diceResult`, "--"); playerSlots.forEach(from => { setDeepValue(gameState, `${slot}/cmd/${from}/v1`, 0); setDeepValue(gameState, `${slot}/cmd/${from}/v2`, 0); });
});
gameState.dayNight = "none";
saveAndRender();
broadcast({ type: 'SYNC', state: gameState });
};
window.shuffleNames = () => {
const currentNames = playerSlots.map(slot => gameState[slot].name || slot);
playerSlots.forEach(s => document.getElementById(`slot-${s}`).classList.add('shuffling'));
let count = 0;
const interval = setInterval(() => {
playerSlots.forEach(slot => {
const el = document.getElementById(`name-${slot}`);
if (document.activeElement !== el) {
el.value = currentNames[Math.floor(Math.random() * currentNames.length)];
}
});
if (++count >= 15) {
clearInterval(interval);
const shuffled = [...currentNames].sort(() => Math.random() - 0.5);
playerSlots.forEach((slot, i) => {
setDeepValue(gameState, `${slot}/name`, shuffled[i]);
document.getElementById(`slot-${slot}`).classList.remove('shuffling');
});
saveAndRender();
broadcast({ type: 'SYNC', state: gameState });
}
}, 80);
};
window.triggerDice = (slot) => {
const input = document.getElementById(`dice-in-${slot}`);
const match = input.value.toLowerCase().match(/^(\d+)d(\d+)$/);
if (!match) return;
window.directInput(`${slot}/diceFormat`, input.value);
let r = 0;
const itv = setInterval(() => {
const tmp = Array.from({length:match[1]},()=>Math.floor(Math.random()*match[2])+1).reduce((a,b)=>a+b);
window.directInput(`${slot}/diceResult`, tmp);
if (++r > 8) clearInterval(itv);
}, 80);
input.blur();
};
window.copyRoomUrl = () => {
navigator.clipboard.writeText(window.location.href);
alert("部屋の同期用URLをコピーしました!他のプレイヤーに送ってください。");
};
function renderUI() {
const d = gameState;
const dnState = d.dayNight || "none";
const dnSelect = document.getElementById('day-night-ctrl');
if (dnSelect && document.activeElement !== dnSelect) dnSelect.value = dnState;
document.body.className = `state-${dnState}`;
playerSlots.forEach((target) => {
const p = d[target] || {};
const nameIn = document.getElementById(`name-${target}`);
const slotBox = document.getElementById(`slot-${target}`);
if (nameIn && document.activeElement !== nameIn && !slotBox.classList.contains('shuffling')) {
nameIn.value = p.name || "";
}
const check = document.getElementById(`check-${target}`);
if (check) check.checked = !!p.isPartner;
document.getElementById(`dice-fmt-${target}`).innerText = p.diceFormat || "";
document.getElementById(`dice-res-${target}`).innerText = p.diceResult || "--";
const lifeIn = document.getElementById(`life-${target}`);
if (lifeIn && document.activeElement !== lifeIn) {
const lifeVal = p.life ?? 40;
lifeIn.value = lifeVal;
lifeIn.style.color = getDynamicColor(lifeVal, 'life');
lifeIn.classList.toggle('small-font', lifeVal >= 100);
}
const poi = p.poison ?? 0;
const rad = p.rad ?? 0;
let sideHtml = "";
if (p.isPartner) {
sideHtml += `<div class="side-item"><span class="side-label" style="color:#00bcd4">統唱数A</span><button class="btn-side" onclick="changeValue('${target}/tax1',-1)">-</button><span class="side-val" style="color:#00bcd4">${p.tax1??0}</span><button class="btn-side" onclick="changeValue('${target}/tax1',1)">+</button></div>`;
sideHtml += `<div class="side-item"><span class="side-label" style="color:#00bcd4">統唱数B</span><button class="btn-side" onclick="changeValue('${target}/tax2',-1)">-</button><span class="side-val" style="color:#00bcd4">${p.tax2??0}</span><button class="btn-side" onclick="changeValue('${target}/tax2',1)">+</button></div>`;
} else {
sideHtml += `<div class="side-item"><span class="side-label" style="color:#00bcd4">統唱数</span><button class="btn-side" onclick="changeValue('${target}/tax1',-1)">-</button><span class="side-val" style="color:#00bcd4">${p.tax1??0}</span><button class="btn-side" onclick="changeValue('${target}/tax1',1)">+</button></div>`;
}
sideHtml += `<div class="side-item"><span class="side-label" style="color:#adff2f">毒</span><button class="btn-side" onclick="changeValue('${target}/poison',-1)">-</button><span class="side-val" style="color:${getDynamicColor(poi, 'poison')}">${poi}</span><button class="btn-side" onclick="changeValue('${target}/poison',1)">+</button></div><div class="side-item"><span class="side-label" style="color:#ffeb3b">RAD</span><button class="btn-side" onclick="changeValue('${target}/rad',-1)">-</button><span class="side-val" style="color:${rad>0?'#ffeb3b':'#888'}">${rad}</span><button class="btn-side" onclick="changeValue('${target}/rad',1)">+</button></div>`;
document.getElementById(`side-main-${target}`).innerHTML = sideHtml;
const cmdArea = document.getElementById(`cmd-area-${target}`);
if (cmdArea) {
cmdArea.innerHTML = '';
const clockwiseOrder = ['P1', 'P2', 'P4', 'P3'];
clockwiseOrder.forEach((from) => {
const fromData = d[from] || {};
const fromIsP = !!fromData.isPartner;
const v1 = p.cmd?.[from]?.v1 ?? 0;
const v2 = p.cmd?.[from]?.v2 ?? 0;
const isSelf = (from === target);
const displayNum = from.replace('P', '');
let boxHtml = `<div class="cmd-box cmd-pos-${displayNum} ${isSelf ? 'is-self' : ''}">
<div class="cmd-header">
<span class="cmd-order">${displayNum}</span>
<span class="cmd-name-label">${fromData.name||from} 統受打</span>
</div>
<div class="cmd-body">
<div class="cmd-unit">
${fromIsP?'<span class="cmd-side-label">A</span>':''}
<div class="cmd-val-wrap">
<button class="btn-cmd" onclick="changeValue('${target}/cmd/${from}/v1',-1)">-</button>
<span class="cmd-val" style="color:${getDynamicColor(v1, 'cmd')}">${v1}</span>
<button class="btn-cmd" onclick="changeValue('${target}/cmd/${from}/v1',1)">+</button>
</div>
</div>
${fromIsP?`<div class="cmd-unit">
<span class="cmd-side-label">B</span>
<div class="cmd-val-wrap">
<button class="btn-cmd" onclick="changeValue('${target}/cmd/${from}/v2',-1)">-</button>
<span class="cmd-val" style="color:${getDynamicColor(v2, 'cmd')}">${v2}</span>
<button class="btn-cmd" onclick="changeValue('${target}/cmd/${from}/v2',1)">+</button>
</div>
</div>`:''}
</div>
</div>`;
cmdArea.insertAdjacentHTML('beforeend', boxHtml);
});
}
});
}
updateDropdownMenus();
renderUI();
</script>
</body>
</html>ここから先は空欄です。でも支援いただけたら助かります。