diff --git a/index.html b/index.html
index 4ebd98a..0e5a030 100644
--- a/index.html
+++ b/index.html
@@ -588,6 +588,98 @@
border-radius: 0;
}
+ /* ── Wizard ────────────────────────────────────────── */
+ #wizard-overlay {
+ position: fixed;
+ inset: 0;
+ z-index: 50;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 24px;
+ background: rgba(12, 18, 33, 0.97);
+ backdrop-filter: blur(24px);
+ -webkit-backdrop-filter: blur(24px);
+ }
+ #wizard-overlay[hidden] { display: none; }
+ #wizard-card {
+ width: 100%;
+ max-width: 420px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+ .wizard-logo {
+ width: 72px; height: 72px;
+ border-radius: 18px;
+ overflow: hidden;
+ margin-bottom: 18px;
+ box-shadow: 0 0 32px rgba(34,211,238,0.3);
+ }
+ .wizard-logo img { width: 100%; height: 100%; object-fit: cover; }
+ .wizard-title {
+ font-size: 22px;
+ font-weight: 600;
+ color: var(--text);
+ margin: 0 0 6px;
+ letter-spacing: -0.03em;
+ }
+ .wizard-subtitle {
+ font-size: 13px;
+ color: var(--text3);
+ margin: 0 0 32px;
+ }
+ .wizard-options {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ width: 100%;
+ margin-bottom: 20px;
+ }
+ .wizard-option {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ text-align: left;
+ padding: 14px 16px;
+ border-radius: 12px;
+ background: rgba(255,255,255,0.03);
+ border: 1px solid var(--border);
+ cursor: pointer;
+ transition: background 0.15s, border-color 0.15s;
+ width: 100%;
+ color: var(--text);
+ }
+ .wizard-option:hover {
+ background: rgba(34,211,238,0.08);
+ border-color: rgba(34,211,238,0.3);
+ }
+ .wizard-option-icon {
+ width: 38px; height: 38px;
+ border-radius: 10px;
+ background: rgba(34,211,238,0.1);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ color: var(--accent);
+ }
+ .wizard-option-body { flex: 1; }
+ .wizard-option-title {
+ font-size: 13px;
+ font-weight: 500;
+ margin-bottom: 2px;
+ }
+ .wizard-option-desc {
+ font-size: 11px;
+ color: var(--text3);
+ }
+ .wizard-hint {
+ font-size: 11px;
+ color: var(--text3);
+ margin: 0;
+ }
+
/* ── Edge label tooltip ────────────────────────────── */
#edge-tooltip {
position: fixed;
@@ -630,13 +722,17 @@
データ
+
@@ -834,6 +930,53 @@
+
+
+
+
+

+
+
Atlas へようこそ
+
インフラ構成図・サービス依存マップ
+
+
+
+
+
+
ノードは後からいつでも追加・編集できます
+
+
+
+
+
+
@@ -880,9 +1023,7 @@ async function loadData() {
if (saved) {
try { atlasData = JSON.parse(saved); return; } catch (e) {}
}
- const res = await fetch('/atlas.json');
- atlasData = await res.json();
- saveData();
+ // No saved data — wizard will handle initialization
}
function saveData() {
@@ -1397,16 +1538,42 @@ function bindEvents() {
.then(() => showToast('クリップボードにコピーしました'));
});
+ document.getElementById('btnImportJson').addEventListener('click', () => {
+ document.getElementById('fileInput').click();
+ });
document.getElementById('btnExportJson').addEventListener('click', () => {
exportJson();
showToast('エクスポートしました');
});
document.getElementById('btnResetData').addEventListener('click', () => {
- if (!confirm('データをデフォルトに戻しますか?')) return;
+ if (!confirm('サンプルデータに戻しますか?現在のデータは失われます。')) return;
localStorage.removeItem(STORAGE_KEY);
location.reload();
});
+ // File input (wizard + settings import)
+ document.getElementById('fileInput').addEventListener('change', e => {
+ const isWizard = !document.getElementById('wizard-overlay').hasAttribute('hidden');
+ handleImportFile(e.target.files[0], isWizard);
+ });
+
+ // Wizard buttons
+ document.getElementById('w-sample').addEventListener('click', async () => {
+ const res = await fetch('/atlas.json');
+ const data = await res.json();
+ finishWizard(data);
+ });
+ document.getElementById('w-empty').addEventListener('click', () => {
+ finishWizard({
+ meta: { owner: '', description: 'My infrastructure', updated: new Date().toISOString().slice(0, 10), version: '1' },
+ nodes: [],
+ edges: [],
+ });
+ });
+ document.getElementById('w-import').addEventListener('click', () => {
+ document.getElementById('fileInput').click();
+ });
+
window.addEventListener('resize', () => {
if (simulation) {
const svg = d3.select('#graph-svg');
@@ -1424,12 +1591,62 @@ if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
-// ── Init ───────────────────────────────────────────────────────
-loadData().then(() => {
+// ── Wizard ─────────────────────────────────────────────────────
+function showWizard() {
+ const overlay = document.getElementById('wizard-overlay');
+ overlay.removeAttribute('hidden');
+ if (window.lucide) lucide.createIcons({ nodes: [overlay] });
+}
+
+function finishWizard(data) {
+ atlasData = data;
+ saveData();
+ document.getElementById('wizard-overlay').setAttribute('hidden', '');
buildFilterBar();
initGraph();
- bindEvents();
setTimeout(fitGraph, 800);
+ if (atlasData.nodes.length === 0) {
+ setTimeout(openAddModal, 700);
+ }
+}
+
+// ── File import (shared: wizard + settings) ────────────────────
+function handleImportFile(file, isWizard) {
+ if (!file) return;
+ const reader = new FileReader();
+ reader.onload = evt => {
+ try {
+ const data = JSON.parse(evt.target.result);
+ if (!Array.isArray(data.nodes) || !Array.isArray(data.edges)) throw new Error();
+ if (isWizard) {
+ finishWizard(data);
+ } else {
+ atlasData = data;
+ saveData();
+ closeDetail();
+ buildFilterBar();
+ initGraph();
+ setTimeout(fitGraph, 500);
+ showToast('インポートしました');
+ }
+ } catch (e) {
+ showToast('atlas.json の形式が正しくありません');
+ }
+ document.getElementById('fileInput').value = '';
+ };
+ reader.readAsText(file);
+}
+
+// ── Init ───────────────────────────────────────────────────────
+loadData().then(() => {
+ bindEvents();
+ if (!atlasData) {
+ showWizard();
+ } else {
+ buildFilterBar();
+ initGraph();
+ setTimeout(fitGraph, 800);
+ }
});