mindmap / index.html
LukasBe's picture
Add 2 files
6c6808f verified
<!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">
<!-- Nodes will be added here by JavaScript -->
</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;
// Mind map data structure
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'
}
];
// Create nodes and connectors
function renderMindmap() {
mindmap.innerHTML = '';
// Create connectors first (so they appear behind nodes)
nodes.forEach(node => {
if (node.parent) {
const parentNode = nodes.find(n => n.id === node.parent);
if (parentNode) {
createConnector(parentNode, node);
}
}
});
// Create nodes
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>
`;
// Make nodes draggable
nodeElement.addEventListener('mousedown', startDrag);
mindmap.appendChild(nodeElement);
}
function createConnector(parentNode, childNode) {
const connector = document.createElement('div');
connector.className = 'connector';
// Calculate connector position and dimensions
const startX = parentNode.x + 150; // Assuming node width ~300px
const startY = parentNode.y + 50; // Middle of parent node
const endX = childNode.x;
const endY = childNode.y + 50; // Middle of child node
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);
}
// Zoom functionality
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})`;
}
// Dragging functionality for the mindmap
function startDrag(e) {
if (e.button !== 0) return; // Only left mouse button
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);
}
// Search functionality
document.getElementById('searchInput').addEventListener('input', function(e) {
const searchTerm = e.target.value.toLowerCase();
// Remove highlights from all nodes
document.querySelectorAll('.node').forEach(node => {
node.classList.remove('highlight');
});
if (searchTerm.trim() === '') return;
// Highlight matching nodes
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');
// Scroll to the node (approximate)
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'
});
}
}
});
});
// Initial render
renderMindmap();
// Center the mindmap on load
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>