This commit is contained in:
@@ -1,218 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ja">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Design Canvas — プロトタイプ</title>
|
|
||||||
<style>
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
||||||
html, body { width: 100%; height: 100%; overflow: hidden; background: #1a1a2e; color: #e0e0e0; font-family: 'Segoe UI', system-ui, sans-serif; }
|
|
||||||
#toolbar { position: fixed; top: 0; left: 0; right: 0; height: 48px; background: #16213e; border-bottom: 1px solid #0f3460; display: flex; align-items: center; gap: 12px; padding: 0 16px; z-index: 100; }
|
|
||||||
#toolbar h1 { font-size: 15px; font-weight: 600; color: #a0d2ff; white-space: nowrap; }
|
|
||||||
.toolbar-btn { background: #0f3460; color: #e0e0e0; border: 1px solid #1a5276; padding: 6px 14px; border-radius: 4px; cursor: pointer; font-size: 13px; transition: background 0.15s; }
|
|
||||||
.toolbar-btn:hover { background: #1a5276; }
|
|
||||||
.toolbar-btn.primary { background: #e94560; border-color: #e94560; color: #fff; }
|
|
||||||
.toolbar-btn.primary:hover { background: #c73e54; }
|
|
||||||
#viewport { position: fixed; top: 48px; left: 0; right: 0; bottom: 0; overflow: hidden; cursor: grab; }
|
|
||||||
#viewport.grabbing { cursor: grabbing; }
|
|
||||||
#canvas { position: absolute; top: 0; left: 0; transform-origin: 0 0; width: 2000px; height: 1400px; background: #0f0f1a; }
|
|
||||||
#artboard { width: 960px; height: 600px; margin: 60px; background: #ffffff; border-radius: 4px; box-shadow: 0 8px 32px rgba(0,0,0,0.5); position: relative; overflow: hidden; }
|
|
||||||
#properties-panel { position: fixed; top: 48px; right: 0; width: 280px; bottom: 0; background: #16213e; border-left: 1px solid #0f3460; padding: 16px; overflow-y: auto; transform: translateX(100%); transition: transform 0.2s ease; z-index: 90; }
|
|
||||||
#properties-panel.open { transform: translateX(0); }
|
|
||||||
#properties-panel h2 { font-size: 14px; color: #a0d2ff; margin-bottom: 12px; }
|
|
||||||
.prop-row { margin-bottom: 10px; }
|
|
||||||
.prop-row label { display: block; font-size: 11px; color: #888; margin-bottom: 3px; }
|
|
||||||
.prop-row input[type="text"], .prop-row input[type="number"], .prop-row textarea { width: 100%; background: #0f3460; border: 1px solid #1a5276; color: #e0e0e0; padding: 6px 8px; border-radius: 4px; font-size: 13px; }
|
|
||||||
.prop-row input[type="color"] { width: 100%; height: 32px; border: none; background: none; cursor: pointer; }
|
|
||||||
#zoom-indicator { position: fixed; bottom: 16px; left: 16px; background: rgba(22,33,62,0.9); padding: 6px 12px; border-radius: 4px; font-size: 12px; color: #a0d2ff; z-index: 100; }
|
|
||||||
[data-editable] { cursor: pointer; outline: 1px dashed transparent; transition: outline 0.1s; }
|
|
||||||
[data-editable]:hover { outline: 1px dashed #e94560; }
|
|
||||||
[data-editable].selected { outline: 2px solid #e94560; }
|
|
||||||
.slide-header { position: absolute; top: 0; left: 0; right: 0; height: 80px; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); display: flex; align-items: center; padding: 0 32px; }
|
|
||||||
.slide-header h2 { color: #ffffff; font-size: 28px; font-weight: 700; }
|
|
||||||
.slide-header .page-num { margin-left: auto; color: #e94560; font-size: 14px; font-weight: 600; }
|
|
||||||
.kpi-grid { position: absolute; top: 100px; left: 32px; right: 32px; display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
|
|
||||||
.kpi-card { background: #f8f9fa; border-radius: 8px; padding: 16px; border-left: 4px solid #3498db; }
|
|
||||||
.kpi-card.warning { border-left-color: #f39c12; }
|
|
||||||
.kpi-card.success { border-left-color: #27ae60; }
|
|
||||||
.kpi-card.danger { border-left-color: #e74c3c; }
|
|
||||||
.kpi-label { font-size: 11px; color: #888; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
|
|
||||||
.kpi-value { font-size: 28px; font-weight: 700; color: #2c3e50; }
|
|
||||||
.kpi-delta { font-size: 12px; margin-top: 4px; color: #27ae60; }
|
|
||||||
.kpi-delta.negative { color: #e74c3c; }
|
|
||||||
.chart-area { position: absolute; top: 260px; left: 32px; right: 32px; bottom: 32px; background: #f8f9fa; border-radius: 8px; padding: 20px; }
|
|
||||||
.chart-area h3 { font-size: 14px; color: #2c3e50; margin-bottom: 12px; }
|
|
||||||
.bar-chart { display: flex; align-items: flex-end; gap: 8px; height: 200px; padding-top: 20px; }
|
|
||||||
.bar { flex: 1; background: #3498db; border-radius: 3px 3px 0 0; position: relative; transition: background 0.2s; }
|
|
||||||
.bar:hover { background: #2980b9; }
|
|
||||||
.bar-label { position: absolute; bottom: -20px; left: 0; right: 0; text-align: center; font-size: 10px; color: #888; }
|
|
||||||
.bar-value { position: absolute; top: -20px; left: 0; right: 0; text-align: center; font-size: 11px; font-weight: 600; color: #2c3e50; }
|
|
||||||
.legend { display: flex; gap: 16px; margin-top: 24px; }
|
|
||||||
.legend-item { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #555; }
|
|
||||||
.legend-dot { width: 10px; height: 10px; border-radius: 2px; }
|
|
||||||
#toast { position: fixed; bottom: 60px; left: 50%; transform: translateX(-50%) translateY(20px); background: #27ae60; color: #fff; padding: 10px 24px; border-radius: 6px; font-size: 13px; opacity: 0; transition: opacity 0.2s, transform 0.2s; z-index: 200; pointer-events: none; }
|
|
||||||
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="toolbar">
|
|
||||||
<h1>Design Canvas</h1>
|
|
||||||
<span style="font-size:12px;color:#666;">スライド1/10 — デザイン確認</span>
|
|
||||||
<div style="flex:1"></div>
|
|
||||||
<button class="toolbar-btn" onclick="resetView()">リセット</button>
|
|
||||||
<button class="toolbar-btn" onclick="exportJSON()">JSON エクスポート</button>
|
|
||||||
<button class="toolbar-btn primary" onclick="confirmSlide()">これでOK →</button>
|
|
||||||
</div>
|
|
||||||
<div id="viewport">
|
|
||||||
<div id="canvas">
|
|
||||||
<div id="artboard">
|
|
||||||
<div class="slide-header">
|
|
||||||
<h2 data-editable data-label="タイトル" data-property="title">月次経営ダッシュボード</h2>
|
|
||||||
<span class="page-num" data-editable data-label="ページ番号" data-property="page">01</span>
|
|
||||||
</div>
|
|
||||||
<div class="kpi-grid">
|
|
||||||
<div class="kpi-card" data-editable data-label="KPI1" data-property="kpi1">
|
|
||||||
<div class="kpi-label">売上高</div>
|
|
||||||
<div class="kpi-value">12.4億円</div>
|
|
||||||
<div class="kpi-delta">+8.2% 前月比</div>
|
|
||||||
</div>
|
|
||||||
<div class="kpi-card success" data-editable data-label="KPI2" data-property="kpi2">
|
|
||||||
<div class="kpi-label">粗利率</div>
|
|
||||||
<div class="kpi-value">42.3%</div>
|
|
||||||
<div class="kpi-delta">+1.2pt 前月比</div>
|
|
||||||
</div>
|
|
||||||
<div class="kpi-card warning" data-editable data-label="KPI3" data-property="kpi3">
|
|
||||||
<div class="kpi-label">顧客数</div>
|
|
||||||
<div class="kpi-value">1,847</div>
|
|
||||||
<div class="kpi-delta">+3.1% 前月比</div>
|
|
||||||
</div>
|
|
||||||
<div class="kpi-card danger" data-editable data-label="KPI4" data-property="kpi4">
|
|
||||||
<div class="kpi-label">離脱率</div>
|
|
||||||
<div class="kpi-value">2.8%</div>
|
|
||||||
<div class="kpi-delta negative">+0.4pt 前月比</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="chart-area">
|
|
||||||
<h3 data-editable data-label="チャートタイトル" data-property="chartTitle">月次売上推移(直近6ヶ月)</h3>
|
|
||||||
<div class="bar-chart" data-editable data-label="チャート" data-property="chart">
|
|
||||||
<div class="bar" style="height:45%"><span class="bar-value">8.2</span><span class="bar-label">3月</span></div>
|
|
||||||
<div class="bar" style="height:52%"><span class="bar-value">9.1</span><span class="bar-label">4月</span></div>
|
|
||||||
<div class="bar" style="height:58%"><span class="bar-value">10.3</span><span class="bar-label">5月</span></div>
|
|
||||||
<div class="bar" style="height:65%"><span class="bar-value">11.5</span><span class="bar-label">6月</span></div>
|
|
||||||
<div class="bar" style="height:72%"><span class="bar-value">11.9</span><span class="bar-label">7月</span></div>
|
|
||||||
<div class="bar" style="height:100%;background:#27ae60"><span class="bar-value">12.4</span><span class="bar-label">8月</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="legend">
|
|
||||||
<div class="legend-item"><div class="legend-dot" style="background:#3498db"></div>通常月</div>
|
|
||||||
<div class="legend-item"><div class="legend-dot" style="background:#27ae60"></div>当月</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="properties-panel">
|
|
||||||
<h2>プロパティ編集</h2>
|
|
||||||
<div id="prop-contents">
|
|
||||||
<p style="font-size:12px;color:#666;">要素をクリックして選択してください。</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="zoom-indicator">100%</div>
|
|
||||||
<div id="toast"></div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
(function(){
|
|
||||||
let panX = 0, panY = 0, scale = 1, isPanning = false, startX = 0, startY = 0;
|
|
||||||
const viewport = document.getElementById('viewport');
|
|
||||||
const canvas = document.getElementById('canvas');
|
|
||||||
const zoomInd = document.getElementById('zoom-indicator');
|
|
||||||
const panel = document.getElementById('properties-panel');
|
|
||||||
const propContents = document.getElementById('prop-contents');
|
|
||||||
let selectedEl = null;
|
|
||||||
|
|
||||||
function updateTransform() {
|
|
||||||
canvas.style.transform = `translate(${panX}px, ${panY}px) scale(${scale})`;
|
|
||||||
zoomInd.textContent = Math.round(scale * 100) + '%';
|
|
||||||
}
|
|
||||||
|
|
||||||
viewport.addEventListener('mousedown', e => {
|
|
||||||
if (e.target.closest('[data-editable]') || e.target.closest('#properties-panel')) return;
|
|
||||||
isPanning = true;
|
|
||||||
startX = e.clientX - panX;
|
|
||||||
startY = e.clientY - panY;
|
|
||||||
viewport.classList.add('grabbing');
|
|
||||||
if (selectedEl) { selectedEl.classList.remove('selected'); selectedEl = null; panel.classList.remove('open'); }
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('mousemove', e => {
|
|
||||||
if (!isPanning) return;
|
|
||||||
panX = e.clientX - startX;
|
|
||||||
panY = e.clientY - startY;
|
|
||||||
updateTransform();
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('mouseup', () => { isPanning = false; viewport.classList.remove('grabbing'); });
|
|
||||||
|
|
||||||
viewport.addEventListener('wheel', e => {
|
|
||||||
e.preventDefault();
|
|
||||||
const delta = e.deltaY > 0 ? 0.9 : 1.1;
|
|
||||||
scale = Math.max(0.25, Math.min(4, scale * delta));
|
|
||||||
updateTransform();
|
|
||||||
}, { passive: false });
|
|
||||||
|
|
||||||
document.querySelectorAll('[data-editable]').forEach(el => {
|
|
||||||
el.addEventListener('click', e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (selectedEl) selectedEl.classList.remove('selected');
|
|
||||||
selectedEl = el;
|
|
||||||
el.classList.add('selected');
|
|
||||||
const label = el.dataset.label || el.dataset.property;
|
|
||||||
const prop = el.dataset.property;
|
|
||||||
propContents.innerHTML = `
|
|
||||||
<div class="prop-row"><label>要素名</label><input type="text" value="${label}" disabled></div>
|
|
||||||
<div class="prop-row"><label>テキスト内容</label><textarea rows="3">${el.textContent.trim()}</textarea></div>
|
|
||||||
<div class="prop-row"><label>背景色</label><input type="color" value="#ffffff"></div>
|
|
||||||
<div class="prop-row"><label>文字色</label><input type="color" value="#2c3e50"></div>
|
|
||||||
<div class="prop-row"><label>フォントサイズ</label><input type="number" value="16"></div>
|
|
||||||
<button class="toolbar-btn primary" style="width:100%;margin-top:12px" onclick="showToast('適用されました')">適用</button>
|
|
||||||
`;
|
|
||||||
panel.classList.add('open');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
window.resetView = function() {
|
|
||||||
scale = 1; panX = 0; panY = 0;
|
|
||||||
updateTransform();
|
|
||||||
};
|
|
||||||
|
|
||||||
window.exportJSON = function() {
|
|
||||||
const data = {};
|
|
||||||
document.querySelectorAll('[data-property]').forEach(el => {
|
|
||||||
data[el.dataset.property] = el.textContent.trim();
|
|
||||||
});
|
|
||||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = URL.createObjectURL(blob);
|
|
||||||
a.download = 'slide-1-design.json';
|
|
||||||
a.click();
|
|
||||||
showToast('JSON をエクスポートしました');
|
|
||||||
};
|
|
||||||
|
|
||||||
window.confirmSlide = function() {
|
|
||||||
showToast('スライド1 確定 → PPTX 生成に進みます');
|
|
||||||
};
|
|
||||||
|
|
||||||
window.showToast = function(msg) {
|
|
||||||
const t = document.getElementById('toast');
|
|
||||||
t.textContent = msg;
|
|
||||||
t.classList.add('show');
|
|
||||||
setTimeout(() => t.classList.remove('show'), 2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Center artboard on load
|
|
||||||
const vw = viewport.clientWidth, vh = viewport.clientHeight;
|
|
||||||
panX = (vw - 960) / 2 - 60;
|
|
||||||
panY = (vh - 600) / 2 - 60;
|
|
||||||
updateTransform();
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user