Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>AI Department Roadmap Builder</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"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> | |
<style> | |
.timeline-item:not(:last-child)::after { | |
content: ''; | |
position: absolute; | |
left: 24px; | |
top: 32px; | |
height: calc(100% - 32px); | |
width: 2px; | |
background-color: #e5e7eb; | |
} | |
.draggable { | |
cursor: move; | |
user-select: none; | |
transition: transform 0.2s, box-shadow 0.2s; | |
} | |
.draggable:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
} | |
.draggable.dragging { | |
opacity: 0.5; | |
transform: scale(0.98); | |
} | |
.dropzone { | |
min-height: 100px; | |
transition: all 0.3s; | |
} | |
.dropzone.active { | |
background-color: rgba(59, 130, 246, 0.1); | |
border: 1px dashed #3b82f6; | |
} | |
.component-pulse { | |
animation: pulse 2s infinite; | |
} | |
@keyframes pulse { | |
0% { transform: scale(1); } | |
50% { transform: scale(1.05); } | |
100% { transform: scale(1); } | |
} | |
.print-only { | |
display: none; | |
} | |
@media print { | |
.no-print { | |
display: none ; | |
} | |
.print-only { | |
display: block ; | |
} | |
body { | |
background: white; | |
font-size: 12pt; | |
} | |
.container { | |
width: 100%; | |
max-width: 100%; | |
padding: 0; | |
} | |
.shadow { | |
box-shadow: none ; | |
} | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 min-h-screen"> | |
<div class="container mx-auto px-4 py-8"> | |
<!-- Header --> | |
<header class="mb-8"> | |
<div class="flex justify-between items-center"> | |
<div> | |
<h1 class="text-3xl font-bold text-gray-800">AI Department Roadmap Builder</h1> | |
<p class="text-gray-600">Strategic planning tool for AI initiatives</p> | |
</div> | |
<div class="flex space-x-4 no-print"> | |
<button id="exportBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center transition-colors"> | |
<i class="fas fa-file-export mr-2"></i> Export | |
</button> | |
<button id="printBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-lg flex items-center transition-colors"> | |
<i class="fas fa-print mr-2"></i> Print | |
</button> | |
<button id="saveBtn" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg flex items-center transition-colors"> | |
<i class="fas fa-save mr-2"></i> Save | |
</button> | |
<button id="loadBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg flex items-center transition-colors"> | |
<i class="fas fa-folder-open mr-2"></i> Load | |
</button> | |
</div> | |
</div> | |
<div class="print-only text-center mb-4"> | |
<h1 class="text-2xl font-bold">AI Department Roadmap</h1> | |
<p class="text-sm text-gray-600">Generated on: <span id="printDate"></span></p> | |
</div> | |
</header> | |
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6"> | |
<!-- Left Panel - Components --> | |
<div class="lg:col-span-1 bg-white rounded-xl shadow p-6 no-print"> | |
<h2 class="text-xl font-semibold mb-4 text-gray-800">Roadmap Components</h2> | |
<div class="space-y-4"> | |
<div class="component-group"> | |
<h3 class="font-medium text-gray-700 mb-2 flex items-center"> | |
<i class="fas fa-layer-group mr-2 text-blue-500"></i> Strategic Goals | |
</h3> | |
<div class="space-y-2"> | |
<div draggable="true" class="draggable bg-blue-50 border border-blue-200 rounded-lg p-3 text-blue-800" data-type="goal" data-id="goal-1"> | |
<div class="flex items-center"> | |
<i class="fas fa-bullseye mr-2"></i> | |
<span>Improve AI Model Accuracy</span> | |
</div> | |
</div> | |
<div draggable="true" class="draggable bg-blue-50 border border-blue-200 rounded-lg p-3 text-blue-800" data-type="goal" data-id="goal-2"> | |
<div class="flex items-center"> | |
<i class="fas fa-bullseye mr-2"></i> | |
<span>Reduce Model Bias</span> | |
</div> | |
</div> | |
<div draggable="true" class="draggable bg-blue-50 border border-blue-200 rounded-lg p-3 text-blue-800" data-type="goal" data-id="goal-3"> | |
<div class="flex items-center"> | |
<i class="fas fa-bullseye mr-2"></i> | |
<span>Enhance Explainability</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="component-group"> | |
<h3 class="font-medium text-gray-700 mb-2 flex items-center"> | |
<i class="fas fa-tasks mr-2 text-green-500"></i> Initiatives | |
</h3> | |
<div class="space-y-2"> | |
<div draggable="true" class="draggable bg-green-50 border border-green-200 rounded-lg p-3 text-green-800" data-type="initiative" data-id="initiative-1"> | |
<div class="flex items-center"> | |
<i class="fas fa-project-diagram mr-2"></i> | |
<span>Model Optimization</span> | |
</div> | |
</div> | |
<div draggable="true" class="draggable bg-green-50 border border-green-200 rounded-lg p-3 text-green-800" data-type="initiative" data-id="initiative-2"> | |
<div class="flex items-center"> | |
<i class="fas fa-project-diagram mr-2"></i> | |
<span>Data Pipeline Upgrade</span> | |
</div> | |
</div> | |
<div draggable="true" class="draggable bg-green-50 border border-green-200 rounded-lg p-3 text-green-800" data-type="initiative" data-id="initiative-3"> | |
<div class="flex items-center"> | |
<i class="fas fa-project-diagram mr-2"></i> | |
<span>Ethics Framework</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="component-group"> | |
<h3 class="font-medium text-gray-700 mb-2 flex items-center"> | |
<i class="fas fa-calendar-check mr-2 text-purple-500"></i> Milestones | |
</h3> | |
<div class="space-y-2"> | |
<div draggable="true" class="draggable bg-purple-50 border border-purple-200 rounded-lg p-3 text-purple-800" data-type="milestone" data-id="milestone-1"> | |
<div class="flex items-center"> | |
<i class="fas fa-flag-checkered mr-2"></i> | |
<span>Model Validation</span> | |
</div> | |
</div> | |
<div draggable="true" class="draggable bg-purple-50 border border-purple-200 rounded-lg p-3 text-purple-800" data-type="milestone" data-id="milestone-2"> | |
<div class="flex items-center"> | |
<i class="fas fa-flag-checkered mr-2"></i> | |
<span>Deployment</span> | |
</div> | |
</div> | |
<div draggable="true" class="draggable bg-purple-50 border border-purple-200 rounded-lg p-3 text-purple-800" data-type="milestone" data-id="milestone-3"> | |
<div class="flex items-center"> | |
<i class="fas fa-flag-checkered mr-2"></i> | |
<span>Audit Completion</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="component-group"> | |
<h3 class="font-medium text-gray-700 mb-2 flex items-center"> | |
<i class="fas fa-users mr-2 text-yellow-500"></i> Resources | |
</h3> | |
<div class="space-y-2"> | |
<div draggable="true" class="draggable bg-yellow-50 border border-yellow-200 rounded-lg p-3 text-yellow-800" data-type="resource" data-id="resource-1"> | |
<div class="flex items-center"> | |
<i class="fas fa-user-tie mr-2"></i> | |
<span>Research Team</span> | |
</div> | |
</div> | |
<div draggable="true" class="draggable bg-yellow-50 border border-yellow-200 rounded-lg p-3 text-yellow-800" data-type="resource" data-id="resource-2"> | |
<div class="flex items-center"> | |
<i class="fas fa-server mr-2"></i> | |
<span>GPU Cluster</span> | |
</div> | |
</div> | |
<div draggable="true" class="draggable bg-yellow-50 border border-yellow-200 rounded-lg p-3 text-yellow-800" data-type="resource" data-id="resource-3"> | |
<div class="flex items-center"> | |
<i class="fas fa-money-bill-wave mr-2"></i> | |
<span>Budget Allocation</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="mt-6"> | |
<h3 class="font-medium text-gray-700 mb-2 flex items-center"> | |
<i class="fas fa-plus-circle mr-2 text-red-500"></i> Custom Component | |
</h3> | |
<div class="flex"> | |
<input type="text" id="customComponent" placeholder="New component name" class="flex-1 border rounded-l-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
<button id="addCustomBtn" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-r-lg transition-colors"> | |
Add | |
</button> | |
</div> | |
<select id="customType" class="mt-2 w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
<option value="goal">Strategic Goal</option> | |
<option value="initiative">Initiative</option> | |
<option value="milestone">Milestone</option> | |
<option value="resource">Resource</option> | |
</select> | |
</div> | |
</div> | |
<!-- Main Roadmap Area --> | |
<div class="lg:col-span-3"> | |
<div class="bg-white rounded-xl shadow p-6 mb-6"> | |
<div class="flex justify-between items-center mb-4 no-print"> | |
<h2 class="text-xl font-semibold text-gray-800">AI Department Roadmap</h2> | |
<div class="flex space-x-2"> | |
<select id="timeframe" class="border rounded-lg px-3 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
<option value="quarterly">Quarterly View</option> | |
<option value="yearly">Yearly View</option> | |
<option value="3year">3-Year Plan</option> | |
</select> | |
<button id="clearAllBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-3 py-1 rounded-lg text-sm transition-colors"> | |
Clear All | |
</button> | |
</div> | |
</div> | |
<div class="overflow-x-auto"> | |
<div id="roadmapTimeline" class="min-w-full"> | |
<!-- Timeline will be generated here --> | |
<div class="relative"> | |
<!-- Q1 --> | |
<div class="timeline-item relative pb-8 pl-10"> | |
<div class="absolute left-0 top-0 w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white font-bold"> | |
Q1 | |
</div> | |
<div class="dropzone p-3 rounded-lg border border-dashed border-gray-300" data-timeframe="q1"> | |
<h3 class="font-medium text-gray-700 mb-2">Q1 2024</h3> | |
<div class="space-y-2" id="q1-items"></div> | |
</div> | |
</div> | |
<!-- Q2 --> | |
<div class="timeline-item relative pb-8 pl-10"> | |
<div class="absolute left-0 top-0 w-8 h-8 rounded-full bg-green-500 flex items-center justify-center text-white font-bold"> | |
Q2 | |
</div> | |
<div class="dropzone p-3 rounded-lg border border-dashed border-gray-300" data-timeframe="q2"> | |
<h3 class="font-medium text-gray-700 mb-2">Q2 2024</h3> | |
<div class="space-y-2" id="q2-items"></div> | |
</div> | |
</div> | |
<!-- Q3 --> | |
<div class="timeline-item relative pb-8 pl-10"> | |
<div class="absolute left-0 top-0 w-8 h-8 rounded-full bg-yellow-500 flex items-center justify-center text-white font-bold"> | |
Q3 | |
</div> | |
<div class="dropzone p-3 rounded-lg border border-dashed border-gray-300" data-timeframe="q3"> | |
<h3 class="font-medium text-gray-700 mb-2">Q3 2024</h3> | |
<div class="space-y-2" id="q3-items"></div> | |
</div> | |
</div> | |
<!-- Q4 --> | |
<div class="timeline-item relative pb-8 pl-10"> | |
<div class="absolute left-0 top-0 w-8 h-8 rounded-full bg-purple-500 flex items-center justify-center text-white font-bold"> | |
Q4 | |
</div> | |
<div class="dropzone p-3 rounded-lg border border-dashed border-gray-300" data-timeframe="q4"> | |
<h3 class="font-medium text-gray-700 mb-2">Q4 2024</h3> | |
<div class="space-y-2" id="q4-items"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Roadmap Summary --> | |
<div class="bg-white rounded-xl shadow p-6"> | |
<h2 class="text-xl font-semibold mb-4 text-gray-800">Roadmap Summary</h2> | |
<div id="roadmapSummary" class="space-y-4"> | |
<div class="text-center text-gray-500 py-8"> | |
<i class="fas fa-road text-4xl mb-2 text-gray-300"></i> | |
<p>Your roadmap summary will appear here</p> | |
<p class="text-sm">Drag and drop components to build your AI department roadmap</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Save/Load Modal --> | |
<div id="saveLoadModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
<div class="bg-white rounded-xl shadow-xl p-6 w-full max-w-md"> | |
<div class="flex justify-between items-center mb-4"> | |
<h3 class="text-xl font-semibold" id="modalTitle">Save Roadmap</h3> | |
<button id="closeModalBtn" class="text-gray-500 hover:text-gray-700"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
<div id="modalContent"> | |
<!-- Content will be filled dynamically --> | |
</div> | |
</div> | |
</div> | |
<script> | |
// Initialize jsPDF | |
const { jsPDF } = window.jspdf; | |
document.addEventListener('DOMContentLoaded', function() { | |
// Set print date | |
const now = new Date(); | |
document.getElementById('printDate').textContent = now.toLocaleDateString() + ' ' + now.toLocaleTimeString(); | |
// Initialize roadmap state | |
let roadmapState = { | |
q1: [], | |
q2: [], | |
q3: [], | |
q4: [] | |
}; | |
// Generate unique ID | |
function generateId() { | |
return 'id-' + Math.random().toString(36).substr(2, 9); | |
} | |
// Drag and drop functionality | |
const draggables = document.querySelectorAll('.draggable'); | |
const dropzones = document.querySelectorAll('.dropzone'); | |
let draggedItem = null; | |
// Add animation to components to encourage interaction | |
setTimeout(() => { | |
const components = document.querySelectorAll('.component-group .draggable'); | |
components.forEach((comp, index) => { | |
setTimeout(() => { | |
comp.classList.add('component-pulse'); | |
setTimeout(() => { | |
comp.classList.remove('component-pulse'); | |
}, 2000); | |
}, index * 300); | |
}); | |
}, 1000); | |
// Add event listeners for draggable items | |
draggables.forEach(item => { | |
item.addEventListener('dragstart', function(e) { | |
draggedItem = this; | |
this.classList.add('dragging'); | |
e.dataTransfer.setData('text/plain', this.dataset.id); | |
e.dataTransfer.effectAllowed = 'move'; | |
}); | |
item.addEventListener('dragend', function() { | |
this.classList.remove('dragging'); | |
}); | |
}); | |
// Add event listeners for drop zones | |
dropzones.forEach(zone => { | |
zone.addEventListener('dragover', function(e) { | |
e.preventDefault(); | |
this.classList.add('active'); | |
e.dataTransfer.dropEffect = 'move'; | |
}); | |
zone.addEventListener('dragleave', function() { | |
this.classList.remove('active'); | |
}); | |
zone.addEventListener('drop', function(e) { | |
e.preventDefault(); | |
this.classList.remove('active'); | |
if (draggedItem) { | |
const componentId = e.dataTransfer.getData('text/plain'); | |
const originalComponent = document.querySelector(`[data-id="${componentId}"]`); | |
if (originalComponent) { | |
const timeframe = this.dataset.timeframe; | |
const componentData = { | |
id: componentId, | |
type: originalComponent.dataset.type, | |
text: originalComponent.querySelector('span').textContent, | |
timeframe: timeframe | |
}; | |
// Check if component already exists in this timeframe | |
const existingIndex = roadmapState[timeframe].findIndex(item => item.id === componentId); | |
if (existingIndex === -1) { | |
// Add to new timeframe | |
roadmapState[timeframe].push(componentData); | |
// Remove from previous timeframe if it exists | |
Object.keys(roadmapState).forEach(q => { | |
if (q !== timeframe) { | |
roadmapState[q] = roadmapState[q].filter(item => item.id !== componentId); | |
} | |
}); | |
// Update UI | |
updateRoadmapUI(); | |
updateRoadmapSummary(); | |
} | |
} | |
} | |
}); | |
}); | |
// Update the roadmap UI based on state | |
function updateRoadmapUI() { | |
// Clear all items first | |
document.querySelectorAll('[id$="-items"]').forEach(container => { | |
container.innerHTML = ''; | |
}); | |
// Add items based on state | |
Object.keys(roadmapState).forEach(timeframe => { | |
const container = document.getElementById(`${timeframe}-items`); | |
roadmapState[timeframe].forEach(item => { | |
const component = createRoadmapComponent(item); | |
container.appendChild(component); | |
}); | |
}); | |
} | |
// Create a roadmap component for the timeline | |
function createRoadmapComponent(item) { | |
let bgColor, borderColor, textColor, icon; | |
switch(item.type) { | |
case 'goal': | |
bgColor = 'bg-blue-50'; | |
borderColor = 'border-blue-200'; | |
textColor = 'text-blue-800'; | |
icon = 'fa-bullseye'; | |
break; | |
case 'initiative': | |
bgColor = 'bg-green-50'; | |
borderColor = 'border-green-200'; | |
textColor = 'text-green-800'; | |
icon = 'fa-project-diagram'; | |
break; | |
case 'milestone': | |
bgColor = 'bg-purple-50'; | |
borderColor = 'border-purple-200'; | |
textColor = 'text-purple-800'; | |
icon = 'fa-flag-checkered'; | |
break; | |
case 'resource': | |
bgColor = 'bg-yellow-50'; | |
borderColor = 'border-yellow-200'; | |
textColor = 'text-yellow-800'; | |
icon = 'fa-user-tie'; | |
break; | |
} | |
const component = document.createElement('div'); | |
component.className = `${bgColor} border ${borderColor} rounded-lg p-3 ${textColor} relative group`; | |
component.dataset.id = item.id; | |
component.dataset.type = item.type; | |
component.innerHTML = ` | |
<div class="flex items-center"> | |
<i class="fas ${icon} mr-2"></i> | |
<span>${item.text}</span> | |
<button class="delete-btn ml-auto opacity-0 group-hover:opacity-100 text-red-500 hover:text-red-700 transition-opacity"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
`; | |
// Add delete functionality | |
const deleteBtn = component.querySelector('.delete-btn'); | |
deleteBtn.addEventListener('click', function() { | |
roadmapState[item.timeframe] = roadmapState[item.timeframe].filter(i => i.id !== item.id); | |
updateRoadmapUI(); | |
updateRoadmapSummary(); | |
}); | |
return component; | |
} | |
// Add custom component | |
document.getElementById('addCustomBtn').addEventListener('click', function() { | |
const componentName = document.getElementById('customComponent').value.trim(); | |
const componentType = document.getElementById('customType').value; | |
if (componentName) { | |
const componentId = generateId(); | |
let bgColor, borderColor, textColor, icon; | |
switch(componentType) { | |
case 'goal': | |
bgColor = 'bg-blue-50'; | |
borderColor = 'border-blue-200'; | |
textColor = 'text-blue-800'; | |
icon = 'fa-bullseye'; | |
break; | |
case 'initiative': | |
bgColor = 'bg-green-50'; | |
borderColor = 'border-green-200'; | |
textColor = 'text-green-800'; | |
icon = 'fa-project-diagram'; | |
break; | |
case 'milestone': | |
bgColor = 'bg-purple-50'; | |
borderColor = 'border-purple-200'; | |
textColor = 'text-purple-800'; | |
icon = 'fa-flag-checkered'; | |
break; | |
case 'resource': | |
bgColor = 'bg-yellow-50'; | |
borderColor = 'border-yellow-200'; | |
textColor = 'text-yellow-800'; | |
icon = 'fa-user-tie'; | |
break; | |
} | |
const newComponent = document.createElement('div'); | |
newComponent.className = `draggable ${bgColor} border ${borderColor} rounded-lg p-3 ${textColor}`; | |
newComponent.setAttribute('draggable', 'true'); | |
newComponent.setAttribute('data-type', componentType); | |
newComponent.setAttribute('data-id', componentId); | |
newComponent.innerHTML = ` | |
<div class="flex items-center"> | |
<i class="fas ${icon} mr-2"></i> | |
<span>${componentName}</span> | |
</div> | |
`; | |
// Add drag events to the new component | |
newComponent.addEventListener('dragstart', function(e) { | |
draggedItem = this; | |
this.classList.add('dragging'); | |
e.dataTransfer.setData('text/plain', this.dataset.id); | |
e.dataTransfer.effectAllowed = 'move'; | |
}); | |
newComponent.addEventListener('dragend', function() { | |
this.classList.remove('dragging'); | |
}); | |
// Add to the appropriate component group | |
const componentGroups = document.querySelectorAll('.component-group'); | |
componentGroups.forEach(group => { | |
const heading = group.querySelector('h3'); | |
if (heading.textContent.includes(componentType.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase()))) { | |
group.querySelector('.space-y-2').prepend(newComponent); | |
} | |
}); | |
document.getElementById('customComponent').value = ''; | |
// Animate the new component | |
newComponent.classList.add('component-pulse'); | |
setTimeout(() => { | |
newComponent.classList.remove('component-pulse'); | |
}, 2000); | |
} | |
}); | |
// Update roadmap summary | |
function updateRoadmapSummary() { | |
const summaryContainer = document.getElementById('roadmapSummary'); | |
summaryContainer.innerHTML = ''; | |
const quarters = ['q1', 'q2', 'q3', 'q4']; | |
let hasContent = false; | |
quarters.forEach(q => { | |
if (roadmapState[q].length > 0) { | |
hasContent = true; | |
const quarterSection = document.createElement('div'); | |
quarterSection.className = 'bg-gray-50 rounded-lg p-4'; | |
const quarterTitle = document.createElement('h3'); | |
quarterTitle.className = 'font-semibold text-lg mb-2'; | |
quarterTitle.textContent = document.querySelector(`[data-timeframe="${q}"] h3`).textContent; | |
quarterSection.appendChild(quarterTitle); | |
const itemsList = document.createElement('ul'); | |
itemsList.className = 'space-y-2'; | |
roadmapState[q].forEach(item => { | |
const listItem = document.createElement('li'); | |
listItem.className = 'flex items-center'; | |
let typeBadge; | |
switch(item.type) { | |
case 'goal': | |
typeBadge = '<span class="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded mr-2">Goal</span>'; | |
break; | |
case 'initiative': | |
typeBadge = '<span class="bg-green-100 text-green-800 text-xs px-2 py-1 rounded mr-2">Initiative</span>'; | |
break; | |
case 'milestone': | |
typeBadge = '<span class="bg-purple-100 text-purple-800 text-xs px-2 py-1 rounded mr-2">Milestone</span>'; | |
break; | |
case 'resource': | |
typeBadge = '<span class="bg-yellow-100 text-yellow-800 text-xs px-2 py-1 rounded mr-2">Resource</span>'; | |
break; | |
} | |
listItem.innerHTML = ` | |
${typeBadge} | |
<span>${item.text}</span> | |
`; | |
itemsList.appendChild(listItem); | |
}); | |
quarterSection.appendChild(itemsList); | |
summaryContainer.appendChild(quarterSection); | |
} | |
}); | |
if (!hasContent) { | |
summaryContainer.innerHTML = ` | |
<div class="text-center text-gray-500 py-8"> | |
<i class="fas fa-road text-4xl mb-2 text-gray-300"></i> | |
<p>Your roadmap summary will appear here</p> | |
<p class="text-sm">Drag and drop components to build your AI department roadmap</p> | |
</div> | |
`; | |
} | |
} | |
// Clear all button | |
document.getElementById('clearAllBtn').addEventListener('click', function() { | |
if (confirm('Are you sure you want to clear all items from the roadmap?')) { | |
Object.keys(roadmapState).forEach(q => { | |
roadmapState[q] = []; | |
}); | |
updateRoadmapUI(); | |
updateRoadmapSummary(); | |
} | |
}); | |
// Export button (PDF) | |
document.getElementById('exportBtn').addEventListener('click', function() { | |
// Create a PDF of the roadmap | |
const roadmapElement = document.getElementById('roadmapTimeline'); | |
html2canvas(roadmapElement, { | |
scale: 2, | |
logging: false, | |
useCORS: true, | |
allowTaint: true | |
}).then(canvas => { | |
const imgData = canvas.toDataURL('image/png'); | |
const pdf = new jsPDF('p', 'mm', 'a4'); | |
const imgWidth = 210; // A4 width in mm | |
const pageHeight = 295; // A4 height in mm | |
const imgHeight = canvas.height * imgWidth / canvas.width; | |
let heightLeft = imgHeight; | |
let position = 0; | |
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); | |
heightLeft -= pageHeight; | |
while (heightLeft >= 0) { | |
position = heightLeft - imgHeight; | |
pdf.addPage(); | |
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); | |
heightLeft -= pageHeight; | |
} | |
pdf.save('AI-Roadmap-' + new Date().toISOString().slice(0, 10) + '.pdf'); | |
}); | |
}); | |
// Print button | |
document.getElementById('printBtn').addEventListener('click', function() { | |
window.print(); | |
}); | |
// Save button | |
document.getElementById('saveBtn').addEventListener('click', function() { | |
showSaveModal(); | |
}); | |
// Load button | |
document.getElementById('loadBtn').addEventListener('click', function() { | |
showLoadModal(); | |
}); | |
// Timeframe selector | |
document.getElementById('timeframe').addEventListener('change', function() { | |
// In a real app, this would change the view of the timeline | |
// For now, we'll just show a notification | |
const notification = document.createElement('div'); | |
notification.className = 'fixed bottom-4 right-4 bg-blue-500 text-white px-4 py-2 rounded-lg shadow-lg'; | |
notification.textContent = `View changed to ${this.options[this.selectedIndex].text}`; | |
document.body.appendChild(notification); | |
setTimeout(() => { | |
notification.classList.add('opacity-0', 'transition-opacity', 'duration-500'); | |
setTimeout(() => { | |
notification.remove(); | |
}, 500); | |
}, 2000); | |
}); | |
// Modal functionality | |
const modal = document.getElementById('saveLoadModal'); | |
const modalTitle = document.getElementById('modalTitle'); | |
const modalContent = document.getElementById('modalContent'); | |
const closeModalBtn = document.getElementById('closeModalBtn'); | |
closeModalBtn.addEventListener('click', function() { | |
modal.classList.add('hidden'); | |
}); | |
function showSaveModal() { | |
modalTitle.textContent = 'Save Roadmap'; | |
const content = ` | |
<div class="mb-4"> | |
<label for="roadmapName" class="block text-sm font-medium text-gray-700 mb-1">Roadmap Name</label> | |
<input type="text" id="roadmapName" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="My AI Roadmap"> | |
</div> | |
<div class="flex justify-end space-x-2"> | |
<button id="cancelSaveBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-lg">Cancel</button> | |
<button id="confirmSaveBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">Save</button> | |
</div> | |
`; | |
modalContent.innerHTML = content; | |
modal.classList.remove('hidden'); | |
document.getElementById('cancelSaveBtn').addEventListener('click', function() { | |
modal.classList.add('hidden'); | |
}); | |
document.getElementById('confirmSaveBtn').addEventListener('click', function() { | |
const roadmapName = document.getElementById('roadmapName').value.trim() || 'My AI Roadmap'; | |
const roadmapData = { | |
name: roadmapName, | |
date: new Date().toISOString(), | |
state: roadmapState | |
}; | |
localStorage.setItem('aiRoadmap', JSON.stringify(roadmapData)); | |
// Show success notification | |
const notification = document.createElement('div'); | |
notification.className = 'fixed bottom-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg'; | |
notification.textContent = `Roadmap "${roadmapName}" saved successfully!`; | |
document.body.appendChild(notification); | |
setTimeout(() => { | |
notification.classList.add('opacity-0', 'transition-opacity', 'duration-500'); | |
setTimeout(() => { | |
notification.remove(); | |
}, 500); | |
}, 2000); | |
modal.classList.add('hidden'); | |
}); | |
} | |
function showLoadModal() { | |
const savedRoadmap = localStorage.getItem('aiRoadmap'); | |
if (!savedRoadmap) { | |
modalTitle.textContent = 'No Saved Roadmap'; | |
modalContent.innerHTML = ` | |
<div class="text-center py-4"> | |
<i class="fas fa-exclamation-circle text-4xl text-yellow-500 mb-2"></i> | |
<p>No saved roadmap found.</p> | |
</div> | |
<div class="flex justify-end"> | |
<button id="closeEmptyModalBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">OK</button> | |
</div> | |
`; | |
modal.classList.remove('hidden'); | |
document.getElementById('closeEmptyModalBtn').addEventListener('click', function() { | |
modal.classList.add('hidden'); | |
}); | |
return; | |
} | |
modalTitle.textContent = 'Load Roadmap'; | |
try { | |
const roadmapData = JSON.parse(savedRoadmap); | |
const content = ` | |
<div class="mb-4"> | |
<p class="text-sm text-gray-600 mb-1">Saved Roadmap:</p> | |
<div class="bg-gray-50 p-3 rounded-lg"> | |
<h4 class="font-medium">${roadmapData.name}</h4> | |
<p class="text-xs text-gray-500">Saved on: ${new Date(roadmapData.date).toLocaleString()}</p> | |
</div> | |
</div> | |
<div class="mb-4"> | |
<p class="text-sm font-medium text-gray-700 mb-1">This will replace your current roadmap. Continue?</p> | |
</div> | |
<div class="flex justify-end space-x-2"> | |
<button id="cancelLoadBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-lg">Cancel</button> | |
<button id="confirmLoadBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">Load</button> | |
</div> | |
`; | |
modalContent.innerHTML = content; | |
modal.classList.remove('hidden'); | |
document.getElementById('cancelLoadBtn').addEventListener('click', function() { | |
modal.classList.add('hidden'); | |
}); | |
document.getElementById('confirmLoadBtn').addEventListener('click', function() { | |
roadmapState = roadmapData.state; | |
updateRoadmapUI(); | |
updateRoadmapSummary(); | |
// Show success notification | |
const notification = document.createElement('div'); | |
notification.className = 'fixed bottom-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg'; | |
notification.textContent = `Roadmap "${roadmapData.name}" loaded successfully!`; | |
document.body.appendChild(notification); | |
setTimeout(() => { | |
notification.classList.add('opacity-0', 'transition-opacity', 'duration-500'); | |
setTimeout(() => { | |
notification.remove(); | |
}, 500); | |
}, 2000); | |
modal.classList.add('hidden'); | |
}); | |
} catch (e) { | |
modalTitle.textContent = 'Error Loading Roadmap'; | |
modalContent.innerHTML = ` | |
<div class="text-center py-4"> | |
<i class="fas fa-exclamation-triangle text-4xl text-red-500 mb-2"></i> | |
<p>Error loading saved roadmap.</p> | |
</div> | |
<div class="flex justify-end"> | |
<button id="closeErrorModalBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">OK</button> | |
</div> | |
`; | |
modal.classList.remove('hidden'); | |
document.getElementById('closeErrorModalBtn').addEventListener('click', function() { | |
modal.classList.add('hidden'); | |
}); | |
} | |
} | |
// Close modal when clicking outside | |
modal.addEventListener('click', function(e) { | |
if (e.target === modal) { | |
modal.classList.add('hidden'); | |
} | |
}); | |
// Initialize empty roadmap | |
updateRoadmapSummary(); | |
}); | |
</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=aceeee/ai-rm-builder-1" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |