@atlashub/smartstack-cli 3.7.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/package.json +3 -2
  2. package/templates/skills/business-analyse/html/ba-interactive.html +3058 -2252
  3. package/templates/skills/business-analyse/html/build-html.js +77 -0
  4. package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +129 -0
  5. package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +22 -0
  6. package/templates/skills/business-analyse/html/src/scripts/03-render-cadrage.js +208 -0
  7. package/templates/skills/business-analyse/html/src/scripts/04-render-modules.js +211 -0
  8. package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +542 -0
  9. package/templates/skills/business-analyse/html/src/scripts/06-render-consolidation.js +105 -0
  10. package/templates/skills/business-analyse/html/src/scripts/07-render-handoff.js +90 -0
  11. package/templates/skills/business-analyse/html/src/scripts/08-editing.js +45 -0
  12. package/templates/skills/business-analyse/html/src/scripts/09-export.js +65 -0
  13. package/templates/skills/business-analyse/html/src/scripts/10-comments.js +165 -0
  14. package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +139 -0
  15. package/templates/skills/business-analyse/html/src/styles/01-variables.css +38 -0
  16. package/templates/skills/business-analyse/html/src/styles/02-layout.css +101 -0
  17. package/templates/skills/business-analyse/html/src/styles/03-navigation.css +62 -0
  18. package/templates/skills/business-analyse/html/src/styles/04-cards.css +196 -0
  19. package/templates/skills/business-analyse/html/src/styles/05-modules.css +325 -0
  20. package/templates/skills/business-analyse/html/src/styles/06-wireframes.css +230 -0
  21. package/templates/skills/business-analyse/html/src/styles/07-comments.css +184 -0
  22. package/templates/skills/business-analyse/html/src/styles/08-review-panel.css +229 -0
  23. package/templates/skills/business-analyse/html/src/template.html +622 -0
@@ -0,0 +1,90 @@
1
+ /* ============================================
2
+ HANDOFF / SYNTHESE
3
+ ============================================ */
4
+ function renderHandoff() {
5
+ renderHandoffStats();
6
+ renderHandoffModules();
7
+ renderCoverageMatrix();
8
+ }
9
+
10
+ function renderHandoffStats() {
11
+ const container = document.getElementById('handoffStats');
12
+ if (!container) return;
13
+
14
+ const totalUCs = data.modules.reduce((sum, m) => sum + (data.moduleSpecs[m.code]?.useCases || []).length, 0);
15
+ const totalBRs = data.modules.reduce((sum, m) => sum + (data.moduleSpecs[m.code]?.businessRules || []).length, 0);
16
+ const totalEnts = data.modules.reduce((sum, m) => sum + (data.moduleSpecs[m.code]?.entities || []).length, 0);
17
+ const totalStakeholders = data.cadrage.stakeholders.length;
18
+
19
+ container.innerHTML = `
20
+ <div class="stat-card"><div class="stat-value">${data.modules.length}</div><div class="stat-label">Domaines fonctionnels</div></div>
21
+ <div class="stat-card"><div class="stat-value">${totalUCs}</div><div class="stat-label">Cas d'utilisation</div></div>
22
+ <div class="stat-card"><div class="stat-value">${totalBRs}</div><div class="stat-label">Regles metier</div></div>
23
+ <div class="stat-card"><div class="stat-value">${totalEnts}</div><div class="stat-label">Types de donnees</div></div>
24
+ <div class="stat-card"><div class="stat-value">${totalStakeholders}</div><div class="stat-label">Profils utilisateurs</div></div>
25
+ <div class="stat-card"><div class="stat-value">${data.dependencies.length}</div><div class="stat-label">Dependances</div></div>
26
+ <div class="stat-card"><div class="stat-value">${data.consolidation.e2eFlows.length}</div><div class="stat-label">Parcours bout en bout</div></div>
27
+ <div class="stat-card"><div class="stat-value">${data.cadrage.risks.length}</div><div class="stat-label">Risques identifies</div></div>
28
+ `;
29
+ }
30
+
31
+ function renderHandoffModules() {
32
+ const container = document.getElementById('handoffModuleList');
33
+ if (!container) return;
34
+
35
+ const layers = computeTopologicalLayers();
36
+ const order = layers ? layers.flat() : data.modules.map(m => m.code);
37
+
38
+ container.innerHTML = order.map((code, i) => {
39
+ const m = data.modules.find(mod => mod.code === code);
40
+ if (!m) return '';
41
+ const spec = data.moduleSpecs[code] || {};
42
+ return `
43
+ <div class="card" style="margin-bottom:0.75rem;">
44
+ <div style="display:flex;align-items:center;gap:0.75rem;">
45
+ <div style="width:28px;height:28px;border-radius:50%;background:var(--primary);display:flex;align-items:center;justify-content:center;color:#fff;font-size:0.75rem;font-weight:700;flex-shrink:0;">${i + 1}</div>
46
+ <div style="flex:1;">
47
+ <div style="font-weight:600;color:var(--text-bright);">${m.name}</div>
48
+ <div style="font-size:0.8rem;color:var(--text-muted);">${m.description || ''}</div>
49
+ </div>
50
+ <div style="display:flex;gap:1rem;font-size:0.75rem;color:var(--text-muted);">
51
+ <span>${(spec.useCases || []).length} cas d'utilisation</span>
52
+ <span>${(spec.businessRules || []).length} regles</span>
53
+ <span>${(spec.entities || []).length} donnees</span>
54
+ </div>
55
+ <span class="priority priority-${m.priority === 'must' ? 'vital' : m.priority === 'should' ? 'important' : 'optional'}">${formatModulePriority(m.priority)}</span>
56
+ </div>
57
+ </div>`;
58
+ }).join('');
59
+ }
60
+
61
+ function renderCoverageMatrix() {
62
+ const container = document.getElementById('coverageMatrix');
63
+ if (!container) return;
64
+
65
+ const allScope = ['vital', 'important', 'optional'].flatMap(p =>
66
+ (data.cadrage.scope[p] || []).map(item => ({ ...item, priority: p }))
67
+ );
68
+
69
+ if (allScope.length === 0) {
70
+ container.innerHTML = '<p style="color:var(--text-muted);font-style:italic;">Aucun element de perimetre defini dans le cadrage.</p>';
71
+ return;
72
+ }
73
+
74
+ container.innerHTML = `
75
+ <table class="mock-table" style="background:var(--bg-card);border-radius:8px;overflow:hidden;">
76
+ <thead><tr><th>Besoin</th><th>Priorite</th><th>Domaine</th><th>Couvert</th></tr></thead>
77
+ <tbody>
78
+ ${allScope.map(item => {
79
+ const moduleName = data.modules.length > 0 ? data.modules[0].name : 'A definir';
80
+ return `
81
+ <tr>
82
+ <td>${item.name}</td>
83
+ <td><span class="priority priority-${item.priority}">${formatPriority(item.priority)}</span></td>
84
+ <td style="color:var(--text-muted);">${moduleName}</td>
85
+ <td style="text-align:center;color:var(--success);">&#10003;</td>
86
+ </tr>`;
87
+ }).join('')}
88
+ </tbody>
89
+ </table>`;
90
+ }
@@ -0,0 +1,45 @@
1
+ /* ============================================
2
+ PERSISTENCE
3
+ ============================================ */
4
+ function autoSave() {
5
+ data.metadata.lastModified = new Date().toISOString();
6
+ localStorage.setItem(APP_KEY, JSON.stringify(data));
7
+ }
8
+
9
+ function saveToLocalStorage() {
10
+ autoSave();
11
+ showNotification('Modifications sauvegardees');
12
+ }
13
+
14
+ function loadFromLocalStorage() {
15
+ const saved = localStorage.getItem(APP_KEY);
16
+ if (saved) {
17
+ try {
18
+ const parsed = JSON.parse(saved);
19
+ // Deep merge with defaults
20
+ data.metadata = { ...data.metadata, ...parsed.metadata };
21
+ data.cadrage = {
22
+ ...data.cadrage,
23
+ ...parsed.cadrage,
24
+ scope: { ...data.cadrage.scope, ...(parsed.cadrage?.scope || {}) }
25
+ };
26
+ data.modules = parsed.modules || [];
27
+ data.dependencies = parsed.dependencies || [];
28
+ data.moduleSpecs = parsed.moduleSpecs || {};
29
+ data.consolidation = { ...data.consolidation, ...(parsed.consolidation || {}) };
30
+ data.handoff = parsed.handoff || {};
31
+ data.wireframeComments = parsed.wireframeComments || {};
32
+ data.specComments = parsed.specComments || {};
33
+ data.customRoles = parsed.customRoles || [];
34
+ data.customActions = parsed.customActions || [];
35
+ data.comments = parsed.comments || [];
36
+
37
+ // Restore editable fields
38
+ document.querySelectorAll('.editable[data-field]').forEach(el => {
39
+ const value = getNestedValue(data, 'cadrage.' + el.dataset.field);
40
+ if (value) el.textContent = value;
41
+ });
42
+ renderProcessFlow();
43
+ } catch (e) { console.error('Error loading saved data:', e); }
44
+ }
45
+ }
@@ -0,0 +1,65 @@
1
+ /* ============================================
2
+ EXPORT JSON
3
+ ============================================ */
4
+ function exportJSON() {
5
+ // Collect all editable fields (cadrage)
6
+ document.querySelectorAll('.editable[data-field]').forEach(el => {
7
+ setNestedValue(data, 'cadrage.' + el.dataset.field, el.textContent.trim());
8
+ });
9
+
10
+ // Collect module editable fields
11
+ document.querySelectorAll('.editable[data-module-field]').forEach(el => {
12
+ const code = el.dataset.moduleCode;
13
+ const field = el.dataset.moduleField;
14
+ if (data.moduleSpecs[code]) {
15
+ data.moduleSpecs[code][field] = el.textContent.trim();
16
+ }
17
+ });
18
+
19
+ data.metadata.lastModified = new Date().toISOString();
20
+ data.metadata.exportedAt = new Date().toISOString();
21
+
22
+ // Build complete export with structured data
23
+ const exportData = {
24
+ metadata: data.metadata,
25
+ cadrage: data.cadrage,
26
+ modules: data.modules,
27
+ dependencies: data.dependencies,
28
+ moduleSpecifications: {},
29
+ consolidation: data.consolidation,
30
+ artifacts: EMBEDDED_ARTIFACTS,
31
+ wireframeComments: data.wireframeComments,
32
+ specComments: data.specComments,
33
+ customRoles: data.customRoles,
34
+ customActions: data.customActions,
35
+ comments: data.comments
36
+ };
37
+
38
+ // Structure module specs for export
39
+ data.modules.forEach(m => {
40
+ const spec = data.moduleSpecs[m.code] || {};
41
+ exportData.moduleSpecifications[m.code] = {
42
+ module: m,
43
+ useCases: (spec.useCases || []).map((uc, i) => ({
44
+ id: 'UC-' + String(i + 1).padStart(3, '0'),
45
+ ...uc
46
+ })),
47
+ businessRules: (spec.businessRules || []).map((br, i) => ({
48
+ id: 'BR-' + br.category.toUpperCase().substring(0, 4) + '-' + String(i + 1).padStart(3, '0'),
49
+ ...br
50
+ })),
51
+ entities: spec.entities || [],
52
+ permissions: spec.permissions || [],
53
+ notes: spec.notes || ''
54
+ };
55
+ });
56
+
57
+ const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
58
+ const url = URL.createObjectURL(blob);
59
+ const a = document.createElement('a');
60
+ a.href = url;
61
+ a.download = (data.metadata.applicationId || 'analyse') + '-export.json';
62
+ a.click();
63
+ URL.revokeObjectURL(url);
64
+ showNotification('Export JSON telecharge');
65
+ }
@@ -0,0 +1,165 @@
1
+ /* ============================================
2
+ INLINE COMMENTS SYSTEM
3
+ ============================================ */
4
+
5
+ /**
6
+ * Comments are stored in data.comments[] with structure:
7
+ * { id, sectionId, cardIndex, author, timestamp, content, status, category }
8
+ *
9
+ * - sectionId: matches the section div id (e.g. "cadrage-problem", "module-spec-Clients")
10
+ * - cardIndex: index of the card within that section (0-based)
11
+ * - status: "to-review" | "validated"
12
+ * - category: "clarification" | "correction" | "suggestion"
13
+ */
14
+
15
+ function initInlineComments() {
16
+ // Add comment buttons under each card and uc-item
17
+ document.querySelectorAll('.section').forEach(section => {
18
+ const sectionId = section.id;
19
+ const cards = section.querySelectorAll(':scope > .card, :scope > .uc-item');
20
+ cards.forEach((card, index) => {
21
+ if (card.querySelector('.comment-btn-container')) return; // already initialized
22
+ const container = createCommentUI(sectionId, index);
23
+ card.appendChild(container);
24
+ });
25
+ });
26
+ }
27
+
28
+ function createCommentUI(sectionId, cardIndex) {
29
+ const comments = getCommentsForCard(sectionId, cardIndex);
30
+ const count = comments.length;
31
+
32
+ const container = document.createElement('div');
33
+ container.className = 'comment-btn-container';
34
+ container.dataset.sectionId = sectionId;
35
+ container.dataset.cardIndex = cardIndex;
36
+
37
+ container.innerHTML = `
38
+ <button class="comment-toggle-btn" onclick="toggleCommentThread('${sectionId}', ${cardIndex})">
39
+ Commentaires <span class="comment-count ${count === 0 ? 'empty' : ''}">${count}</span>
40
+ </button>
41
+ <div class="comment-thread" id="comment-thread-${sectionId}-${cardIndex}">
42
+ <div class="comment-items" id="comment-items-${sectionId}-${cardIndex}">
43
+ ${renderCommentItems(sectionId, cardIndex)}
44
+ </div>
45
+ <div class="comment-add-form">
46
+ <textarea id="comment-text-${sectionId}-${cardIndex}" placeholder="Ajouter un commentaire..."></textarea>
47
+ <select id="comment-cat-${sectionId}-${cardIndex}">
48
+ <option value="clarification">Clarification</option>
49
+ <option value="correction">Correction</option>
50
+ <option value="suggestion">Suggestion</option>
51
+ </select>
52
+ <button onclick="addInlineComment('${sectionId}', ${cardIndex})">Ajouter</button>
53
+ </div>
54
+ </div>
55
+ `;
56
+
57
+ return container;
58
+ }
59
+
60
+ function getCommentsForCard(sectionId, cardIndex) {
61
+ return (data.comments || []).filter(c =>
62
+ c.sectionId === sectionId && c.cardIndex === cardIndex
63
+ );
64
+ }
65
+
66
+ function toggleCommentThread(sectionId, cardIndex) {
67
+ const thread = document.getElementById('comment-thread-' + sectionId + '-' + cardIndex);
68
+ if (thread) thread.classList.toggle('visible');
69
+ }
70
+
71
+ function renderCommentItems(sectionId, cardIndex) {
72
+ const comments = getCommentsForCard(sectionId, cardIndex);
73
+ if (comments.length === 0) {
74
+ return '<div style="font-size:0.8rem;color:var(--text-muted);padding:0.5rem 0;font-style:italic;">Aucun commentaire</div>';
75
+ }
76
+
77
+ return comments.map((c, i) => {
78
+ const initials = (c.author || 'U').substring(0, 2).toUpperCase();
79
+ const date = c.timestamp ? new Date(c.timestamp).toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : '';
80
+ const globalIndex = data.comments.indexOf(c);
81
+
82
+ return `
83
+ <div class="comment-item">
84
+ <div class="comment-avatar">${initials}</div>
85
+ <div class="comment-body">
86
+ <div class="comment-meta">
87
+ <span class="comment-author">${c.author || 'Utilisateur'}</span>
88
+ <span class="comment-date">${date}</span>
89
+ <span class="comment-category comment-category-${c.category}">${c.category}</span>
90
+ <span class="comment-status comment-status-${c.status}">${c.status === 'validated' ? 'Valide' : 'A revoir'}</span>
91
+ </div>
92
+ <div class="comment-text">${c.content}</div>
93
+ <div class="comment-actions">
94
+ <button class="comment-action-btn" onclick="toggleCommentStatus(${globalIndex})">${c.status === 'validated' ? 'Remettre a revoir' : 'Valider'}</button>
95
+ <button class="comment-action-btn" onclick="deleteComment(${globalIndex})" style="color:var(--error);">Supprimer</button>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ `;
100
+ }).join('');
101
+ }
102
+
103
+ function addInlineComment(sectionId, cardIndex) {
104
+ const textEl = document.getElementById('comment-text-' + sectionId + '-' + cardIndex);
105
+ const catEl = document.getElementById('comment-cat-' + sectionId + '-' + cardIndex);
106
+ const content = textEl.value.trim();
107
+ if (!content) return;
108
+
109
+ const comment = {
110
+ id: 'comment-' + Date.now(),
111
+ sectionId: sectionId,
112
+ cardIndex: cardIndex,
113
+ author: 'Utilisateur',
114
+ timestamp: new Date().toISOString(),
115
+ content: content,
116
+ status: 'to-review',
117
+ category: catEl.value
118
+ };
119
+
120
+ data.comments.push(comment);
121
+ textEl.value = '';
122
+
123
+ refreshCommentUI(sectionId, cardIndex);
124
+ renderReviewPanel();
125
+ autoSave();
126
+ }
127
+
128
+ function toggleCommentStatus(globalIndex) {
129
+ const comment = data.comments[globalIndex];
130
+ if (!comment) return;
131
+ comment.status = comment.status === 'validated' ? 'to-review' : 'validated';
132
+ refreshCommentUI(comment.sectionId, comment.cardIndex);
133
+ renderReviewPanel();
134
+ autoSave();
135
+ }
136
+
137
+ function deleteComment(globalIndex) {
138
+ const comment = data.comments[globalIndex];
139
+ if (!comment) return;
140
+ const sectionId = comment.sectionId;
141
+ const cardIndex = comment.cardIndex;
142
+ data.comments.splice(globalIndex, 1);
143
+ refreshCommentUI(sectionId, cardIndex);
144
+ renderReviewPanel();
145
+ autoSave();
146
+ }
147
+
148
+ function refreshCommentUI(sectionId, cardIndex) {
149
+ // Update comment items
150
+ const itemsContainer = document.getElementById('comment-items-' + sectionId + '-' + cardIndex);
151
+ if (itemsContainer) {
152
+ itemsContainer.innerHTML = renderCommentItems(sectionId, cardIndex);
153
+ }
154
+
155
+ // Update count badge
156
+ const count = getCommentsForCard(sectionId, cardIndex).length;
157
+ const container = document.querySelector(`.comment-btn-container[data-section-id="${sectionId}"][data-card-index="${cardIndex}"]`);
158
+ if (container) {
159
+ const badge = container.querySelector('.comment-count');
160
+ if (badge) {
161
+ badge.textContent = count;
162
+ badge.className = 'comment-count' + (count === 0 ? ' empty' : '');
163
+ }
164
+ }
165
+ }
@@ -0,0 +1,139 @@
1
+ /* ============================================
2
+ REVIEW PANEL
3
+ ============================================ */
4
+ let reviewPanelOpen = false;
5
+ let reviewFilter = 'all';
6
+
7
+ function toggleReviewPanel() {
8
+ reviewPanelOpen = !reviewPanelOpen;
9
+ const panel = document.getElementById('reviewPanel');
10
+ const body = document.getElementById('appBody');
11
+ const btn = document.getElementById('reviewToggleBtn');
12
+
13
+ if (reviewPanelOpen) {
14
+ panel.classList.add('visible');
15
+ body.classList.add('review-open');
16
+ btn.classList.add('active');
17
+ } else {
18
+ panel.classList.remove('visible');
19
+ body.classList.remove('review-open');
20
+ btn.classList.remove('active');
21
+ }
22
+
23
+ renderReviewPanel();
24
+ }
25
+
26
+ function filterReviewComments(filter) {
27
+ reviewFilter = filter;
28
+ // Update active filter button
29
+ document.querySelectorAll('.review-filter-btn').forEach(btn => {
30
+ btn.classList.toggle('active', btn.dataset.filter === filter);
31
+ });
32
+ renderReviewPanel();
33
+ }
34
+
35
+ function renderReviewPanel() {
36
+ const comments = data.comments || [];
37
+
38
+ // Update header badge
39
+ const toReviewCount = comments.filter(c => c.status === 'to-review').length;
40
+ const badge = document.getElementById('reviewBadge');
41
+ if (badge) {
42
+ badge.textContent = toReviewCount;
43
+ badge.classList.toggle('hidden', toReviewCount === 0);
44
+ }
45
+
46
+ // Update stats
47
+ const totalEl = document.getElementById('reviewStatTotal');
48
+ const toReviewEl = document.getElementById('reviewStatToReview');
49
+ const validatedEl = document.getElementById('reviewStatValidated');
50
+ if (totalEl) totalEl.textContent = comments.length;
51
+ if (toReviewEl) toReviewEl.textContent = toReviewCount;
52
+ if (validatedEl) validatedEl.textContent = comments.filter(c => c.status === 'validated').length;
53
+
54
+ // Filter comments
55
+ let filtered = comments;
56
+ if (reviewFilter === 'to-review') {
57
+ filtered = comments.filter(c => c.status === 'to-review');
58
+ } else if (reviewFilter === 'validated') {
59
+ filtered = comments.filter(c => c.status === 'validated');
60
+ }
61
+
62
+ // Render comment list
63
+ const container = document.getElementById('reviewCommentsList');
64
+ if (!container) return;
65
+
66
+ if (filtered.length === 0) {
67
+ container.innerHTML = '<div class="review-empty">Aucun commentaire' +
68
+ (reviewFilter !== 'all' ? ' avec ce filtre' : '') + '.</div>';
69
+ return;
70
+ }
71
+
72
+ container.innerHTML = filtered.map((c, i) => {
73
+ const globalIndex = data.comments.indexOf(c);
74
+ const sectionLabel = getSectionLabel(c.sectionId);
75
+ const date = c.timestamp ? new Date(c.timestamp).toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit' }) : '';
76
+
77
+ return `
78
+ <div class="review-comment-item" onclick="navigateToComment('${c.sectionId}', ${c.cardIndex})">
79
+ <div class="review-comment-section">${sectionLabel}</div>
80
+ <div class="review-comment-text">${c.content}</div>
81
+ <div class="review-comment-footer">
82
+ <span class="review-comment-author">${c.author || 'Utilisateur'} - ${date}</span>
83
+ <div class="review-comment-actions">
84
+ <button class="review-action-btn ${c.status === 'validated' ? 'reject' : 'validate'}"
85
+ onclick="event.stopPropagation();toggleCommentStatus(${globalIndex})"
86
+ title="${c.status === 'validated' ? 'Remettre a revoir' : 'Valider'}">
87
+ ${c.status === 'validated' ? 'A revoir' : 'Valider'}
88
+ </button>
89
+ <button class="review-action-btn delete"
90
+ onclick="event.stopPropagation();deleteComment(${globalIndex})"
91
+ title="Supprimer">
92
+ &#10005;
93
+ </button>
94
+ </div>
95
+ </div>
96
+ </div>
97
+ `;
98
+ }).join('');
99
+ }
100
+
101
+ function getSectionLabel(sectionId) {
102
+ const labels = {
103
+ 'cadrage-problem': 'Probleme',
104
+ 'cadrage-current': 'Situation actuelle',
105
+ 'cadrage-vision': 'Vision',
106
+ 'cadrage-stakeholders': 'Parties prenantes',
107
+ 'cadrage-scope': 'Perimetre',
108
+ 'cadrage-risks': 'Risques',
109
+ 'cadrage-success': 'Criteres',
110
+ 'decomp-modules': 'Domaines',
111
+ 'decomp-dependencies': 'Dependances',
112
+ 'consol-interactions': 'Interactions',
113
+ 'consol-permissions': 'Acces',
114
+ 'consol-flows': 'Parcours',
115
+ 'handoff-summary': 'Synthese'
116
+ };
117
+ if (labels[sectionId]) return labels[sectionId];
118
+ if (sectionId.startsWith('module-spec-')) {
119
+ const code = sectionId.replace('module-spec-', '');
120
+ const mod = data.modules.find(m => m.code === code);
121
+ return mod ? mod.name : code;
122
+ }
123
+ return sectionId;
124
+ }
125
+
126
+ function navigateToComment(sectionId, cardIndex) {
127
+ showSection(sectionId);
128
+ // Scroll to the card and open its comment thread
129
+ setTimeout(() => {
130
+ const thread = document.getElementById('comment-thread-' + sectionId + '-' + cardIndex);
131
+ if (thread && !thread.classList.contains('visible')) {
132
+ thread.classList.add('visible');
133
+ }
134
+ const container = document.querySelector(`.comment-btn-container[data-section-id="${sectionId}"][data-card-index="${cardIndex}"]`);
135
+ if (container) {
136
+ container.scrollIntoView({ behavior: 'smooth', block: 'center' });
137
+ }
138
+ }, 100);
139
+ }
@@ -0,0 +1,38 @@
1
+ /* ============================================
2
+ DESIGN SYSTEM - SmartStack Business Analysis
3
+ ============================================ */
4
+ :root {
5
+ --primary: #6366f1;
6
+ --primary-dark: #4f46e5;
7
+ --primary-light: #818cf8;
8
+ --secondary: #f97316;
9
+ --accent: #06b6d4;
10
+ --bg-dark: #0f172a;
11
+ --bg-card: #1e293b;
12
+ --bg-hover: #334155;
13
+ --bg-input: #151d2e;
14
+ --text: #b8c4d1;
15
+ --text-muted: #8a9bb0;
16
+ --text-bright: #e2e8f0;
17
+ --border: #334155;
18
+ --border-light: #475569;
19
+ --success: #22c55e;
20
+ --warning: #eab308;
21
+ --error: #ef4444;
22
+ --info: #3b82f6;
23
+ --sidebar-width: 280px;
24
+ --header-height: 52px;
25
+ --transition-fast: 0.15s ease;
26
+ --transition-normal: 0.3s ease;
27
+ }
28
+
29
+ * { margin: 0; padding: 0; box-sizing: border-box; }
30
+ html { scroll-behavior: smooth; }
31
+
32
+ body {
33
+ font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
34
+ background: var(--bg-dark);
35
+ color: var(--text);
36
+ line-height: 1.7;
37
+ min-height: 100vh;
38
+ }
@@ -0,0 +1,101 @@
1
+ /* ============================================
2
+ LAYOUT
3
+ ============================================ */
4
+ .app { display: flex; flex-direction: column; min-height: 100vh; }
5
+
6
+ .header {
7
+ background: var(--bg-card);
8
+ border-bottom: 1px solid var(--border);
9
+ height: var(--header-height);
10
+ display: flex;
11
+ align-items: center;
12
+ padding: 0 1.5rem;
13
+ gap: 1rem;
14
+ position: sticky;
15
+ top: 0;
16
+ z-index: 100;
17
+ }
18
+
19
+ .header-logo {
20
+ width: 32px; height: 32px;
21
+ background: linear-gradient(135deg, var(--primary), var(--secondary));
22
+ border-radius: 6px;
23
+ display: flex; align-items: center; justify-content: center;
24
+ font-weight: 700; font-size: 0.85rem; color: #fff;
25
+ flex-shrink: 0;
26
+ }
27
+
28
+ .header-title { font-size: 1rem; font-weight: 600; color: var(--text-bright); }
29
+ .header-sep { width: 1px; height: 24px; background: var(--border); }
30
+ .header-app-name { font-size: 0.9rem; color: var(--primary-light); font-weight: 500; }
31
+ .header-spacer { flex: 1; }
32
+
33
+ .header-actions { display: flex; gap: 0.5rem; }
34
+
35
+ .body { display: flex; flex: 1; }
36
+
37
+ /* ============================================
38
+ SIDEBAR - Container
39
+ ============================================ */
40
+ .sidebar {
41
+ width: var(--sidebar-width);
42
+ background: var(--bg-card);
43
+ border-right: 1px solid var(--border);
44
+ overflow-y: auto;
45
+ height: calc(100vh - var(--header-height));
46
+ position: sticky;
47
+ top: var(--header-height);
48
+ flex-shrink: 0;
49
+ }
50
+
51
+ .sidebar::-webkit-scrollbar { width: 4px; }
52
+ .sidebar::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
53
+
54
+ /* ============================================
55
+ MAIN CONTENT - FULL WIDTH
56
+ ============================================ */
57
+ .main {
58
+ flex: 1;
59
+ padding: 2rem 3rem;
60
+ overflow-y: auto;
61
+ height: calc(100vh - var(--header-height));
62
+ }
63
+
64
+ .section { margin-bottom: 4rem; }
65
+ .section-title {
66
+ font-size: 1.4rem;
67
+ color: var(--text-bright);
68
+ font-weight: 600;
69
+ margin-bottom: 0.5rem;
70
+ padding-bottom: 0.5rem;
71
+ border-bottom: 2px solid var(--primary);
72
+ }
73
+ .section-subtitle {
74
+ font-size: 0.9rem;
75
+ color: var(--text-muted);
76
+ margin-bottom: 1.5rem;
77
+ }
78
+
79
+ /* ============================================
80
+ RESPONSIVE
81
+ ============================================ */
82
+ @media (max-width: 768px) {
83
+ .sidebar { display: none; }
84
+ .main { padding: 1rem; }
85
+ .form-row { grid-template-columns: 1fr; }
86
+ .stakeholder-grid { grid-template-columns: 1fr; }
87
+ }
88
+
89
+ /* ============================================
90
+ PRINT
91
+ ============================================ */
92
+ @media print {
93
+ .sidebar, .header-actions, .add-btn, .uc-actions, .inline-form, .module-card-remove, .phase-progress { display: none !important; }
94
+ .main { max-width: 100%; padding: 0; }
95
+ .section { display: block !important; page-break-inside: avoid; }
96
+ body { background: #fff; color: #1a1a1a; }
97
+ .card, .uc-item, .br-item, .module-card, .entity-block, .interaction-item { border-color: #ddd; }
98
+ .tab-panel { display: block !important; }
99
+ .tab-bar { display: none; }
100
+ .review-panel, .comment-btn-container { display: none !important; }
101
+ }