diff --git a/index.html b/index.html index 0e5a030..6cba136 100644 --- a/index.html +++ b/index.html @@ -680,6 +680,51 @@ margin: 0; } + /* ── Share banner ──────────────────────────────────── */ + #share-banner { + position: fixed; + top: 52px; + left: 0; right: 0; + z-index: 15; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 7px 16px; + background: rgba(34,211,238,0.08); + border-bottom: 1px solid rgba(34,211,238,0.18); + font-size: 12px; + color: var(--accent); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + } + #share-banner[hidden] { display: none; } + .has-banner #graph-wrap { top: 88px; } + .has-banner #filter-bar { top: 96px; } + + /* ── Edge delete button ─────────────────────────────── */ + .conn-delete-btn { + flex-shrink: 0; + width: 20px; height: 20px; + border-radius: 5px; + background: transparent; + border: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + color: var(--text3); + opacity: 0; + transition: opacity 0.15s, background 0.15s, color 0.15s; + padding: 0; + margin-left: auto; + } + .conn-item:hover .conn-delete-btn { opacity: 1; } + .conn-delete-btn:hover { + background: rgba(239,68,68,0.15); + color: #F87171; + } + /* ── Edge label tooltip ────────────────────────────── */ #edge-tooltip { position: fixed; @@ -740,6 +785,13 @@ + + + +
+ Claude や Gemini のチャットに貼り付けると、AI があなたの環境を即座に把握します +

     
 
@@ -918,6 +973,9 @@
     
+    
     
`; return `
${other?.label || otherId} ${e.label ? `${e.label}` : ''} - ${isOut ? 'out' : 'in'} + ${isOut ? 'out' : 'in'} + ${delBtn}
`; }).join(''); } @@ -1505,6 +1573,19 @@ function exportJson() { function bindEvents() { document.getElementById('btn-fit').addEventListener('click', fitGraph); document.getElementById('btn-add-node').addEventListener('click', openAddModal); + document.getElementById('btn-share').addEventListener('click', () => { + const url = generateShareURL(); + navigator.clipboard.writeText(url) + .then(() => showToast('シェア URL をコピーしました')) + .catch(() => { + // Fallback: prompt + prompt('シェア URL をコピーしてください', url); + }); + }); + document.getElementById('share-banner-own-btn').addEventListener('click', () => { + history.replaceState(null, '', location.pathname); + location.reload(); + }); document.getElementById('btn-ai').addEventListener('click', () => { document.getElementById('ai-context-text').textContent = generateAIContext(); openModal('ai-modal-overlay'); @@ -1591,6 +1672,52 @@ if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js'); } +// ── URL Sharing ──────────────────────────────────────────────── +function generateShareURL() { + const json = JSON.stringify(atlasData); + const b64 = btoa(unescape(encodeURIComponent(json))); + return `${location.origin}${location.pathname}#atlas=${b64}`; +} + +function checkShareURL() { + const hash = location.hash; + if (!hash.startsWith('#atlas=')) return false; + try { + const b64 = hash.slice(7); + const json = decodeURIComponent(escape(atob(b64))); + const data = JSON.parse(json); + if (!Array.isArray(data.nodes) || !Array.isArray(data.edges)) return false; + atlasData = data; + isReadOnly = true; + return true; + } catch (e) { + return false; + } +} + +function applyReadOnly() { + const banner = document.getElementById('share-banner'); + banner.removeAttribute('hidden'); + document.body.classList.add('has-banner'); + if (window.lucide) lucide.createIcons({ nodes: [banner] }); + document.getElementById('btn-add-node').style.display = 'none'; +} + +// ── Edge delete ──────────────────────────────────────────────── +function deleteEdge(from, to) { + if (isReadOnly) return; + atlasData.edges = atlasData.edges.filter(e => !(e.from === from && e.to === to)); + saveData(); + const keepId = selectedNodeId; + initGraph(); + if (keepId) { + selectedNodeId = keepId; + updateHighlight(currentSimNodes, currentSimEdges); + showDetail(keepId, currentSimNodes, currentSimEdges); + } + showToast('接続を削除しました'); +} + // ── Wizard ───────────────────────────────────────────────────── function showWizard() { const overlay = document.getElementById('wizard-overlay'); @@ -1640,6 +1767,15 @@ function handleImportFile(file, isWizard) { // ── Init ─────────────────────────────────────────────────────── loadData().then(() => { bindEvents(); + // 1. Check for shared atlas in URL hash (read-only mode) + if (checkShareURL()) { + buildFilterBar(); + initGraph(); + applyReadOnly(); + setTimeout(fitGraph, 800); + return; + } + // 2. Normal flow if (!atlasData) { showWizard(); } else {