diff --git a/packages/dashboard/index.html b/packages/dashboard/index.html index f3bb6e7..3b362f4 100644 --- a/packages/dashboard/index.html +++ b/packages/dashboard/index.html @@ -328,6 +328,29 @@ @keyframes hype-pulse-anim { 0%,100% { opacity: 0.25; r: 16; } 50% { opacity: 0.08; r: 24; } } .hype-connector { stroke: rgba(255,255,255,0.12); stroke-width: 1; stroke-dasharray: 2,3; } + /* Hype Cycle Hover Tooltip */ + .hype-tooltip { + position: fixed; z-index: 1000; pointer-events: none; + background: rgba(10,11,16,0.95); border: 1px solid rgba(255,129,0,0.3); + border-radius: 8px; padding: 10px 14px; min-width: 180px; + box-shadow: 0 8px 24px rgba(0,0,0,0.5); backdrop-filter: blur(8px); + font-size: 12px; color: var(--text); opacity: 0; transition: opacity 0.15s; + } + .hype-tooltip.visible { opacity: 1; } + .hype-tooltip .tt-tech { font-weight: 700; font-size: 13px; margin-bottom: 4px; } + .hype-tooltip .tt-phase { font-size: 11px; opacity: 0.7; margin-bottom: 6px; } + .hype-tooltip .tt-row { display: flex; justify-content: space-between; gap: 12px; margin: 2px 0; } + .hype-tooltip .tt-label { color: var(--text-dim); } + .hype-tooltip .tt-val { font-family: 'JetBrains Mono', monospace; font-weight: 600; } + + /* Phase Legend */ + .hype-legend { + display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; + margin-top: 12px; padding: 8px 0; + } + .legend-item { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text-dim); } + .legend-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; } + .hype-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; position: relative; z-index: 1; @@ -1422,13 +1445,31 @@ function renderHypeSvg(techs) { // Outer ring svg += ''; - // Main dot - svg += ''; + // Main dot (with hover data attributes) + var adoptPct = t.adoptionPct != null ? Math.round(t.adoptionPct) : 0; + var peakYr = t.peakYear || '—'; + svg += ''; // Inner highlight svg += ''; } svg += ''; + + // Phase legend + svg += '
'; + var phases = [ + {k:'Innovation Trigger',c:PC['Innovation Trigger']||'#4deaff'}, + {k:'Peak of Inflated Expectations',c:PC['Peak of Inflated Expectations']||'#fbbf24'}, + {k:'Trough of Disillusionment',c:PC['Trough of Disillusionment']||'#f87171'}, + {k:'Slope of Enlightenment',c:PC['Slope of Enlightenment']||'#a78bfa'}, + {k:'Plateau of Productivity',c:PC['Plateau of Productivity']||'#34d399'}, + {k:'Legacy / Decline',c:PC['Legacy / Decline']||'#8888a4'} + ]; + for (var pi = 0; pi < phases.length; pi++) { + var cnt = techs.filter(function(t){return t.phase===phases[pi].k}).length; + if (cnt > 0) svg += ''+phases[pi].k+' ('+cnt+')'; + } + svg += '
'; return svg; } diff --git a/packages/mcp-server/src/tools/switch-docs.ts b/packages/mcp-server/src/tools/switch-docs.ts index 16464af..c216291 100644 --- a/packages/mcp-server/src/tools/switch-docs.ts +++ b/packages/mcp-server/src/tools/switch-docs.ts @@ -19,7 +19,7 @@ export async function registerSwitchDocTools(server: McpServer): Promise { const switchResult = await pool.query( `SELECT sw.id, sw.model, sw.series, sw.image_url, sw.datasheet_url, sw.product_page_url, sw.manual_urls, - v.name as vendor_name, v.docs_portal_url, v.support_portal_url + v.name as vendor_name, v.website as vendor_website FROM switches sw LEFT JOIN vendors v ON sw.vendor_id = v.id WHERE sw.model ILIKE $1 @@ -60,11 +60,8 @@ export async function registerSwitchDocTools(server: McpServer): Promise { if (sw.datasheet_url) { text += `**Datasheet:** ${sw.datasheet_url}\n`; } - if (sw.docs_portal_url) { - text += `**Vendor Docs Portal:** ${sw.docs_portal_url}\n`; - } - if (sw.support_portal_url) { - text += `**Support Portal:** ${sw.support_portal_url}\n`; + if (sw.vendor_website) { + text += `**Vendor Website:** ${sw.vendor_website}\n`; } if (docsResult.rows.length > 0) {