<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jubo 官網資訊架構 Sitemap</title>
<style>
body { margin: 0; padding: 0; background: #f4f6f8; font-family: "Microsoft JhengHei", "Heiti TC", sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; overflow: hidden; }
#container { width: 100%; height: 100%; max-width: 1400px; max-height: 900px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); background: white; border-radius: 12px; position: relative; }
/* --- Share Button Styles --- */
#share-widget { position: absolute; bottom: 30px; right: 30px; z-index: 100; }
#share-btn {
background: white; color: #555; border: 1px solid #ddd;
padding: 12px 20px; border-radius: 50px; font-size: 14px; font-weight: 600;
cursor: pointer; box-shadow: 0 4px 10px rgba(0,0,0,0.1);
transition: all 0.2s; display: flex; align-items: center; gap: 8px;
}
#share-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 15px rgba(0,0,0,0.15); color: #333; }
#share-btn:active { transform: translateY(0); }
/* --- Toast Notification --- */
#toast {
position: absolute; bottom: 80px; right: 30px;
background: rgba(0,0,0,0.8); color: white;
padding: 10px 20px; border-radius: 8px;
font-size: 14px; opacity: 0; transition: opacity 0.3s;
pointer-events: none; z-index: 101;
}
#toast.show { opacity: 1; }
/* --- Detail Modal Styles --- */
#detail-modal-overlay {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.4);
display: none; justify-content: center; align-items: center;
z-index: 200;
backdrop-filter: blur(2px);
opacity: 0; transition: opacity 0.3s;
}
#detail-modal {
background: white; width: 400px; max-width: 90%; padding: 25px;
border-radius: 16px; box-shadow: 0 10px 40px rgba(0,0,0,0.2);
transform: scale(0.9); transition: transform 0.3s;
position: relative;
display: flex; flex-direction: column; gap: 15px;
}
#detail-modal.active { transform: scale(1); }
#modal-close { position: absolute; top: 15px; right: 15px; cursor: pointer; font-size: 20px; color: #999; }
#modal-title { font-size: 22px; font-weight: bold; color: #333; margin: 0; }
#modal-desc { font-size: 15px; color: #555; line-height: 1.6; background: #f5f7fa; padding: 15px; border-radius: 8px; border-left: 4px solid #667eea; }
#modal-children-title { font-size: 14px; font-weight: bold; color: #777; margin-top: 10px; text-transform: uppercase; letter-spacing: 1px; }
#modal-children-list { display: flex; flex-wrap: wrap; gap: 10px; }
.child-btn {
background: white; border: 1px solid #667eea; color: #667eea;
padding: 8px 16px; border-radius: 20px; font-size: 14px; font-weight: 600;
cursor: pointer; transition: all 0.2s;
}
.child-btn:hover { background: #667eea; color: white; }
</style>
</head>
<body>
<div id="container">
<!-- SVG 容器 -->
<svg id="sitemapSvg" width="100%" height="100%" viewBox="0 0 1300 800" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="dropShadow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur in="SourceAlpha" stdDeviation="3"/>
<feOffset dx="2" dy="2" result="offsetblur"/>
<feComponentTransfer>
<feFuncA type="linear" slope="0.3"/>
</feComponentTransfer>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#ccc"/>
</marker>
<marker id="arrowhead-active" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#FFD700"/>
</marker>
</defs>
<style>
.node-item { cursor: pointer; transition: transform 0.3s ease; }
.node-item:hover { transform: scale(1.1); }
.node-rect { stroke-width: 2px; transition: all 0.3s ease; filter: url(#dropShadow); }
.node-text { font-size: 14px; font-weight: bold; pointer-events: none; user-select: none; fill: #333; }
.link { fill: none; stroke: #e0e0e0; stroke-width: 2px; transition: stroke 0.3s; }
.active .node-rect { fill: #fffde7 !important; stroke: #F5A623 !important; stroke-width: 3px; }
.active-link { stroke: #F5A623; stroke-width: 3px; stroke-dasharray: 10; animation: dash 1s linear infinite; }
@keyframes dash { to { stroke-dashoffset: -20; } }
#info-bar { transition: opacity 0.3s; pointer-events: none; }
#info-bg { fill: rgba(0, 0, 0, 0.8); rx: 20; }
#info-text { fill: white; font-size: 16px; text-anchor: middle; font-weight: bold; }
</style>
<g id="links-layer"></g>
<g id="nodes-layer"></g>
<!-- 底部簡單提示 -->
<g id="info-bar" transform="translate(650, 750)" style="opacity: 0;">
<rect id="info-bg" x="-200" y="-20" width="400" height="40"></rect>
<text id="info-text" x="0" y="6">點擊節點查看詳細介紹</text>
</g>
</svg>
<!-- Detail Modal -->
<div id="detail-modal-overlay" onclick="closeModal(event)">
<div id="detail-modal">
<span id="modal-close" onclick="closeModal(event)">×</span>
<h2 id="modal-title">標題</h2>
<div id="modal-desc">描述內容...</div>
<div id="modal-children-container" style="display:none;">
<div id="modal-children-title">下一層項目</div>
<div id="modal-children-list"></div>
</div>
</div>
</div>
<!-- Share Widget (Replaces AI) -->
<div id="share-widget">
<button id="share-btn" onclick="copyLink()">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path>
<polyline points="16 6 12 2 8 6"></polyline>
<line x1="12" y1="2" x2="12" y2="15"></line>
</svg>
<span>分享連結</span>
</button>
</div>
<div id="toast">連結已複製!</div>
<script>
// --- 資料結構 (最新版) ---
const data = {
id: "root", label: "Jubo 官網", desc: "全方位智慧照護科技平台", detailedDesc: "Jubo 智齡科技提供從機構到居家的全方位照護解決方案,串聯 IoT 設備與 AI 數據分析。", color: "#2c3e50", width: 160, height: 50,
children: [
{
id: "products", label: "產品 (Products)", desc: "機構與個人軟硬體", detailedDesc: "涵蓋照護機構管理系統、個人健康 App 以及智慧照護推車等核心產品。", color: "#4A90E2", width: 140,
children: [
{
id: "inst", label: "照護機構", desc: "機構管理核心系統", detailedDesc: "專為住宿型、日照型與居服單位設計的數位化管理系統。", color: "#5DADE2", width: 110,
children: [
{ id: "p1", label: "住宿型系統", desc: "24h 機構管理", detailedDesc: "• 系統截圖與功能介紹<br>• Navattic 互動 Demo<br>• Learn More 連結<br>專為住宿型長照機構打造,整合護理紀錄與行政管理。", color: "#AED6F1" },
{ id: "p2", label: "日照型系統", desc: "日照排程管理", detailedDesc: "• 系統截圖與功能介紹<br>• Navattic 互動 Demo<br>• Learn More 連結<br>提供個案管理、交通車排程與活動記錄功能。", color: "#AED6F1" },
{ id: "p3", label: "居服系統", desc: "居服派遣核銷", detailedDesc: "• 系統截圖與功能介紹<br>• Navattic 互動 Demo<br>• Learn More 連結<br>Hi 居服 App 整合,優化派遣效率與核銷流程。", color: "#AED6F1" },
{ id: "p4", label: "智慧推車", desc: "Smart Cart", detailedDesc: "• IoT 量測整合<br>• 生命徵象自動上傳<br>減少抄寫錯誤,提升護理工作效率。", color: "#AED6F1" }
]
},
{
id: "pers", label: "個人健康", desc: "家屬與長輩連結", detailedDesc: "連結家屬與長輩的數位橋樑,提供即時健康資訊。", color: "#5DADE2", width: 110,
children: [
{ id: "p5", label: "Jubo健康APP", desc: "家屬聯繫與紀錄", detailedDesc: "• Jubo 健康 APP 介紹<br>• App Store / Google Play 連結<br>讓家屬即時掌握長輩健康狀況。", color: "#AED6F1" },
{ id: "p6", label: "智齡照顧網", desc: "照護知識平台", detailedDesc: "• 智齡照顧網介紹<br>• 外部網站連結<br>提供專業照護知識文章與資源。", color: "#AED6F1" }
]
}
]
},
{
id: "solutions", label: "解決方案", desc: "場景驅動整合服務", detailedDesc: "針對不同照護場景提供的完整解決方案包,整合軟體、硬體與 AI。", color: "#50E3C2", width: 140,
children: [
{ id: "s_res", label: "住宿型", desc: "住宿機構方案", detailedDesc: "• 模組介紹連結回住宿產品頁面<br>• 客戶成功案例<br>• Demo 連結<br>一站式住宿機構數位轉型方案。", color: "#A2D9CE" },
{ id: "s_day", label: "日照型", desc: "日照中心方案", detailedDesc: "• 模組介紹連結回日照產品頁面<br>• 客戶成功案例<br>• Demo 連結<br>提升日照中心營運效率與個案互動。", color: "#A2D9CE" },
{ id: "s_home", label: "居服型", desc: "居家服務方案", detailedDesc: "• 模組介紹連結回居服產品頁面<br>• 客戶成功案例<br>• Demo 連結<br>解決居服單位派案與管理痛點。", color: "#A2D9CE" },
{ id: "s_iot", label: "Jubo IoT生態圈", desc: "硬體聯網生態", detailedDesc: "• 照護推車 + IoT<br>• 合作廠商設備整合<br>打造互聯互通的智慧照護環境。", color: "#A2D9CE" },
{ id: "s_ai", label: "N-Copilot", desc: "護理 AI 夥伴", detailedDesc: "• N-copilot 功能展示<br>• Navattic Demo 整合<br>專屬於護理人員的 AI 智能助理,輔助紀錄與決策。", color: "#A2D9CE" }
]
},
{
id: "resources", label: "資源 (Resources)", desc: "活動與知識", detailedDesc: "匯集活動花絮、教育資源與客戶成功案例。", color: "#F5A623", width: 130,
children: [
{
id: "r1", label: "活動花絮", desc: "線上線下活動", detailedDesc: "• 線上線下活動清單<br>• 活動詳情頁 (CMS)<br>• Hubspot 表單整合", color: "#FAD7A0",
children: [
{ id: "r1_1", label: "三三分享會", desc: "產業知識交流", detailedDesc: "定期舉辦的產業知識分享會,邀請專家與業者交流。", color: "#FEF9E7" }
]
},
{ id: "r3", label: "教育平台", desc: "卓越教育中心", detailedDesc: "• 卓越教育平台介紹<br>• 外部網站連結<br>提供系統操作教學與長照專業課程。", color: "#FAD7A0" },
{ id: "r4", label: "客戶案例", desc: "成功導入故事", detailedDesc: "• 成功案例展示 (CMS)<br>• 文章列表<br>見證合作夥伴的數位轉型成效。", color: "#FAD7A0" },
{ id: "r5", label: "媒合平台", desc: "長照資源網", detailedDesc: "• 長照資源網介紹<br>• 機構媒合功能<br>連結需求者與照護資源。", color: "#FAD7A0" }
]
},
{
id: "partners", label: "夥伴 (Partners)", desc: "跨領域聯盟", detailedDesc: "IoT 夥伴與策略合作夥伴。", color: "#BD10E0", width: 120,
children: [
{ id: "pt1", label: "IoT夥伴", desc: "硬體生態", detailedDesc: "合作廠商名單與產品整合說明。", color: "#D7BDE2" },
{ id: "pt2", label: "策略夥伴", desc: "投資人與公司", detailedDesc: "公司的投資人、投資公司與策略合作單位。", color: "#D7BDE2" }
]
},
{
id: "company", label: "關於我們", desc: "品牌與團隊", detailedDesc: "Jubo 品牌故事、團隊介紹與人才招募。", color: "#9013FE", width: 120,
children: [
{ id: "c1", label: "品牌故事", desc: "願景與使命", detailedDesc: "品牌願景:以科技溫暖照護。", color: "#D2B4DE" },
{ id: "c2", label: "團隊介紹", desc: "專業團隊", detailedDesc: "結合醫療、資料科學與設計的跨領域團隊。", color: "#D2B4DE" },
{ id: "c3", label: "人才招募", desc: "加入我們", detailedDesc: "職缺、文化與福利介紹。<br>連結至 104 人力銀行。", color: "#D2B4DE" }
]
},
{
id: "contact", label: "聯絡我們", desc: "業務洽詢", detailedDesc: "業務名單 + 聯絡表單 (Hubspot)。<br>我們將盡快與您聯繫。", color: "#E91E63", width: 120,
children: []
}
]
};
// --- 佈局計算 ---
const svg = document.getElementById('sitemapSvg');
const nodesLayer = document.getElementById('nodes-layer');
const linksLayer = document.getElementById('links-layer');
const startY = 50;
const levelHeight = 150;
const leafGap = 120;
const nodesMap = {};
const linksArr = [];
function calcWeight(node) {
if (!node.children || node.children.length === 0) {
node.weight = 1;
} else {
node.weight = node.children.reduce((sum, child) => sum + calcWeight(child), 0);
}
return node.weight;
}
function assignPositions(node, x, y, level, parentId) {
const nodeWidth = node.width || 100;
const nodeHeight = node.height || 40;
nodesMap[node.id] = {
x: x,
y: y,
w: nodeWidth,
h: nodeHeight,
data: node,
parent: parentId,
level: level
};
if (parentId) {
linksArr.push({ source: parentId, target: node.id });
}
if (node.children && node.children.length > 0) {
// 特別處理:若子節點是 "三三分享會"
if (node.id === "r1") {
assignPositions(node.children[0], x, y + 60, level + 1, node.id);
return;
}
let currentX = x - (node.weight * leafGap) / 2;
if(level === 0) currentX = x - (node.weight * (leafGap + 10)) / 2;
node.children.forEach(child => {
const childWeight = child.weight;
const childSpan = childWeight * leafGap;
const childX = currentX + childSpan / 2;
let childY = y + levelHeight;
assignPositions(child, childX, childY, level + 1, node.id);
currentX += childSpan;
});
}
}
calcWeight(data);
// 根節點 X 設為中心點 (1300/2 = 650)
assignPositions(data, 650, startY, 0, null);
// --- 修正垂直列表佈局 ---
function adjustLayoutToVerticalList(parentId) {
const parent = nodesMap[parentId];
if (!parent) return;
const childrenIds = Object.keys(nodesMap).filter(k => nodesMap[k].parent === parentId);
childrenIds.forEach((childId, index) => {
nodesMap[childId].x = parent.x;
nodesMap[childId].y = parent.y + 60 + (index * 50);
});
}
// 調整特定分支為垂直排列
adjustLayoutToVerticalList("inst");
adjustLayoutToVerticalList("pers");
adjustLayoutToVerticalList("solutions");
adjustLayoutToVerticalList("resources");
adjustLayoutToVerticalList("partners");
adjustLayoutToVerticalList("company");
// --- 手動微調第一層的位置 (X軸) ---
nodesMap["products"].x = 180; // 左移
nodesMap["solutions"].x = 420; // 緊接
nodesMap["resources"].x = 640; // 接近中間
nodesMap["partners"].x = 840;
nodesMap["company"].x = 1000;
nodesMap["contact"].x = 1160; // 新增在最右側
// 重新套用垂直佈局以更新子節點位置
nodesMap["inst"].x = nodesMap["products"].x - 60; nodesMap["inst"].y = 250;
nodesMap["pers"].x = nodesMap["products"].x + 60; nodesMap["pers"].y = 250;
adjustLayoutToVerticalList("inst");
adjustLayoutToVerticalList("pers");
adjustLayoutToVerticalList("solutions");
adjustLayoutToVerticalList("resources");
adjustLayoutToVerticalList("partners");
adjustLayoutToVerticalList("company");
// 特別調整:三三分享會
if (nodesMap["r1"] && nodesMap["r1_1"]) {
nodesMap["r1_1"].x = nodesMap["r1"].x + 20;
nodesMap["r1_1"].y = nodesMap["r1"].y + 45;
const resChildren = ["r3", "r4", "r5"];
resChildren.forEach((id) => {
if(nodesMap[id]) nodesMap[id].y += 30;
});
}
// --- 繪圖 ---
function drawTree() {
// 1. Draw Links
linksArr.forEach(link => {
const s = nodesMap[link.source];
const t = nodesMap[link.target];
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("class", "link");
path.setAttribute("id", `link-${link.source}-${link.target}`);
let d = "";
if (Math.abs(s.x - t.x) < 40) {
if (link.target === "r1_1") {
d = `M ${s.x} ${s.y + s.h/2} L ${s.x} ${t.y} L ${t.x - t.w/2} ${t.y}`;
} else {
d = `M ${s.x} ${s.y + s.h/2} L ${t.x} ${t.y - t.h/2}`;
}
} else {
d = `M ${s.x} ${s.y + s.h/2} C ${s.x} ${s.y + s.h/2 + 50}, ${t.x} ${t.y - t.h/2 - 50}, ${t.x} ${t.y - t.h/2}`;
}
path.setAttribute("d", d);
linksLayer.appendChild(path);
});
// 2. Draw Nodes
Object.keys(nodesMap).forEach(key => {
const n = nodesMap[key];
const container = document.createElementNS("http://www.w3.org/2000/svg", "g");
container.setAttribute("class", "node-container");
container.setAttribute("transform", `translate(${n.x}, ${n.y})`);
const item = document.createElementNS("http://www.w3.org/2000/svg", "g");
item.setAttribute("class", "node-item");
item.setAttribute("id", `node-${key}`);
item.onclick = (e) => handleClick(key, e);
item.onmouseover = () => handleHover(key);
item.onmouseout = () => handleOut();
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("class", "node-rect");
rect.setAttribute("x", -n.w / 2);
rect.setAttribute("y", -n.h / 2);
rect.setAttribute("width", n.w);
rect.setAttribute("height", n.h);
rect.setAttribute("rx", 8);
rect.setAttribute("ry", 8);
rect.setAttribute("fill", "white");
rect.setAttribute("stroke", n.data.color || "#999");
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
text.setAttribute("class", "node-text");
text.setAttribute("x", 0);
text.setAttribute("y", 5);
text.setAttribute("text-anchor", "middle");
text.textContent = n.data.label;
item.appendChild(rect);
item.appendChild(text);
container.appendChild(item);
nodesLayer.appendChild(container);
});
}
// --- Interaction Logic ---
function handleHover(id) {
const node = nodesMap[id];
const infoBar = document.getElementById('info-bar');
const infoText = document.getElementById('info-text');
const infoBg = document.getElementById('info-bg');
infoBar.style.opacity = 1;
infoText.textContent = node.data.label;
infoBg.setAttribute("width", node.data.label.length * 20 + 40);
infoBg.setAttribute("x", -(node.data.label.length * 20 + 40) / 2);
}
function handleOut() {
document.getElementById('info-bar').style.opacity = 0;
}
function handleClick(id, e) {
if(e) e.stopPropagation();
document.querySelectorAll('.active').forEach(el => el.classList.remove('active'));
document.querySelectorAll('.active-link').forEach(el => el.classList.remove('active-link'));
const nodeEl = document.getElementById(`node-${id}`);
if(nodeEl) nodeEl.classList.add('active');
tracePath(id);
showDetailModal(id);
}
function tracePath(currentId) {
const node = nodesMap[currentId];
if (!node || !node.parent) return;
const parentId = node.parent;
const linkId = `link-${parentId}-${currentId}`;
const linkEl = document.getElementById(linkId);
if (linkEl) {
linkEl.classList.add('active-link');
linkEl.parentNode.appendChild(linkEl);
}
const parentEl = document.getElementById(`node-${parentId}`);
if(parentEl) parentEl.classList.add('active');
tracePath(parentId);
}
// --- Detail Modal Logic ---
function showDetailModal(id) {
const node = nodesMap[id];
const modalOverlay = document.getElementById('detail-modal-overlay');
const modal = document.getElementById('detail-modal');
const title = document.getElementById('modal-title');
const desc = document.getElementById('modal-desc');
const childContainer = document.getElementById('modal-children-container');
const childList = document.getElementById('modal-children-list');
title.textContent = node.data.label;
desc.innerHTML = node.data.detailedDesc || node.data.desc;
childList.innerHTML = '';
if (node.data.children && node.data.children.length > 0) {
childContainer.style.display = 'block';
node.data.children.forEach(child => {
const btn = document.createElement('button');
btn.className = 'child-btn';
btn.textContent = child.label;
btn.onclick = () => {
handleClick(child.id, null);
};
childList.appendChild(btn);
});
} else {
childContainer.style.display = 'none';
}
modalOverlay.style.display = 'flex';
setTimeout(() => {
modalOverlay.style.opacity = 1;
modal.classList.add('active');
}, 10);
}
function closeModal(e) {
if (e.target.id === 'detail-modal-overlay' || e.target.id === 'modal-close') {
const modalOverlay = document.getElementById('detail-modal-overlay');
const modal = document.getElementById('detail-modal');
modalOverlay.style.opacity = 0;
modal.classList.remove('active');
setTimeout(() => {
modalOverlay.style.display = 'none';
}, 300);
}
}
// --- Share Functionality ---
function copyLink() {
const url = window.location.href;
navigator.clipboard.writeText(url).then(() => {
const toast = document.getElementById('toast');
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 2000);
});
}
drawTree();
</script>
</div>
</body>
</html>