|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Freelance Platform Mind Map</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap'); |
|
|
|
|
|
body { |
|
|
font-family: 'Poppins', sans-serif; |
|
|
background-color: #f8fafc; |
|
|
} |
|
|
|
|
|
.mindmap-container { |
|
|
position: relative; |
|
|
min-height: 100vh; |
|
|
overflow-x: hidden; |
|
|
} |
|
|
|
|
|
.node { |
|
|
position: absolute; |
|
|
border-radius: 12px; |
|
|
padding: 1rem; |
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); |
|
|
transition: all 0.3s ease; |
|
|
cursor: pointer; |
|
|
max-width: 300px; |
|
|
z-index: 10; |
|
|
} |
|
|
|
|
|
.node:hover { |
|
|
transform: scale(1.05); |
|
|
z-index: 20; |
|
|
} |
|
|
|
|
|
.connector { |
|
|
position: absolute; |
|
|
height: 2px; |
|
|
background-color: #cbd5e1; |
|
|
transform-origin: left center; |
|
|
z-index: 1; |
|
|
} |
|
|
|
|
|
.node-icon { |
|
|
font-size: 1.5rem; |
|
|
margin-right: 0.5rem; |
|
|
} |
|
|
|
|
|
.node-content { |
|
|
margin-top: 0.5rem; |
|
|
} |
|
|
|
|
|
.node-item { |
|
|
margin-bottom: 0.25rem; |
|
|
font-size: 0.9rem; |
|
|
} |
|
|
|
|
|
.controls { |
|
|
position: fixed; |
|
|
bottom: 2rem; |
|
|
right: 2rem; |
|
|
z-index: 100; |
|
|
} |
|
|
|
|
|
.zoom-buttons { |
|
|
display: flex; |
|
|
gap: 0.5rem; |
|
|
} |
|
|
|
|
|
.legend { |
|
|
position: fixed; |
|
|
top: 2rem; |
|
|
right: 2rem; |
|
|
background: white; |
|
|
padding: 1rem; |
|
|
border-radius: 8px; |
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); |
|
|
z-index: 100; |
|
|
} |
|
|
|
|
|
.legend-item { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
margin-bottom: 0.5rem; |
|
|
} |
|
|
|
|
|
.legend-color { |
|
|
width: 20px; |
|
|
height: 20px; |
|
|
border-radius: 4px; |
|
|
margin-right: 0.5rem; |
|
|
} |
|
|
|
|
|
.search-container { |
|
|
position: fixed; |
|
|
top: 2rem; |
|
|
left: 2rem; |
|
|
z-index: 100; |
|
|
} |
|
|
|
|
|
.highlight { |
|
|
animation: pulse 1.5s infinite; |
|
|
} |
|
|
|
|
|
@keyframes pulse { |
|
|
0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4); } |
|
|
70% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); } |
|
|
100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); } |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-50"> |
|
|
<div class="mindmap-container" id="mindmap"> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="legend"> |
|
|
<h3 class="font-bold text-lg mb-2">Legend</h3> |
|
|
<div class="legend-item"> |
|
|
<div class="legend-color bg-blue-500"></div> |
|
|
<span>Platform Overview</span> |
|
|
</div> |
|
|
<div class="legend-item"> |
|
|
<div class="legend-color bg-green-500"></div> |
|
|
<span>Actors</span> |
|
|
</div> |
|
|
<div class="legend-item"> |
|
|
<div class="legend-color bg-purple-500"></div> |
|
|
<span>Functional Areas</span> |
|
|
</div> |
|
|
<div class="legend-item"> |
|
|
<div class="legend-color bg-yellow-500"></div> |
|
|
<span>Data Model</span> |
|
|
</div> |
|
|
<div class="legend-item"> |
|
|
<div class="legend-color bg-red-500"></div> |
|
|
<span>Technical Architecture</span> |
|
|
</div> |
|
|
<div class="legend-item"> |
|
|
<div class="legend-color bg-indigo-500"></div> |
|
|
<span>UI/UX</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="search-container"> |
|
|
<div class="relative"> |
|
|
<input type="text" id="searchInput" placeholder="Search nodes..." |
|
|
class="pl-10 pr-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"> |
|
|
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="controls"> |
|
|
<div class="zoom-buttons bg-white p-2 rounded-lg shadow-lg"> |
|
|
<button id="zoomIn" class="p-2 rounded-full hover:bg-gray-100"> |
|
|
<i class="fas fa-search-plus text-blue-500"></i> |
|
|
</button> |
|
|
<button id="zoomOut" class="p-2 rounded-full hover:bg-gray-100"> |
|
|
<i class="fas fa-search-minus text-blue-500"></i> |
|
|
</button> |
|
|
<button id="resetZoom" class="p-2 rounded-full hover:bg-gray-100"> |
|
|
<i class="fas fa-expand text-blue-500"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
const mindmap = document.getElementById('mindmap'); |
|
|
let scale = 1; |
|
|
let offsetX = 0; |
|
|
let offsetY = 0; |
|
|
let isDragging = false; |
|
|
let startX, startY; |
|
|
|
|
|
|
|
|
const nodes = [ |
|
|
{ |
|
|
id: 'platform-overview', |
|
|
title: 'Platform Overview', |
|
|
icon: 'fa-globe', |
|
|
color: 'bg-blue-500', |
|
|
content: [ |
|
|
'Digitize freelance workflow end-to-end', |
|
|
'Mobile-first UI', |
|
|
'Scope grooming per order', |
|
|
'Iterative delivery with reviews', |
|
|
'Transparent pricing & time tracking', |
|
|
'Messaging with attachments', |
|
|
'Email proposal tracking', |
|
|
'Microfrontend architecture' |
|
|
], |
|
|
x: 50, |
|
|
y: 50 |
|
|
}, |
|
|
{ |
|
|
id: 'actors', |
|
|
title: 'Actors', |
|
|
icon: 'fa-users', |
|
|
color: 'bg-green-500', |
|
|
content: [ |
|
|
'<b>Freelancer</b>: Manages proposals, projects, time tracking', |
|
|
'<b>Customer</b>: Requests work, manages scope, gives feedback', |
|
|
'<b>System</b>: Notifications, email handling, auth' |
|
|
], |
|
|
x: 50, |
|
|
y: 250, |
|
|
parent: 'platform-overview' |
|
|
}, |
|
|
{ |
|
|
id: 'functional-areas', |
|
|
title: 'Functional Areas', |
|
|
icon: 'fa-sitemap', |
|
|
color: 'bg-purple-500', |
|
|
content: [ |
|
|
'Request Intake', |
|
|
'Proposal Management', |
|
|
'Project & Iteration Management', |
|
|
'Messaging', |
|
|
'Time Tracking & Finance', |
|
|
'Reviews & Ratings', |
|
|
'Email & Proposal Tracking' |
|
|
], |
|
|
x: 50, |
|
|
y: 450, |
|
|
parent: 'platform-overview' |
|
|
}, |
|
|
{ |
|
|
id: 'request-intake', |
|
|
title: 'Request Intake', |
|
|
icon: 'fa-inbox', |
|
|
color: 'bg-purple-300', |
|
|
content: [ |
|
|
'Customer submits request', |
|
|
'Optional scope grooming', |
|
|
'Budget preview', |
|
|
'Feature toggles' |
|
|
], |
|
|
x: 300, |
|
|
y: 350, |
|
|
parent: 'functional-areas' |
|
|
}, |
|
|
{ |
|
|
id: 'proposal-management', |
|
|
title: 'Proposal Management', |
|
|
icon: 'fa-file-contract', |
|
|
color: 'bg-purple-300', |
|
|
content: [ |
|
|
'Generate proposal with price + scope', |
|
|
'Email-based response tracking', |
|
|
'Customer accepts/rejects', |
|
|
'Digital signature support', |
|
|
'Advance payment integration' |
|
|
], |
|
|
x: 300, |
|
|
y: 450, |
|
|
parent: 'functional-areas' |
|
|
}, |
|
|
{ |
|
|
id: 'project-iteration', |
|
|
title: 'Project & Iteration', |
|
|
icon: 'fa-tasks', |
|
|
color: 'bg-purple-300', |
|
|
content: [ |
|
|
'Projects contain multiple iterations', |
|
|
'Markdown deliverable descriptions', |
|
|
'Downloadable attachments', |
|
|
'Linked customer feedback', |
|
|
'Review process with accept/changes' |
|
|
], |
|
|
x: 300, |
|
|
y: 550, |
|
|
parent: 'functional-areas' |
|
|
}, |
|
|
{ |
|
|
id: 'messaging', |
|
|
title: 'Messaging', |
|
|
icon: 'fa-comments', |
|
|
color: 'bg-purple-300', |
|
|
content: [ |
|
|
'Tied to project and iteration', |
|
|
'File attachments support', |
|
|
'Threaded conversations', |
|
|
'Real-time notifications' |
|
|
], |
|
|
x: 300, |
|
|
y: 650, |
|
|
parent: 'functional-areas' |
|
|
}, |
|
|
{ |
|
|
id: 'time-finance', |
|
|
title: 'Time & Finance', |
|
|
icon: 'fa-clock', |
|
|
color: 'bg-purple-300', |
|
|
content: [ |
|
|
'Hour logging per iteration', |
|
|
'Compare hours vs. budget', |
|
|
'Automatic rate application', |
|
|
'Payment tracking', |
|
|
'Invoicing' |
|
|
], |
|
|
x: 300, |
|
|
y: 750, |
|
|
parent: 'functional-areas' |
|
|
}, |
|
|
{ |
|
|
id: 'data-model', |
|
|
title: 'Data Model', |
|
|
icon: 'fa-database', |
|
|
color: 'bg-yellow-500', |
|
|
content: [ |
|
|
'Users (freelancer, customer)', |
|
|
'Projects', |
|
|
'Proposals', |
|
|
'Iterations', |
|
|
'Deliverables', |
|
|
'Messages', |
|
|
'TimeLogs', |
|
|
'Payments', |
|
|
'Reviews', |
|
|
'EmailTrackers' |
|
|
], |
|
|
x: 50, |
|
|
y: 650, |
|
|
parent: 'platform-overview' |
|
|
}, |
|
|
{ |
|
|
id: 'api-layer', |
|
|
title: 'API Layer', |
|
|
icon: 'fa-plug', |
|
|
color: 'bg-red-500', |
|
|
content: [ |
|
|
'RESTful API with domain services', |
|
|
'CRUD for all key entities', |
|
|
'JWT authentication', |
|
|
'Endpoint groups:', |
|
|
'- /auth, /projects', |
|
|
'- /proposals, /iterations', |
|
|
'- /messages, /reviews', |
|
|
'- /time-logs, /email-trackers', |
|
|
'File uploads for attachments' |
|
|
], |
|
|
x: 50, |
|
|
y: 850, |
|
|
parent: 'platform-overview' |
|
|
}, |
|
|
{ |
|
|
id: 'backend-arch', |
|
|
title: 'Backend Architecture', |
|
|
icon: 'fa-server', |
|
|
color: 'bg-red-500', |
|
|
content: [ |
|
|
'Node.js + LowDB for prototyping', |
|
|
'Lightweight JSON DB', |
|
|
'Modular domain services', |
|
|
'Central config and error handling', |
|
|
'Scalable Microservices (Logical)', |
|
|
'Separated services per domain', |
|
|
'API gateway simulation' |
|
|
], |
|
|
x: 300, |
|
|
y: 850, |
|
|
parent: 'api-layer' |
|
|
}, |
|
|
{ |
|
|
id: 'microfrontend', |
|
|
title: 'Microfrontend Design', |
|
|
icon: 'fa-window-maximize', |
|
|
color: 'bg-red-500', |
|
|
content: [ |
|
|
'Independent UI apps per domain:', |
|
|
'- Project dashboard', |
|
|
'- Proposal builder', |
|
|
'- Iteration tracker', |
|
|
'- Review system', |
|
|
'- Messaging panel', |
|
|
'- Time logging', |
|
|
'Deployed independently', |
|
|
'Talks to API gateway' |
|
|
], |
|
|
x: 600, |
|
|
y: 850, |
|
|
parent: 'api-layer' |
|
|
}, |
|
|
{ |
|
|
id: 'ui-ux', |
|
|
title: 'UI/UX Notes', |
|
|
icon: 'fa-paint-brush', |
|
|
color: 'bg-indigo-500', |
|
|
content: [ |
|
|
'Mobile-first design', |
|
|
'Scope sliders or checkboxes', |
|
|
'Kanban board style dashboard', |
|
|
'Simple chat-style messaging', |
|
|
'Progress meters for time/budget', |
|
|
'Email timeline and interactions', |
|
|
'Deliverable previews' |
|
|
], |
|
|
x: 50, |
|
|
y: 1050, |
|
|
parent: 'platform-overview' |
|
|
} |
|
|
]; |
|
|
|
|
|
|
|
|
function renderMindmap() { |
|
|
mindmap.innerHTML = ''; |
|
|
|
|
|
|
|
|
nodes.forEach(node => { |
|
|
if (node.parent) { |
|
|
const parentNode = nodes.find(n => n.id === node.parent); |
|
|
if (parentNode) { |
|
|
createConnector(parentNode, node); |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
nodes.forEach(node => { |
|
|
createNode(node); |
|
|
}); |
|
|
} |
|
|
|
|
|
function createNode(node) { |
|
|
const nodeElement = document.createElement('div'); |
|
|
nodeElement.className = `node ${node.color} text-white`; |
|
|
nodeElement.id = `node-${node.id}`; |
|
|
nodeElement.style.left = `${node.x}px`; |
|
|
nodeElement.style.top = `${node.y}px`; |
|
|
|
|
|
nodeElement.innerHTML = ` |
|
|
<div class="flex items-center"> |
|
|
<i class="node-icon fas ${node.icon}"></i> |
|
|
<h3 class="font-bold">${node.title}</h3> |
|
|
</div> |
|
|
<div class="node-content"> |
|
|
${node.content.map(item => `<div class="node-item">${item}</div>`).join('')} |
|
|
</div> |
|
|
`; |
|
|
|
|
|
|
|
|
nodeElement.addEventListener('mousedown', startDrag); |
|
|
|
|
|
mindmap.appendChild(nodeElement); |
|
|
} |
|
|
|
|
|
function createConnector(parentNode, childNode) { |
|
|
const connector = document.createElement('div'); |
|
|
connector.className = 'connector'; |
|
|
|
|
|
|
|
|
const startX = parentNode.x + 150; |
|
|
const startY = parentNode.y + 50; |
|
|
const endX = childNode.x; |
|
|
const endY = childNode.y + 50; |
|
|
|
|
|
const length = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2)); |
|
|
const angle = Math.atan2(endY - startY, endX - startX) * 180 / Math.PI; |
|
|
|
|
|
connector.style.width = `${length}px`; |
|
|
connector.style.left = `${startX}px`; |
|
|
connector.style.top = `${startY}px`; |
|
|
connector.style.transform = `rotate(${angle}deg)`; |
|
|
|
|
|
mindmap.appendChild(connector); |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('zoomIn').addEventListener('click', () => { |
|
|
scale += 0.1; |
|
|
updateTransform(); |
|
|
}); |
|
|
|
|
|
document.getElementById('zoomOut').addEventListener('click', () => { |
|
|
if (scale > 0.5) { |
|
|
scale -= 0.1; |
|
|
updateTransform(); |
|
|
} |
|
|
}); |
|
|
|
|
|
document.getElementById('resetZoom').addEventListener('click', () => { |
|
|
scale = 1; |
|
|
offsetX = 0; |
|
|
offsetY = 0; |
|
|
updateTransform(); |
|
|
}); |
|
|
|
|
|
function updateTransform() { |
|
|
mindmap.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`; |
|
|
} |
|
|
|
|
|
|
|
|
function startDrag(e) { |
|
|
if (e.button !== 0) return; |
|
|
|
|
|
const node = e.currentTarget; |
|
|
const rect = node.getBoundingClientRect(); |
|
|
|
|
|
isDragging = true; |
|
|
startX = e.clientX - offsetX; |
|
|
startY = e.clientY - offsetY; |
|
|
|
|
|
document.addEventListener('mousemove', drag); |
|
|
document.addEventListener('mouseup', stopDrag); |
|
|
|
|
|
e.preventDefault(); |
|
|
} |
|
|
|
|
|
function drag(e) { |
|
|
if (!isDragging) return; |
|
|
|
|
|
offsetX = e.clientX - startX; |
|
|
offsetY = e.clientY - startY; |
|
|
|
|
|
updateTransform(); |
|
|
} |
|
|
|
|
|
function stopDrag() { |
|
|
isDragging = false; |
|
|
document.removeEventListener('mousemove', drag); |
|
|
document.removeEventListener('mouseup', stopDrag); |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('searchInput').addEventListener('input', function(e) { |
|
|
const searchTerm = e.target.value.toLowerCase(); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.node').forEach(node => { |
|
|
node.classList.remove('highlight'); |
|
|
}); |
|
|
|
|
|
if (searchTerm.trim() === '') return; |
|
|
|
|
|
|
|
|
nodes.forEach(node => { |
|
|
const nodeMatches = node.title.toLowerCase().includes(searchTerm) || |
|
|
node.content.some(item => item.toLowerCase().includes(searchTerm)); |
|
|
|
|
|
if (nodeMatches) { |
|
|
const nodeElement = document.getElementById(`node-${node.id}`); |
|
|
if (nodeElement) { |
|
|
nodeElement.classList.add('highlight'); |
|
|
|
|
|
|
|
|
const x = node.x * scale + offsetX; |
|
|
const y = node.y * scale + offsetY; |
|
|
|
|
|
window.scrollTo({ |
|
|
left: x - window.innerWidth / 2, |
|
|
top: y - window.innerHeight / 2, |
|
|
behavior: 'smooth' |
|
|
}); |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
renderMindmap(); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
const centerX = (mindmap.scrollWidth - window.innerWidth) / 2; |
|
|
const centerY = (mindmap.scrollHeight - window.innerHeight) / 2; |
|
|
|
|
|
offsetX = -centerX; |
|
|
offsetY = -centerY; |
|
|
updateTransform(); |
|
|
}, 100); |
|
|
}); |
|
|
</script> |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=LukasBe/mindmap" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |