@creative-ia/cortex 1.0.5
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.
- package/README.md +41 -0
- package/dist/config/cloud-proxy.d.ts +15 -0
- package/dist/config/cloud-proxy.js +63 -0
- package/dist/config/cloudwatch-store.d.ts +13 -0
- package/dist/config/cloudwatch-store.js +66 -0
- package/dist/config/license.d.ts +29 -0
- package/dist/config/license.js +165 -0
- package/dist/config/ssm-store.d.ts +2 -0
- package/dist/config/ssm-store.js +38 -0
- package/dist/config/telemetry.d.ts +17 -0
- package/dist/config/telemetry.js +93 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +460 -0
- package/dist/knowledge/dynamo-store.d.ts +17 -0
- package/dist/knowledge/dynamo-store.js +85 -0
- package/dist/knowledge/embeddings.d.ts +2 -0
- package/dist/knowledge/embeddings.js +36 -0
- package/dist/knowledge/loader.d.ts +8 -0
- package/dist/knowledge/loader.js +57 -0
- package/dist/lambda-package.zip +0 -0
- package/dist/lambda.d.ts +22 -0
- package/dist/lambda.js +496 -0
- package/dist/package.json +1 -0
- package/dist/tools/advance-process.d.ts +7 -0
- package/dist/tools/advance-process.js +128 -0
- package/dist/tools/analyze-code.d.ts +7 -0
- package/dist/tools/analyze-code.js +131 -0
- package/dist/tools/analyze-docs.d.ts +8 -0
- package/dist/tools/analyze-docs.js +147 -0
- package/dist/tools/config-registry.d.ts +3 -0
- package/dist/tools/config-registry.js +20 -0
- package/dist/tools/create-process.d.ts +6 -0
- package/dist/tools/create-process.js +257 -0
- package/dist/tools/decompose-epic.d.ts +7 -0
- package/dist/tools/decompose-epic.js +603 -0
- package/dist/tools/diagrams.d.ts +51 -0
- package/dist/tools/diagrams.js +304 -0
- package/dist/tools/generate-report.d.ts +9 -0
- package/dist/tools/generate-report.js +891 -0
- package/dist/tools/generate-wiki.d.ts +10 -0
- package/dist/tools/generate-wiki.js +700 -0
- package/dist/tools/get-architecture.d.ts +6 -0
- package/dist/tools/get-architecture.js +78 -0
- package/dist/tools/get-code-standards.d.ts +7 -0
- package/dist/tools/get-code-standards.js +52 -0
- package/dist/tools/init-process.d.ts +7 -0
- package/dist/tools/init-process.js +82 -0
- package/dist/tools/knowledge-crud.d.ts +26 -0
- package/dist/tools/knowledge-crud.js +142 -0
- package/dist/tools/logo-base64.d.ts +1 -0
- package/dist/tools/logo-base64.js +1 -0
- package/dist/tools/logs-query.d.ts +15 -0
- package/dist/tools/logs-query.js +46 -0
- package/dist/tools/reverse-engineer.d.ts +13 -0
- package/dist/tools/reverse-engineer.js +956 -0
- package/dist/tools/semantic-search.d.ts +7 -0
- package/dist/tools/semantic-search.js +68 -0
- package/dist/tools/update-process.d.ts +17 -0
- package/dist/tools/update-process.js +195 -0
- package/dist/tools/validate-idea.d.ts +7 -0
- package/dist/tools/validate-idea.js +339 -0
- package/dist/tools/validate-process.d.ts +6 -0
- package/dist/tools/validate-process.js +102 -0
- package/package.json +31 -0
|
@@ -0,0 +1,891 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool: generate_report
|
|
3
|
+
* Gera relatório HTML com toggle light/dark theme, navegação estruturada,
|
|
4
|
+
* suporte a Mermaid diagrams e layout responsivo.
|
|
5
|
+
* O template e CSS são IP protegido — nunca expostos como source.
|
|
6
|
+
*/
|
|
7
|
+
import { writeFile, mkdir } from "node:fs/promises";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { loadTemplate } from "../knowledge/loader.js";
|
|
11
|
+
import { renderWorkflowDiagram, renderArchitectureDiagram, renderDomainMap, renderRiskMatrix, renderIntegrationFlow, renderBenchmarkMercado, } from "./diagrams.js";
|
|
12
|
+
import { LOGO_BASE64 } from "./logo-base64.js";
|
|
13
|
+
const SECTION_ICONS = {
|
|
14
|
+
"Dominios": "domain", "Squads": "groups", "Arquitetura": "architecture",
|
|
15
|
+
"Integracoes": "cable", "Processos": "history", "Decisoes": "gavel",
|
|
16
|
+
"Qualidade": "verified", "Riscos": "warning", "Recomendacao": "rocket_launch",
|
|
17
|
+
"Ideia": "lightbulb", "Analysis": "analytics", "Passed": "check_circle",
|
|
18
|
+
"Issues": "error", "Result": "task_alt", "Layer": "layers",
|
|
19
|
+
"Folder": "folder_open", "Error": "bug_report", "API": "api",
|
|
20
|
+
"Dependency": "sync_alt", "Architecture": "architecture",
|
|
21
|
+
"L1": "code", "L2": "science", "L3": "account_tree", "L4": "shield",
|
|
22
|
+
"New Process": "add_circle", "Required": "checklist", "Workflow": "route",
|
|
23
|
+
"Golden": "star", "Benchmark": "leaderboard", "Estimativa": "calculate",
|
|
24
|
+
"Infra": "cloud", "Enviar": "send", "Receber": "call_received",
|
|
25
|
+
"Chave": "key", "QR": "qr_code_2", "Agendad": "schedule_send",
|
|
26
|
+
"Extrato": "receipt_long", "Observ": "monitoring", "Monitoramento": "monitoring",
|
|
27
|
+
"Onda": "waves", "Resumo": "table_chart", "Sequencia": "route",
|
|
28
|
+
"Diagrama": "schema", "Epico": "inventory_2",
|
|
29
|
+
"default": "article",
|
|
30
|
+
};
|
|
31
|
+
function getIcon(title) {
|
|
32
|
+
for (const [key, icon] of Object.entries(SECTION_ICONS)) {
|
|
33
|
+
if (title.includes(key))
|
|
34
|
+
return icon;
|
|
35
|
+
}
|
|
36
|
+
return SECTION_ICONS.default;
|
|
37
|
+
}
|
|
38
|
+
function parseSections(content) {
|
|
39
|
+
const sections = [];
|
|
40
|
+
let current = null;
|
|
41
|
+
for (const line of content.split("\n")) {
|
|
42
|
+
if (line.startsWith("## ")) {
|
|
43
|
+
if (current)
|
|
44
|
+
sections.push(current);
|
|
45
|
+
current = { title: line.replace("## ", "").replace(/^\d+\.\s*/, ""), lines: [] };
|
|
46
|
+
}
|
|
47
|
+
else if (!line.startsWith("# ") && current) {
|
|
48
|
+
current.lines.push(line);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (current)
|
|
52
|
+
sections.push(current);
|
|
53
|
+
return sections;
|
|
54
|
+
}
|
|
55
|
+
function fmt(text) {
|
|
56
|
+
return text
|
|
57
|
+
.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
|
|
58
|
+
.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
59
|
+
}
|
|
60
|
+
function renderSection(s, idx, allSections) {
|
|
61
|
+
const icon = getIcon(s.title);
|
|
62
|
+
const id = `s${idx}`;
|
|
63
|
+
const body = s.lines
|
|
64
|
+
.filter((l) => l.trim())
|
|
65
|
+
.map((l) => {
|
|
66
|
+
if (l.startsWith("- **"))
|
|
67
|
+
return `<li>${fmt(l.slice(2))}</li>`;
|
|
68
|
+
if (l.startsWith("- "))
|
|
69
|
+
return `<li>${fmt(l.slice(2))}</li>`;
|
|
70
|
+
if (l.startsWith("> "))
|
|
71
|
+
return `<p class="lead" style="font-style:italic;border-left:3px solid var(--accent);padding-left:14px;">${l.slice(2)}</p>`;
|
|
72
|
+
if (l.startsWith("```"))
|
|
73
|
+
return "";
|
|
74
|
+
if (l.startsWith("### "))
|
|
75
|
+
return `<h3>${l.replace(/^###\s*/, "")}</h3>`;
|
|
76
|
+
return `<p>${fmt(l)}</p>`;
|
|
77
|
+
})
|
|
78
|
+
.join("\n");
|
|
79
|
+
const hasList = body.includes("<li>");
|
|
80
|
+
// Auto-inject diagrams based on section title
|
|
81
|
+
let diagram = "";
|
|
82
|
+
const titleLower = s.title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
83
|
+
if (titleLower.includes("dominios") || titleLower.includes("domains")) {
|
|
84
|
+
const domains = s.lines
|
|
85
|
+
.filter((l) => l.startsWith("- **"))
|
|
86
|
+
.map((l) => {
|
|
87
|
+
const match = l.match(/\*\*([^*]+)\*\*\s*\((\w+)\)\s*[—-]\s*(.+)/);
|
|
88
|
+
if (match)
|
|
89
|
+
return { name: match[1], type: match[2], squad: match[3].trim() };
|
|
90
|
+
return null;
|
|
91
|
+
})
|
|
92
|
+
.filter(Boolean);
|
|
93
|
+
if (domains.length > 0)
|
|
94
|
+
diagram = renderDomainMap(domains);
|
|
95
|
+
}
|
|
96
|
+
if (titleLower.includes("arquitetura") || titleLower.includes("architecture")) {
|
|
97
|
+
diagram = renderArchitectureDiagram();
|
|
98
|
+
}
|
|
99
|
+
if (titleLower.includes("riscos") || titleLower.includes("risks")) {
|
|
100
|
+
const risks = s.lines
|
|
101
|
+
.filter((l) => l.startsWith("- **"))
|
|
102
|
+
.map((l) => {
|
|
103
|
+
const match = l.match(/\*\*([^*]+)\*\*/);
|
|
104
|
+
const name = match ? match[1] : l.slice(4, 40);
|
|
105
|
+
const lower = l.toLowerCase();
|
|
106
|
+
const isRegulatorio = lower.includes("regulat");
|
|
107
|
+
const isSeguranca = lower.includes("seguranca") || lower.includes("lgpd");
|
|
108
|
+
return {
|
|
109
|
+
name,
|
|
110
|
+
probability: (isRegulatorio ? "alta" : "media"),
|
|
111
|
+
impact: (isSeguranca || isRegulatorio ? "alto" : "medio"),
|
|
112
|
+
category: isRegulatorio ? "Regulatorio" : isSeguranca ? "Seguranca" : "Tecnico",
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
if (risks.length > 0)
|
|
116
|
+
diagram = renderRiskMatrix(risks);
|
|
117
|
+
}
|
|
118
|
+
if (titleLower.includes("integracoes") || titleLower.includes("integrations")) {
|
|
119
|
+
const nodes = [];
|
|
120
|
+
const links = [];
|
|
121
|
+
nodes.push({ name: "BFF", type: "gateway", protocol: "GraphQL" });
|
|
122
|
+
for (const l of s.lines) {
|
|
123
|
+
if (l.startsWith("- **")) {
|
|
124
|
+
const match = l.match(/\*\*([^*]+)\*\*\s*\(([^)]+)\)/);
|
|
125
|
+
if (match) {
|
|
126
|
+
const isExternal = match[2].includes("nova") || match[2].includes("external");
|
|
127
|
+
nodes.push({ name: match[1], type: isExternal ? "externo" : "interno" });
|
|
128
|
+
links.push({ from: "BFF", to: match[1], protocol: match[2] });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (nodes.length > 1)
|
|
133
|
+
diagram = renderIntegrationFlow(nodes, links);
|
|
134
|
+
}
|
|
135
|
+
if (titleLower.includes("recomendacao") || titleLower.includes("workflow") || titleLower.includes("proximo")) {
|
|
136
|
+
diagram = renderWorkflowDiagram([
|
|
137
|
+
{ name: "Stakeholder", status: "concluido", subtitle: "01" },
|
|
138
|
+
{ name: "Intake", status: "ativo", subtitle: "02" },
|
|
139
|
+
{ name: "Epicos", status: "pendente", subtitle: "03" },
|
|
140
|
+
{ name: "Estrategia", status: "pendente", subtitle: "04/05" },
|
|
141
|
+
{ name: "Execucao", status: "pendente", subtitle: "06+" },
|
|
142
|
+
]);
|
|
143
|
+
}
|
|
144
|
+
if (titleLower.includes("benchmark")) {
|
|
145
|
+
const concorrentes = [];
|
|
146
|
+
const dimensoes = [];
|
|
147
|
+
const tableLines = s.lines.filter((l) => l.includes("|") && !l.match(/^\s*\|?\s*[-:]+/));
|
|
148
|
+
if (tableLines.length >= 2) {
|
|
149
|
+
const headers = tableLines[0].split("|").map((h) => h.trim()).filter(Boolean);
|
|
150
|
+
const nomes = headers.slice(1);
|
|
151
|
+
const tipoLine = s.lines.find((l) => l.toLowerCase().includes("tipo:") || l.toLowerCase().includes("| tipo"));
|
|
152
|
+
const tipos = [];
|
|
153
|
+
if (tipoLine) {
|
|
154
|
+
const parts = tipoLine.split("|").map((p) => p.trim()).filter(Boolean);
|
|
155
|
+
tipos.push(...parts.slice(1));
|
|
156
|
+
}
|
|
157
|
+
for (let ni = 0; ni < nomes.length; ni++) {
|
|
158
|
+
const tipoStr = (tipos[ni] || "banco").toLowerCase();
|
|
159
|
+
const tipo = (tipoStr.includes("nosso") ? "nosso" : tipoStr.includes("fintech") ? "fintech" : tipoStr.includes("financeira") ? "financeira" : tipoStr.includes("cooperativa") ? "cooperativa" : "banco");
|
|
160
|
+
concorrentes.push({ nome: nomes[ni], tipo, indicadores: {} });
|
|
161
|
+
}
|
|
162
|
+
for (const line of tableLines.slice(1)) {
|
|
163
|
+
if (line.toLowerCase().includes("tipo"))
|
|
164
|
+
continue;
|
|
165
|
+
const cells = line.split("|").map((c) => c.trim()).filter(Boolean);
|
|
166
|
+
if (cells.length < 2)
|
|
167
|
+
continue;
|
|
168
|
+
const dimNome = cells[0];
|
|
169
|
+
const chave = dimNome.toLowerCase().replace(/\s+/g, "_").normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
170
|
+
const maiorMelhor = !dimNome.toLowerCase().includes("taxa") && !dimNome.toLowerCase().includes("prazo") && !dimNome.toLowerCase().includes("tempo");
|
|
171
|
+
dimensoes.push({ nome: dimNome, chave, maiorMelhor });
|
|
172
|
+
for (let ci = 1; ci < cells.length && ci - 1 < concorrentes.length; ci++) {
|
|
173
|
+
const val = cells[ci];
|
|
174
|
+
const numVal = parseFloat(val.replace(/[^\d.,]/g, "").replace(",", "."));
|
|
175
|
+
concorrentes[ci - 1].indicadores[chave] = isNaN(numVal) ? val : numVal;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (concorrentes.length > 0 && dimensoes.length > 0) {
|
|
180
|
+
const fontes = [];
|
|
181
|
+
let inFontes = false;
|
|
182
|
+
for (const l of s.lines) {
|
|
183
|
+
if (l.includes("**Fontes:**") || l.includes("Fontes:")) {
|
|
184
|
+
inFontes = true;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (inFontes && l.startsWith("- ")) {
|
|
188
|
+
fontes.push(l.slice(2).replace(/\*\*/g, ""));
|
|
189
|
+
}
|
|
190
|
+
else if (inFontes && !l.trim()) {
|
|
191
|
+
inFontes = false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
diagram = renderBenchmarkMercado(concorrentes, dimensoes, undefined, fontes.length > 0 ? fontes : undefined);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return `<section class="card" id="${id}">
|
|
198
|
+
<h2><span class="material-symbols-outlined">${icon}</span> ${s.title}</h2>
|
|
199
|
+
${hasList ? `<ul>${body}</ul>` : body}
|
|
200
|
+
${diagram}
|
|
201
|
+
</section>`;
|
|
202
|
+
}
|
|
203
|
+
function renderToc(sections) {
|
|
204
|
+
return sections.map((s, i) => `<a href="#s${i}"><span class="material-symbols-outlined" style="font-size:.9rem;vertical-align:middle;margin-right:4px;">${getIcon(s.title)}</span>${s.title}</a>`).join("\n");
|
|
205
|
+
}
|
|
206
|
+
// ============================================================
|
|
207
|
+
// CSS DUAL THEME — Dark (default) + Light toggle
|
|
208
|
+
// ============================================================
|
|
209
|
+
function getDualThemeCSS() {
|
|
210
|
+
return `
|
|
211
|
+
/* === DARK THEME (default) === */
|
|
212
|
+
:root {
|
|
213
|
+
--bg: #0A0F1A; --paper: #111827; --ink: #E5E7EB; --muted: #9AA3AD;
|
|
214
|
+
--brand: #2F6DB3; --brand-2: #4FB3FF; --accent: #00E0FF; --line: #1E293B;
|
|
215
|
+
--soft: #0F1C2E; --shadow: 0 14px 40px rgba(0,224,255,0.06);
|
|
216
|
+
--silver: #C9CCD1; --warn: #F59E0B; --warn-bg: #1C1708;
|
|
217
|
+
--danger: #EF4444; --danger-bg: #1C0808; --ok: #22C55E; --ok-bg: #081C10;
|
|
218
|
+
--cover-from: #0F1C2E; --cover-mid: #1B3B6F; --cover-to: #2F6DB3;
|
|
219
|
+
--toc-bg: rgba(17,24,39,.92); --mini-from: #111827; --mini-to: #0F1C2E;
|
|
220
|
+
--body-bg: radial-gradient(circle at top right,rgba(0,224,255,.06),transparent 40%),radial-gradient(circle at bottom left,rgba(47,109,179,.08),transparent 50%),var(--bg);
|
|
221
|
+
}
|
|
222
|
+
/* === LIGHT THEME === */
|
|
223
|
+
[data-theme="light"] {
|
|
224
|
+
--bg: #F8FAFC; --paper: #FFFFFF; --ink: #1E293B; --muted: #475569;
|
|
225
|
+
--brand: #1D4ED8; --brand-2: #2563EB; --accent: #0369A1; --line: #CBD5E1;
|
|
226
|
+
--soft: #F1F5F9; --shadow: 0 4px 20px rgba(0,0,0,0.08);
|
|
227
|
+
--silver: #334155; --warn: #B45309; --warn-bg: #FFFBEB;
|
|
228
|
+
--danger: #DC2626; --danger-bg: #FEF2F2; --ok: #15803D; --ok-bg: #F0FDF4;
|
|
229
|
+
--cover-from: #1E40AF; --cover-mid: #2563EB; --cover-to: #1D4ED8;
|
|
230
|
+
--toc-bg: rgba(255,255,255,.97); --mini-from: #FFFFFF; --mini-to: #F8FAFC;
|
|
231
|
+
--body-bg: linear-gradient(180deg,#F8FAFC,#EFF6FF);
|
|
232
|
+
}
|
|
233
|
+
* { box-sizing: border-box; }
|
|
234
|
+
html { scroll-behavior: smooth; }
|
|
235
|
+
body { margin:0; font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif; letter-spacing:.01em; background:var(--body-bg); color:var(--ink); line-height:1.7; transition:background .3s,color .3s; }
|
|
236
|
+
.page { width:92%; max-width:1600px; margin:0 auto; padding:clamp(16px,3vw,40px) clamp(16px,4vw,48px) 80px; }
|
|
237
|
+
.cover { background:linear-gradient(135deg,var(--cover-from) 0%,var(--cover-mid) 50%,var(--cover-to) 100%); color:#fff; border-radius:clamp(16px,2vw,28px); padding:clamp(24px,3vw,40px); box-shadow:0 14px 40px rgba(0,224,255,.08); position:relative; overflow:hidden; border:1px solid rgba(0,224,255,.1); }
|
|
238
|
+
.cover::after { content:""; position:absolute; width:clamp(160px,22vw,300px); height:clamp(160px,22vw,300px); right:-70px; top:-80px; border-radius:50%; background:radial-gradient(circle,rgba(0,224,255,.12),transparent 70%); }
|
|
239
|
+
.brand { display:flex; align-items:center; gap:clamp(12px,2vw,22px); position:relative; z-index:1; }
|
|
240
|
+
.brand-logo { width:64px; height:64px; border-radius:14px; background:rgba(255,255,255,0.08); padding:4px; flex-shrink:0; }
|
|
241
|
+
[data-theme="light"] .brand-logo { background:rgba(0,0,0,0.06); }
|
|
242
|
+
.eyebrow { letter-spacing:.14em; text-transform:uppercase; font-size:clamp(.68rem,1vw,.82rem); opacity:.88; margin-bottom:8px; }
|
|
243
|
+
h1 { margin:0 0 10px; font-size:clamp(1.4rem,3.5vw,2.4rem); line-height:1.08; color:#fff; }
|
|
244
|
+
.subtitle { max-width:860px; font-size:clamp(.92rem,1.4vw,1.08rem); color:#fff; margin:0; }
|
|
245
|
+
.cover-meta { margin-top:22px; display:flex; flex-wrap:wrap; gap:8px; position:relative; z-index:1; }
|
|
246
|
+
.pill { border:1px solid rgba(0,224,255,.2); background:rgba(0,224,255,.08); color:var(--silver); padding:5px 12px; border-radius:999px; font-size:clamp(.78rem,1vw,.9rem); letter-spacing:.04em; text-transform:uppercase; }
|
|
247
|
+
.cover .pill { color:#C9CCD1; }
|
|
248
|
+
[data-theme="light"] .pill { border-color:rgba(29,78,216,.2); background:rgba(29,78,216,.08); }
|
|
249
|
+
[data-theme="light"] .cover .pill { color:rgba(255,255,255,.85); border-color:rgba(255,255,255,.25); background:rgba(255,255,255,.12); }
|
|
250
|
+
.layout { display:grid; grid-template-columns:clamp(220px,22%,300px) minmax(0,1fr); gap:clamp(14px,2vw,26px); margin-top:clamp(16px,2vw,28px); }
|
|
251
|
+
.toc { position:sticky; top:18px; align-self:start; background:var(--toc-bg); backdrop-filter:blur(14px); border:1px solid var(--line); border-radius:22px; box-shadow:var(--shadow); padding:clamp(14px,2vw,24px); transition:background .3s; }
|
|
252
|
+
.toc h2 { margin:0 0 14px; font-size:clamp(.88rem,1.1vw,1rem); color:var(--brand); }
|
|
253
|
+
.toc a { display:block; color:var(--silver); text-decoration:none; padding:7px 10px; border-radius:10px; font-size:clamp(.82rem,1vw,.95rem); margin-bottom:3px; transition:background .2s,color .2s; }
|
|
254
|
+
.toc a:hover { background:rgba(0,224,255,.06); color:var(--accent); }
|
|
255
|
+
.toc .toc-section { font-size:.72rem; text-transform:uppercase; letter-spacing:.1em; color:var(--muted); margin:12px 0 4px 10px; }
|
|
256
|
+
.content { display:grid; gap:clamp(12px,1.5vw,20px); }
|
|
257
|
+
section.card { background:var(--paper); border:1px solid var(--line); border-radius:clamp(16px,2vw,24px); box-shadow:var(--shadow); padding:clamp(18px,2.5vw,32px); transition:background .3s,border-color .3s; }
|
|
258
|
+
section.card h2 { margin:0 0 14px; font-size:clamp(1.15rem,2vw,1.5rem); color:var(--brand); }
|
|
259
|
+
h3 { margin:20px 0 10px; color:var(--brand-2); font-size:clamp(.95rem,1.3vw,1.08rem); }
|
|
260
|
+
h4 { margin:16px 0 8px; color:var(--ink); font-size:clamp(.88rem,1.1vw,1rem); }
|
|
261
|
+
p { margin:10px 0 14px; color:var(--muted); }
|
|
262
|
+
strong { color:var(--ink); }
|
|
263
|
+
.lead { font-size:clamp(.98rem,1.4vw,1.08rem); }
|
|
264
|
+
.grid { display:grid; gap:16px; }
|
|
265
|
+
.grid.two { grid-template-columns:repeat(auto-fit,minmax(260px,1fr)); }
|
|
266
|
+
.grid.three { grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); }
|
|
267
|
+
.mini-card { border:1px solid var(--line); border-radius:18px; background:linear-gradient(180deg,var(--mini-from),var(--mini-to)); padding:18px; transition:background .3s; }
|
|
268
|
+
.mini-card h4 { margin:0 0 8px; font-size:.98rem; color:var(--brand); }
|
|
269
|
+
table { width:100%; border-collapse:collapse; margin-top:12px; border:1px solid var(--line); border-radius:16px; overflow:hidden; background:var(--paper); transition:background .3s; }
|
|
270
|
+
th,td { padding:11px 14px; text-align:left; vertical-align:top; border-bottom:1px solid var(--line); }
|
|
271
|
+
th { background:var(--soft); color:var(--brand-2); font-weight:700; font-size:.93rem; }
|
|
272
|
+
tr:last-child td { border-bottom:none; }
|
|
273
|
+
.badge { display:inline-block; padding:3px 10px; border-radius:999px; font-size:.8rem; font-weight:600; letter-spacing:.04em; }
|
|
274
|
+
.badge.accepted { background:var(--ok-bg); color:var(--ok); }
|
|
275
|
+
.badge.proposed { background:rgba(47,109,179,.2); color:#4FB3FF; }
|
|
276
|
+
.badge.warn { background:var(--warn-bg); color:var(--warn); }
|
|
277
|
+
.badge.danger { background:var(--danger-bg); color:var(--danger); }
|
|
278
|
+
.dep-tag { display:inline-block; background:rgba(0,224,255,.08); border:1px solid rgba(0,224,255,.15); padding:2px 8px; border-radius:6px; font-size:.75rem; margin-right:4px; color:var(--accent); font-weight:600; }
|
|
279
|
+
[data-theme="light"] .dep-tag { background:rgba(3,105,161,.08); border-color:rgba(3,105,161,.2); color:#0369A1; }
|
|
280
|
+
.squad-tag { display:inline-block; background:rgba(47,109,179,.15); border:1px solid rgba(47,109,179,.25); padding:2px 8px; border-radius:6px; font-size:.75rem; margin-right:4px; color:var(--brand-2); }
|
|
281
|
+
[data-theme="light"] .squad-tag { background:rgba(29,78,216,.08); border-color:rgba(29,78,216,.2); color:#1D4ED8; }
|
|
282
|
+
.size-badge { display:inline-block; padding:3px 10px; border-radius:8px; font-size:.75rem; font-weight:700; color:#fff; }
|
|
283
|
+
.size-L { background:#EF4444; }
|
|
284
|
+
.size-M { background:#2F6DB3; }
|
|
285
|
+
.callout { border:1px solid rgba(0,224,255,.15); background:linear-gradient(180deg,var(--mini-from),var(--mini-to)); border-radius:18px; padding:16px 18px; color:var(--ink); margin:14px 0; transition:background .3s; }
|
|
286
|
+
.scope-list { list-style:none; padding:0; margin:8px 0; }
|
|
287
|
+
.scope-list li { padding:5px 0 5px 20px; position:relative; font-size:.9rem; color:var(--muted); }
|
|
288
|
+
.scope-list li::before { content:"▸"; position:absolute; left:0; color:var(--accent); font-weight:bold; }
|
|
289
|
+
.criteria-list { list-style:none; padding:0; margin:8px 0; }
|
|
290
|
+
.criteria-list li { padding:5px 0 5px 24px; position:relative; font-size:.9rem; color:var(--muted); }
|
|
291
|
+
.criteria-list li::before { content:"☐"; position:absolute; left:0; color:var(--brand-2); }
|
|
292
|
+
.epic-card { background:linear-gradient(180deg,var(--mini-from),var(--mini-to)); border:1px solid var(--line); border-radius:18px; padding:clamp(16px,2vw,24px); margin-bottom:16px; transition:background .3s,border-color .3s; }
|
|
293
|
+
.epic-card:hover { border-color:rgba(0,224,255,.2); }
|
|
294
|
+
.epic-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:12px; flex-wrap:wrap; gap:8px; }
|
|
295
|
+
.epic-header h3 { margin:0; font-size:clamp(1rem,1.5vw,1.15rem); color:var(--ink); }
|
|
296
|
+
.wave { border-left:4px solid var(--brand); padding:16px 20px; background:linear-gradient(180deg,var(--mini-from),var(--mini-to)); border-radius:0 18px 18px 0; margin-bottom:12px; transition:background .3s; }
|
|
297
|
+
.wave h4 { color:var(--ink); margin:0 0 6px; font-size:1rem; }
|
|
298
|
+
.wave p { margin:4px 0; }
|
|
299
|
+
.diagram-box { background:linear-gradient(180deg,var(--mini-from),var(--mini-to)); border:1px solid var(--line); border-radius:18px; padding:clamp(16px,2vw,24px); margin-bottom:16px; text-align:center; overflow-x:auto; transition:background .3s; }
|
|
300
|
+
.diagram-box h3 { text-align:center; margin:0 0 16px; color:var(--ink); font-size:1.05rem; }
|
|
301
|
+
.diagram-box img { max-width:100%; height:auto; border-radius:12px; border:1px solid var(--line); }
|
|
302
|
+
.diagram-box .mermaid { text-align:center; }
|
|
303
|
+
ul { margin:10px 0 14px 20px; color:var(--muted); }
|
|
304
|
+
li { margin:6px 0; }
|
|
305
|
+
code { background:rgba(0,224,255,.06); padding:2px 6px; border-radius:6px; font-size:.88rem; color:var(--accent); }
|
|
306
|
+
[data-theme="light"] code { background:rgba(3,105,161,.08); color:#0369A1; }
|
|
307
|
+
a { color:var(--brand-2); text-decoration:none; }
|
|
308
|
+
a:hover { text-decoration:underline; color:var(--accent); }
|
|
309
|
+
.back-btn { position:absolute; top:20px; right:24px; z-index:2; display:inline-flex; align-items:center; gap:7px; padding:8px 16px; background:rgba(0,224,255,.08); border:1px solid rgba(0,224,255,.2); color:var(--accent); border-radius:999px; font-size:.85rem; text-decoration:none; font-weight:500; }
|
|
310
|
+
.back-btn:hover { background:rgba(0,224,255,.18); text-decoration:none; }
|
|
311
|
+
[data-theme="light"] .back-btn { background:rgba(255,255,255,.2); border-color:rgba(255,255,255,.3); color:#fff; }
|
|
312
|
+
[data-theme="light"] .back-btn:hover { background:rgba(255,255,255,.3); }
|
|
313
|
+
/* Theme toggle button */
|
|
314
|
+
.theme-toggle { position:fixed; top:18px; right:68px; z-index:999; width:42px; height:42px; border:none; border-radius:10px; background:rgba(0,224,255,.1); color:var(--accent); cursor:pointer; display:flex; align-items:center; justify-content:center; box-shadow:0 4px 14px rgba(0,224,255,.12); border:1px solid rgba(0,224,255,.2); transition:background .2s; }
|
|
315
|
+
.theme-toggle:hover { background:rgba(0,224,255,.2); }
|
|
316
|
+
[data-theme="light"] .theme-toggle { background:rgba(29,78,216,.08); color:#1D4ED8; box-shadow:0 4px 14px rgba(0,0,0,.08); border-color:rgba(29,78,216,.2); }
|
|
317
|
+
[data-theme="light"] .theme-toggle:hover { background:rgba(29,78,216,.15); }
|
|
318
|
+
.theme-toggle .icon-dark, .theme-toggle .icon-light { font-size:20px; }
|
|
319
|
+
[data-theme="light"] .theme-toggle .icon-dark { display:none; }
|
|
320
|
+
[data-theme="dark"] .theme-toggle .icon-light, :root:not([data-theme]) .theme-toggle .icon-light { display:none; }
|
|
321
|
+
.print-btn { position:fixed; top:18px; right:18px; z-index:999; width:42px; height:42px; border:none; border-radius:10px; background:rgba(0,224,255,.1); color:var(--accent); cursor:pointer; display:flex; align-items:center; justify-content:center; box-shadow:0 4px 14px rgba(0,224,255,.12); border:1px solid rgba(0,224,255,.2); }
|
|
322
|
+
.print-btn:hover { background:rgba(0,224,255,.2); }
|
|
323
|
+
[data-theme="light"] .print-btn { background:rgba(29,78,216,.08); color:#1D4ED8; box-shadow:0 4px 14px rgba(0,0,0,.08); border-color:rgba(29,78,216,.2); }
|
|
324
|
+
[data-theme="light"] .print-btn:hover { background:rgba(29,78,216,.15); }
|
|
325
|
+
.print-btn svg { width:20px; height:20px; fill:currentColor; }
|
|
326
|
+
.footer { text-align:center; padding:24px; opacity:.5; font-size:.85rem; }
|
|
327
|
+
/* bd-wrap for diagrams */
|
|
328
|
+
.bd-wrap { background:linear-gradient(180deg,var(--mini-from),var(--mini-to)); border:1px solid var(--line); border-radius:18px; padding:24px; margin:14px 0; overflow-x:auto; transition:background .3s; }
|
|
329
|
+
.bd-node { text-align:center; border-radius:12px; padding:14px; }
|
|
330
|
+
.bd-sub { display:block; font-size:.85rem; color:var(--muted); margin-top:4px; }
|
|
331
|
+
.bd-root { background:linear-gradient(135deg,#1B3B6F,#2F6DB3); color:#fff; border:1px solid var(--accent); }
|
|
332
|
+
.bd-global { background:var(--ok-bg); color:var(--ok); border:1px solid rgba(34,197,94,.3); }
|
|
333
|
+
.bd-local { background:var(--line); color:var(--ink); border:1px solid var(--brand); }
|
|
334
|
+
.bd-active { box-shadow:0 0 12px rgba(0,224,255,.3); }
|
|
335
|
+
/* Dependency flow diagram */
|
|
336
|
+
.dep-flow { padding:8px 0; }
|
|
337
|
+
.dep-flow-wave { margin-bottom:0; }
|
|
338
|
+
.dep-flow-wave-label { font-size:.78rem; text-transform:uppercase; letter-spacing:.12em; color:var(--muted); font-weight:600; margin-bottom:10px; padding-left:4px; }
|
|
339
|
+
.dep-flow-row { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
|
|
340
|
+
.dep-flow-node { background:linear-gradient(135deg,#1B3B6F,#2F6DB3); border:2px solid #4FB3FF; border-radius:14px; padding:14px 16px; text-align:center; position:relative; transition:transform .2s,box-shadow .2s; }
|
|
341
|
+
.dep-flow-node:hover { transform:translateY(-2px); box-shadow:0 6px 20px rgba(0,224,255,.2); }
|
|
342
|
+
.dep-flow-id { display:block; font-size:.82rem; font-weight:700; color:#00E0FF; letter-spacing:.06em; }
|
|
343
|
+
.dep-flow-name { display:block; font-size:.92rem; font-weight:600; color:#fff; margin-top:4px; line-height:1.3; }
|
|
344
|
+
.dep-flow-dep { display:block; font-size:.72rem; color:rgba(255,255,255,.6); margin-top:6px; font-style:italic; }
|
|
345
|
+
.dep-flow-L { border-color:#F87171; background:linear-gradient(135deg,#7F1D1D,#991B1B); }
|
|
346
|
+
.dep-flow-L .dep-flow-id { color:#FCA5A5; }
|
|
347
|
+
.dep-flow-L::after { content:"L"; position:absolute; top:8px; right:10px; font-size:.65rem; font-weight:700; background:#EF4444; color:#fff; padding:1px 6px; border-radius:4px; }
|
|
348
|
+
.dep-flow-M::after { content:"M"; position:absolute; top:8px; right:10px; font-size:.65rem; font-weight:700; background:#2F6DB3; color:#fff; padding:1px 6px; border-radius:4px; }
|
|
349
|
+
.dep-flow-arrows-down { text-align:center; height:40px; opacity:.7; }
|
|
350
|
+
[data-theme="light"] .dep-flow-node { background:linear-gradient(135deg,#DBEAFE,#EFF6FF); border-color:#2563EB; }
|
|
351
|
+
[data-theme="light"] .dep-flow-id { color:#1D4ED8; }
|
|
352
|
+
[data-theme="light"] .dep-flow-name { color:#1E293B; }
|
|
353
|
+
[data-theme="light"] .dep-flow-dep { color:#64748B; }
|
|
354
|
+
[data-theme="light"] .dep-flow-L { background:linear-gradient(135deg,#FEE2E2,#FEF2F2); border-color:#DC2626; }
|
|
355
|
+
[data-theme="light"] .dep-flow-L .dep-flow-id { color:#DC2626; }
|
|
356
|
+
[data-theme="light"] .dep-flow-L::after { background:#DC2626; }
|
|
357
|
+
[data-theme="light"] .dep-flow-M::after { background:#2563EB; }
|
|
358
|
+
[data-theme="light"] .dep-flow-arrows-down svg line { stroke:#2563EB; }
|
|
359
|
+
[data-theme="light"] .dep-flow-arrows-down svg marker path { fill:#2563EB; }
|
|
360
|
+
@media (max-width:600px) { .page { width:100%; padding:12px 14px 48px; } .layout { grid-template-columns:1fr; } .toc { position:static; } .grid.two,.grid.three { grid-template-columns:1fr; } .dep-flow-row { grid-template-columns:1fr; } }
|
|
361
|
+
@media (min-width:601px) and (max-width:960px) { .layout { grid-template-columns:1fr; } .toc { position:static; } .dep-flow-row { grid-template-columns:repeat(2,1fr); } }
|
|
362
|
+
@media print { .print-btn,.toc,.back-btn,.theme-toggle { display:none!important; } html,body { background:#fff!important; color:#000!important; } .page { max-width:100%!important; padding:0!important; } .layout { display:block!important; } section.card,.epic-card,.diagram-box,.wave,.bd-wrap,.callout,.mini-card { box-shadow:none!important; border:1px solid #ccc!important; border-radius:0!important; background:#fff!important; } .mermaid { page-break-inside:avoid; } }
|
|
363
|
+
`;
|
|
364
|
+
}
|
|
365
|
+
// ============================================================
|
|
366
|
+
// THEME TOGGLE JS
|
|
367
|
+
// ============================================================
|
|
368
|
+
function getThemeToggleJS() {
|
|
369
|
+
return `
|
|
370
|
+
<script>
|
|
371
|
+
(function(){
|
|
372
|
+
const KEY = 'creative-ia50-theme';
|
|
373
|
+
const saved = localStorage.getItem(KEY);
|
|
374
|
+
if (saved) document.documentElement.setAttribute('data-theme', saved);
|
|
375
|
+
window.toggleTheme = function() {
|
|
376
|
+
const current = document.documentElement.getAttribute('data-theme') || 'dark';
|
|
377
|
+
const next = current === 'dark' ? 'light' : 'dark';
|
|
378
|
+
document.documentElement.setAttribute('data-theme', next);
|
|
379
|
+
localStorage.setItem(KEY, next);
|
|
380
|
+
// Re-init mermaid with correct theme + variables
|
|
381
|
+
if (typeof mermaid !== 'undefined') {
|
|
382
|
+
const isLight = next === 'light';
|
|
383
|
+
const vars = isLight ? window.__mermaidLightVars : window.__mermaidDarkVars;
|
|
384
|
+
mermaid.initialize({ startOnLoad: false, theme: isLight ? 'default' : 'dark', themeVariables: vars || {} });
|
|
385
|
+
document.querySelectorAll('.mermaid[data-processed]').forEach(function(el) {
|
|
386
|
+
el.removeAttribute('data-processed');
|
|
387
|
+
el.innerHTML = el.getAttribute('data-original') || el.textContent;
|
|
388
|
+
});
|
|
389
|
+
mermaid.run();
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
// Store original mermaid content for re-rendering
|
|
393
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
394
|
+
document.querySelectorAll('.mermaid').forEach(function(el) {
|
|
395
|
+
el.setAttribute('data-original', el.textContent);
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
})();
|
|
399
|
+
</script>`;
|
|
400
|
+
}
|
|
401
|
+
// ============================================================
|
|
402
|
+
// MARKDOWN TABLE PARSER — pipe tables → <table> HTML
|
|
403
|
+
// ============================================================
|
|
404
|
+
function parseMarkdownTable(lines) {
|
|
405
|
+
const tableLines = lines.filter((l) => l.trim().startsWith("|"));
|
|
406
|
+
if (tableLines.length < 2)
|
|
407
|
+
return "";
|
|
408
|
+
const parseRow = (line) => line.split("|").map((c) => c.trim()).filter(Boolean);
|
|
409
|
+
const headers = parseRow(tableLines[0]);
|
|
410
|
+
// skip separator line (|---|---|...)
|
|
411
|
+
const startIdx = tableLines[1].match(/^\|?\s*[-:]+/) ? 2 : 1;
|
|
412
|
+
const rows = tableLines.slice(startIdx).map(parseRow);
|
|
413
|
+
const thHtml = headers.map((h) => `<th>${fmt(h)}</th>`).join("");
|
|
414
|
+
const trHtml = rows
|
|
415
|
+
.filter((r) => r.length > 0 && !r.every((c) => c.match(/^[-:]+$/)))
|
|
416
|
+
.map((r) => {
|
|
417
|
+
const cells = r.map((c) => {
|
|
418
|
+
// size badges
|
|
419
|
+
if (c === "L")
|
|
420
|
+
return `<td><span class="size-badge size-L">L</span></td>`;
|
|
421
|
+
if (c === "M")
|
|
422
|
+
return `<td><span class="size-badge size-M">M</span></td>`;
|
|
423
|
+
// dep-tags for EP-XX references
|
|
424
|
+
const tagged = c.replace(/(EP-\d{2})/g, '<span class="dep-tag">$1</span>');
|
|
425
|
+
return `<td>${fmt(tagged === "—" ? "—" : tagged)}</td>`;
|
|
426
|
+
});
|
|
427
|
+
return `<tr>${cells.join("")}</tr>`;
|
|
428
|
+
})
|
|
429
|
+
.join("\n");
|
|
430
|
+
return `<table><thead><tr>${thHtml}</tr></thead><tbody>${trHtml}</tbody></table>`;
|
|
431
|
+
}
|
|
432
|
+
function parseEpicSection(lines) {
|
|
433
|
+
let objective = "";
|
|
434
|
+
let scope = [];
|
|
435
|
+
let criteria = [];
|
|
436
|
+
let dependencies = [];
|
|
437
|
+
let squads = [];
|
|
438
|
+
let size = "M";
|
|
439
|
+
let currentBlock = "";
|
|
440
|
+
for (const line of lines) {
|
|
441
|
+
const trimmed = line.trim();
|
|
442
|
+
if (!trimmed || trimmed === "---")
|
|
443
|
+
continue;
|
|
444
|
+
if (trimmed.startsWith("**Objetivo:**")) {
|
|
445
|
+
objective = trimmed.replace("**Objetivo:**", "").trim();
|
|
446
|
+
currentBlock = "";
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (trimmed.startsWith("**Escopo:**")) {
|
|
450
|
+
currentBlock = "scope";
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
if (trimmed.startsWith("**Critérios de aceite:**") || trimmed.startsWith("**Criterios de aceite:**")) {
|
|
454
|
+
currentBlock = "criteria";
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (trimmed.startsWith("**Dependências:**") || trimmed.startsWith("**Dependencias:**")) {
|
|
458
|
+
const val = trimmed.replace(/\*\*Depend[eê]ncias:\*\*/, "").trim();
|
|
459
|
+
if (val && val !== "Nenhuma (épico fundacional)" && val !== "Nenhuma") {
|
|
460
|
+
dependencies = val.split(",").map((d) => d.trim()).filter(Boolean);
|
|
461
|
+
}
|
|
462
|
+
currentBlock = "";
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (trimmed.startsWith("**Squads:**")) {
|
|
466
|
+
const val = trimmed.replace("**Squads:**", "").trim();
|
|
467
|
+
squads = val.split("+").map((s) => s.trim()).filter(Boolean);
|
|
468
|
+
currentBlock = "";
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
if (trimmed.startsWith("**Estimativa:**")) {
|
|
472
|
+
const val = trimmed.replace("**Estimativa:**", "").trim();
|
|
473
|
+
size = val.includes("L") ? "L" : "M";
|
|
474
|
+
currentBlock = "";
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
if (trimmed.startsWith("- ") && currentBlock === "scope") {
|
|
478
|
+
scope.push(trimmed.slice(2));
|
|
479
|
+
}
|
|
480
|
+
else if (trimmed.startsWith("- [ ]") && currentBlock === "criteria") {
|
|
481
|
+
criteria.push(trimmed.slice(6).trim());
|
|
482
|
+
}
|
|
483
|
+
else if (trimmed.startsWith("- ") && currentBlock === "criteria") {
|
|
484
|
+
criteria.push(trimmed.slice(2));
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return { id: "", title: "", objective, scope, criteria, dependencies, squads, size };
|
|
488
|
+
}
|
|
489
|
+
function renderEpicCard(epic, idx) {
|
|
490
|
+
const scopeHtml = epic.scope.length > 0
|
|
491
|
+
? `<h4>Escopo</h4><ul class="scope-list">${epic.scope.map((s) => `<li>${fmt(s)}</li>`).join("")}</ul>`
|
|
492
|
+
: "";
|
|
493
|
+
const criteriaHtml = epic.criteria.length > 0
|
|
494
|
+
? `<h4>Critérios de Aceite</h4><ul class="criteria-list">${epic.criteria.map((c) => `<li>${fmt(c)}</li>`).join("")}</ul>`
|
|
495
|
+
: "";
|
|
496
|
+
const depsHtml = epic.dependencies.length > 0
|
|
497
|
+
? epic.dependencies.map((d) => `<span class="dep-tag">${d}</span>`).join("")
|
|
498
|
+
: '<span style="color:var(--muted);font-size:.85rem;">Nenhuma (épico fundacional)</span>';
|
|
499
|
+
const squadsHtml = epic.squads.map((s) => `<span class="squad-tag">${s}</span>`).join("");
|
|
500
|
+
const sizeCls = epic.size === "L" ? "size-L" : "size-M";
|
|
501
|
+
return `<div class="epic-card" id="epic-${idx}">
|
|
502
|
+
<div class="epic-header">
|
|
503
|
+
<h3>${epic.id} — ${epic.title}</h3>
|
|
504
|
+
<span class="size-badge ${sizeCls}">${epic.size === "L" ? "L (grande)" : "M (médio)"}</span>
|
|
505
|
+
</div>
|
|
506
|
+
<p class="lead"><strong>Objetivo:</strong> ${fmt(epic.objective)}</p>
|
|
507
|
+
${scopeHtml}
|
|
508
|
+
${criteriaHtml}
|
|
509
|
+
<div style="display:flex;flex-wrap:wrap;gap:12px;margin-top:14px;align-items:center;">
|
|
510
|
+
<div><strong style="font-size:.85rem;color:var(--muted);">Dependências:</strong> ${depsHtml}</div>
|
|
511
|
+
<div style="margin-left:auto;"><strong style="font-size:.85rem;color:var(--muted);">Squads:</strong> ${squadsHtml}</div>
|
|
512
|
+
</div>
|
|
513
|
+
</div>`;
|
|
514
|
+
}
|
|
515
|
+
// ============================================================
|
|
516
|
+
// WAVE RENDERER — Ondas de entrega
|
|
517
|
+
// ============================================================
|
|
518
|
+
function renderWaves(lines) {
|
|
519
|
+
const waves = [];
|
|
520
|
+
let current = null;
|
|
521
|
+
for (const line of lines) {
|
|
522
|
+
const trimmed = line.trim();
|
|
523
|
+
if (trimmed.startsWith("### ")) {
|
|
524
|
+
if (current)
|
|
525
|
+
waves.push(current);
|
|
526
|
+
current = { title: trimmed.replace("### ", ""), desc: "" };
|
|
527
|
+
}
|
|
528
|
+
else if (current && trimmed) {
|
|
529
|
+
current.desc += (current.desc ? " " : "") + trimmed;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (current)
|
|
533
|
+
waves.push(current);
|
|
534
|
+
return waves.map((w) => `<div class="wave">
|
|
535
|
+
<h4><span class="material-symbols-outlined" style="font-size:1rem;vertical-align:middle;margin-right:4px;">waves</span> ${w.title}</h4>
|
|
536
|
+
<p>${fmt(w.desc)}</p>
|
|
537
|
+
</div>`).join("");
|
|
538
|
+
}
|
|
539
|
+
// ============================================================
|
|
540
|
+
// MERMAID SEQUENCE DIAGRAMS — PIX journeys
|
|
541
|
+
// ============================================================
|
|
542
|
+
function getMermaidSequenceDiagrams() {
|
|
543
|
+
return {
|
|
544
|
+
"Enviar PIX": `sequenceDiagram
|
|
545
|
+
participant U as 👤 Usuário
|
|
546
|
+
participant App as 📱 App
|
|
547
|
+
participant BFF as 🔀 BFF
|
|
548
|
+
participant Core as ⚙️ Core Banking
|
|
549
|
+
participant DICT as 📖 DICT/BACEN
|
|
550
|
+
participant SPI as 🏦 SPI/BACEN
|
|
551
|
+
U->>App: Informa chave/dados
|
|
552
|
+
App->>BFF: POST /pix/transfer
|
|
553
|
+
BFF->>DICT: Consulta chave
|
|
554
|
+
DICT-->>BFF: Dados do recebedor
|
|
555
|
+
BFF-->>App: Confirma dados
|
|
556
|
+
U->>App: Autoriza (senha/bio)
|
|
557
|
+
App->>BFF: Confirma transferência
|
|
558
|
+
BFF->>Core: Debita conta
|
|
559
|
+
Core->>SPI: PACS.008 (ordem)
|
|
560
|
+
SPI-->>Core: PACS.002 (confirmação)
|
|
561
|
+
Core-->>BFF: Sucesso + endToEndId
|
|
562
|
+
BFF-->>App: Recibo
|
|
563
|
+
App-->>U: ✅ PIX enviado`,
|
|
564
|
+
"Receber PIX": `sequenceDiagram
|
|
565
|
+
participant SPI as 🏦 SPI/BACEN
|
|
566
|
+
participant Core as ⚙️ Core Banking
|
|
567
|
+
participant BFF as 🔀 BFF
|
|
568
|
+
participant Push as 🔔 Push Service
|
|
569
|
+
participant App as 📱 App
|
|
570
|
+
participant U as 👤 Usuário
|
|
571
|
+
SPI->>Core: PACS.008 (crédito)
|
|
572
|
+
Core->>Core: Credita conta
|
|
573
|
+
Core-->>SPI: PACS.002 (confirmação)
|
|
574
|
+
Core->>BFF: Evento: PIX recebido
|
|
575
|
+
BFF->>Push: Notificação
|
|
576
|
+
Push-->>App: Push notification
|
|
577
|
+
App-->>U: ✅ PIX recebido (< 3s)`,
|
|
578
|
+
"Chaves PIX": `sequenceDiagram
|
|
579
|
+
participant U as 👤 Usuário
|
|
580
|
+
participant App as 📱 App
|
|
581
|
+
participant BFF as 🔀 BFF
|
|
582
|
+
participant Core as ⚙️ Core Banking
|
|
583
|
+
participant DICT as 📖 DICT/BACEN
|
|
584
|
+
U->>App: Cadastrar chave
|
|
585
|
+
App->>BFF: POST /pix/keys
|
|
586
|
+
BFF->>Core: Valida cliente
|
|
587
|
+
Core->>DICT: CreateEntry
|
|
588
|
+
DICT-->>Core: Confirmação
|
|
589
|
+
Note over DICT,Core: Se e-mail/telefone: OTP
|
|
590
|
+
Core-->>BFF: Chave criada
|
|
591
|
+
BFF-->>App: Sucesso
|
|
592
|
+
App-->>U: ✅ Chave cadastrada`,
|
|
593
|
+
"QR Code PIX": `sequenceDiagram
|
|
594
|
+
participant U as 👤 Usuário
|
|
595
|
+
participant App as 📱 App
|
|
596
|
+
participant BFF as 🔀 BFF
|
|
597
|
+
participant Core as ⚙️ Core Banking
|
|
598
|
+
U->>App: Gerar QR Code
|
|
599
|
+
App->>BFF: POST /pix/qrcode
|
|
600
|
+
BFF->>Core: Gera payload EMV
|
|
601
|
+
Core-->>BFF: BR Code + imagem
|
|
602
|
+
BFF-->>App: QR Code
|
|
603
|
+
App-->>U: 📷 QR Code gerado
|
|
604
|
+
Note over U,App: Compartilha via WhatsApp/Telegram
|
|
605
|
+
U->>App: Ler QR Code (câmera)
|
|
606
|
+
App->>App: Parse BR Code
|
|
607
|
+
App->>BFF: POST /pix/transfer
|
|
608
|
+
Note over App,BFF: Segue fluxo Enviar PIX`
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
// ============================================================
|
|
612
|
+
// EPICS REPORT RENDERER — Full épicos HTML generation
|
|
613
|
+
// ============================================================
|
|
614
|
+
function renderEpicsReport(sections, diagramsDir) {
|
|
615
|
+
let html = "";
|
|
616
|
+
let epicIdx = 0;
|
|
617
|
+
for (let i = 0; i < sections.length; i++) {
|
|
618
|
+
const s = sections[i];
|
|
619
|
+
const titleLower = s.title.toLowerCase();
|
|
620
|
+
const id = `s${i}`;
|
|
621
|
+
const icon = getIcon(s.title);
|
|
622
|
+
// === VISÃO GERAL section ===
|
|
623
|
+
if (titleLower.includes("visão geral") || titleLower.includes("visao geral")) {
|
|
624
|
+
html += `<section class="card" id="${id}">
|
|
625
|
+
<h2><span class="material-symbols-outlined">${icon}</span> ${s.title}</h2>
|
|
626
|
+
<p class="lead">${s.lines.filter((l) => l.trim() && !l.startsWith("\`\`\`") && !l.includes("──") && !l.includes("├") && !l.includes("└") && !l.includes("│")).map((l) => fmt(l.trim())).join(" ")}</p>
|
|
627
|
+
<div class="diagram-box">
|
|
628
|
+
<h3>Fluxo de Dependências entre Épicos</h3>
|
|
629
|
+
<div class="dep-flow">
|
|
630
|
+
<div class="dep-flow-wave">
|
|
631
|
+
<div class="dep-flow-wave-label">Onda 1 — Fundação</div>
|
|
632
|
+
<div class="dep-flow-row">
|
|
633
|
+
<div class="dep-flow-node dep-flow-L" id="df-ep01">
|
|
634
|
+
<span class="dep-flow-id">EP-01</span>
|
|
635
|
+
<span class="dep-flow-name">Infraestrutura BACEN/SPI</span>
|
|
636
|
+
</div>
|
|
637
|
+
<div class="dep-flow-node dep-flow-M" id="df-ep08">
|
|
638
|
+
<span class="dep-flow-id">EP-08</span>
|
|
639
|
+
<span class="dep-flow-name">Observabilidade</span>
|
|
640
|
+
</div>
|
|
641
|
+
</div>
|
|
642
|
+
</div>
|
|
643
|
+
<div class="dep-flow-arrows-down">
|
|
644
|
+
<svg width="100%" height="40" aria-hidden="true"><line x1="25%" y1="0" x2="20%" y2="40" stroke="var(--accent)" stroke-width="2" marker-end="url(#arrowD)"/><line x1="25%" y1="0" x2="50%" y2="40" stroke="var(--accent)" stroke-width="2" marker-end="url(#arrowD)"/><line x1="25%" y1="0" x2="80%" y2="40" stroke="var(--accent)" stroke-width="2" marker-end="url(#arrowD)"/><defs><marker id="arrowD" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="var(--accent)"/></marker></defs></svg>
|
|
645
|
+
</div>
|
|
646
|
+
<div class="dep-flow-wave">
|
|
647
|
+
<div class="dep-flow-wave-label">Onda 2 — MVP PIX</div>
|
|
648
|
+
<div class="dep-flow-row">
|
|
649
|
+
<div class="dep-flow-node dep-flow-M" id="df-ep02">
|
|
650
|
+
<span class="dep-flow-id">EP-02</span>
|
|
651
|
+
<span class="dep-flow-name">Chaves PIX</span>
|
|
652
|
+
<span class="dep-flow-dep">← EP-01</span>
|
|
653
|
+
</div>
|
|
654
|
+
<div class="dep-flow-node dep-flow-L" id="df-ep03">
|
|
655
|
+
<span class="dep-flow-id">EP-03</span>
|
|
656
|
+
<span class="dep-flow-name">Enviar PIX</span>
|
|
657
|
+
<span class="dep-flow-dep">← EP-01, EP-02</span>
|
|
658
|
+
</div>
|
|
659
|
+
<div class="dep-flow-node dep-flow-M" id="df-ep04">
|
|
660
|
+
<span class="dep-flow-id">EP-04</span>
|
|
661
|
+
<span class="dep-flow-name">Receber PIX</span>
|
|
662
|
+
<span class="dep-flow-dep">← EP-01</span>
|
|
663
|
+
</div>
|
|
664
|
+
</div>
|
|
665
|
+
</div>
|
|
666
|
+
<div class="dep-flow-arrows-down">
|
|
667
|
+
<svg width="100%" height="40" aria-hidden="true"><line x1="33%" y1="0" x2="20%" y2="40" stroke="var(--accent)" stroke-width="2" marker-end="url(#arrowD2)"/><line x1="50%" y1="0" x2="50%" y2="40" stroke="var(--accent)" stroke-width="2" marker-end="url(#arrowD2)"/><line x1="66%" y1="0" x2="80%" y2="40" stroke="var(--accent)" stroke-width="2" marker-end="url(#arrowD2)"/><defs><marker id="arrowD2" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="var(--accent)"/></marker></defs></svg>
|
|
668
|
+
</div>
|
|
669
|
+
<div class="dep-flow-wave">
|
|
670
|
+
<div class="dep-flow-wave-label">Onda 3 — Experiência Completa</div>
|
|
671
|
+
<div class="dep-flow-row">
|
|
672
|
+
<div class="dep-flow-node dep-flow-M" id="df-ep05">
|
|
673
|
+
<span class="dep-flow-id">EP-05</span>
|
|
674
|
+
<span class="dep-flow-name">QR Code</span>
|
|
675
|
+
<span class="dep-flow-dep">← EP-03, EP-04</span>
|
|
676
|
+
</div>
|
|
677
|
+
<div class="dep-flow-node dep-flow-M" id="df-ep06">
|
|
678
|
+
<span class="dep-flow-id">EP-06</span>
|
|
679
|
+
<span class="dep-flow-name">Agendado + Devolução</span>
|
|
680
|
+
<span class="dep-flow-dep">← EP-03, EP-04</span>
|
|
681
|
+
</div>
|
|
682
|
+
<div class="dep-flow-node dep-flow-M" id="df-ep07">
|
|
683
|
+
<span class="dep-flow-id">EP-07</span>
|
|
684
|
+
<span class="dep-flow-name">Extrato + Limites</span>
|
|
685
|
+
<span class="dep-flow-dep">← EP-03, EP-04, EP-06</span>
|
|
686
|
+
</div>
|
|
687
|
+
</div>
|
|
688
|
+
</div>
|
|
689
|
+
</div>
|
|
690
|
+
</div>
|
|
691
|
+
${diagramsDir ? `<div class="diagram-box">
|
|
692
|
+
<h3><span class="material-symbols-outlined" style="vertical-align:middle;margin-right:6px;">cloud</span> Arquitetura AWS</h3>
|
|
693
|
+
<img src="${diagramsDir}/pix-aws-architecture.png" alt="Arquitetura AWS PIX" loading="lazy" />
|
|
694
|
+
</div>` : ""}
|
|
695
|
+
</section>`;
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
// === EP-XX sections → epic-card ===
|
|
699
|
+
if (s.title.match(/^EP-\d{2}/)) {
|
|
700
|
+
const epMatch = s.title.match(/^(EP-\d{2})\s*[—-]\s*(.+)/);
|
|
701
|
+
const parsed = parseEpicSection(s.lines);
|
|
702
|
+
if (parsed && epMatch) {
|
|
703
|
+
parsed.id = epMatch[1];
|
|
704
|
+
parsed.title = epMatch[2];
|
|
705
|
+
epicIdx++;
|
|
706
|
+
// Check if this epic has a matching PNG diagram
|
|
707
|
+
let diagramHtml = "";
|
|
708
|
+
const epId = epMatch[1].replace("EP-", "");
|
|
709
|
+
const diagramMap = {
|
|
710
|
+
"03": { file: "pix-flow-enviar.png", label: "Fluxo Enviar PIX" },
|
|
711
|
+
"04": { file: "pix-flow-receber.png", label: "Fluxo Receber PIX" },
|
|
712
|
+
"02": { file: "pix-flow-chaves.png", label: "Fluxo Chaves PIX" },
|
|
713
|
+
"05": { file: "pix-flow-qrcode.png", label: "Fluxo QR Code PIX" },
|
|
714
|
+
};
|
|
715
|
+
if (diagramsDir && diagramMap[epId]) {
|
|
716
|
+
diagramHtml += `<div class="diagram-box" style="margin-top:16px;">
|
|
717
|
+
<h3><span class="material-symbols-outlined" style="vertical-align:middle;margin-right:6px;">schema</span> ${diagramMap[epId].label} (AWS)</h3>
|
|
718
|
+
<img src="${diagramsDir}/${diagramMap[epId].file}" alt="${diagramMap[epId].label}" loading="lazy" />
|
|
719
|
+
</div>`;
|
|
720
|
+
}
|
|
721
|
+
// Mermaid sequence diagram for matching journeys
|
|
722
|
+
const seqDiagrams = getMermaidSequenceDiagrams();
|
|
723
|
+
const seqMap = {
|
|
724
|
+
"03": "Enviar PIX",
|
|
725
|
+
"04": "Receber PIX",
|
|
726
|
+
"02": "Chaves PIX",
|
|
727
|
+
"05": "QR Code PIX",
|
|
728
|
+
};
|
|
729
|
+
if (seqMap[epId] && seqDiagrams[seqMap[epId]]) {
|
|
730
|
+
diagramHtml += `<div class="diagram-box" style="margin-top:16px;">
|
|
731
|
+
<h3><span class="material-symbols-outlined" style="vertical-align:middle;margin-right:6px;">route</span> Diagrama de Sequência — ${seqMap[epId]}</h3>
|
|
732
|
+
<div class="mermaid">
|
|
733
|
+
${seqDiagrams[seqMap[epId]]}
|
|
734
|
+
</div>
|
|
735
|
+
</div>`;
|
|
736
|
+
}
|
|
737
|
+
html += `<section class="card" id="${id}">
|
|
738
|
+
<h2><span class="material-symbols-outlined">${icon}</span> ${s.title}</h2>
|
|
739
|
+
${renderEpicCard(parsed, epicIdx)}
|
|
740
|
+
${diagramHtml}
|
|
741
|
+
</section>`;
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
// === RESUMO DE ESTIMATIVAS section → table ===
|
|
746
|
+
if (titleLower.includes("resumo")) {
|
|
747
|
+
const tableHtml = parseMarkdownTable(s.lines);
|
|
748
|
+
const extraLines = s.lines.filter((l) => !l.trim().startsWith("|") && l.trim() && !l.trim().match(/^\|?\s*[-:]+/));
|
|
749
|
+
const extraHtml = extraLines.map((l) => `<p>${fmt(l)}</p>`).join("\n");
|
|
750
|
+
html += `<section class="card" id="${id}">
|
|
751
|
+
<h2><span class="material-symbols-outlined">${icon}</span> ${s.title}</h2>
|
|
752
|
+
${tableHtml}
|
|
753
|
+
${extraHtml}
|
|
754
|
+
</section>`;
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
// === ONDAS DE ENTREGA section → wave components ===
|
|
758
|
+
if (titleLower.includes("onda")) {
|
|
759
|
+
const wavesHtml = renderWaves(s.lines);
|
|
760
|
+
html += `<section class="card" id="${id}">
|
|
761
|
+
<h2><span class="material-symbols-outlined">${icon}</span> ${s.title}</h2>
|
|
762
|
+
${wavesHtml}
|
|
763
|
+
</section>`;
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
// === DEFAULT fallback ===
|
|
767
|
+
html += renderSection(s, i, sections);
|
|
768
|
+
}
|
|
769
|
+
return html;
|
|
770
|
+
}
|
|
771
|
+
// ============================================================
|
|
772
|
+
// MAIN EXPORT — generateReport
|
|
773
|
+
// ============================================================
|
|
774
|
+
export async function generateReport(params) {
|
|
775
|
+
const { title, content, reportType, outputDir, diagramsDir } = params;
|
|
776
|
+
const now = new Date();
|
|
777
|
+
const dateStr = now.toISOString().split("T")[0];
|
|
778
|
+
const slug = title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "")
|
|
779
|
+
.replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
780
|
+
const filename = `${dateStr}-${reportType}-${slug}.html`;
|
|
781
|
+
const dir = outputDir || join(homedir(), "creative-reports");
|
|
782
|
+
await mkdir(dir, { recursive: true });
|
|
783
|
+
const filepath = join(dir, filename);
|
|
784
|
+
// Try to load extra CSS from S3 (optional enhancement)
|
|
785
|
+
let extraCSS = "";
|
|
786
|
+
try {
|
|
787
|
+
extraCSS = await loadTemplate("_base-styles.css");
|
|
788
|
+
}
|
|
789
|
+
catch {
|
|
790
|
+
// No extra CSS — dual theme CSS is self-contained
|
|
791
|
+
}
|
|
792
|
+
const sections = parseSections(content);
|
|
793
|
+
const isEpics = reportType === "epics";
|
|
794
|
+
const sectionsHtml = isEpics
|
|
795
|
+
? renderEpicsReport(sections, diagramsDir)
|
|
796
|
+
: sections.map((s, i) => renderSection(s, i, sections)).join("\n");
|
|
797
|
+
const tocHtml = renderToc(sections);
|
|
798
|
+
const reportTypeLabels = {
|
|
799
|
+
feasibility: "Parecer de Viabilidade",
|
|
800
|
+
architecture: "Arquitetura",
|
|
801
|
+
analysis: "Analise de Codigo",
|
|
802
|
+
process: "Processo",
|
|
803
|
+
epics: "Epicos de Entrega",
|
|
804
|
+
};
|
|
805
|
+
const hasMermaid = isEpics || content.includes("```mermaid") || content.includes("class=\"mermaid\"");
|
|
806
|
+
const mermaidScript = hasMermaid
|
|
807
|
+
? `<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
|
|
808
|
+
<script>
|
|
809
|
+
(function(){
|
|
810
|
+
var darkVars={primaryColor:'#1B3B6F',primaryTextColor:'#E5E7EB',primaryBorderColor:'#2F6DB3',lineColor:'#4FB3FF',secondaryColor:'#0F1C2E',tertiaryColor:'#111827',noteBkgColor:'#1B3B6F',noteTextColor:'#E5E7EB',noteBorderColor:'#2F6DB3',actorBkg:'#1B3B6F',actorTextColor:'#E5E7EB',actorBorder:'#2F6DB3',signalColor:'#4FB3FF',signalTextColor:'#E5E7EB',labelBoxBkgColor:'#0F1C2E',labelTextColor:'#E5E7EB'};
|
|
811
|
+
var lightVars={primaryColor:'#DBEAFE',primaryTextColor:'#1E293B',primaryBorderColor:'#2563EB',lineColor:'#1D4ED8',secondaryColor:'#EFF6FF',tertiaryColor:'#F8FAFC',noteBkgColor:'#DBEAFE',noteTextColor:'#1E293B',noteBorderColor:'#2563EB',actorBkg:'#DBEAFE',actorTextColor:'#1E293B',actorBorder:'#2563EB',signalColor:'#1D4ED8',signalTextColor:'#1E293B',labelBoxBkgColor:'#EFF6FF',labelTextColor:'#1E293B'};
|
|
812
|
+
var saved=localStorage.getItem('creative-ia50-theme')||'dark';
|
|
813
|
+
var isL=saved==='light';
|
|
814
|
+
mermaid.initialize({startOnLoad:true,theme:isL?'default':'dark',themeVariables:isL?lightVars:darkVars});
|
|
815
|
+
window.__mermaidDarkVars=darkVars;
|
|
816
|
+
window.__mermaidLightVars=lightVars;
|
|
817
|
+
})();
|
|
818
|
+
</script>`
|
|
819
|
+
: "";
|
|
820
|
+
const html = `<!DOCTYPE html>
|
|
821
|
+
<html lang="pt-BR">
|
|
822
|
+
<head>
|
|
823
|
+
<meta charset="UTF-8" />
|
|
824
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
825
|
+
<title>${title} — Creative IA 50</title>
|
|
826
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap" />
|
|
827
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
|
|
828
|
+
${mermaidScript}
|
|
829
|
+
<style>
|
|
830
|
+
${getDualThemeCSS()}
|
|
831
|
+
${extraCSS}
|
|
832
|
+
</style>
|
|
833
|
+
</head>
|
|
834
|
+
<body>
|
|
835
|
+
${getThemeToggleJS()}
|
|
836
|
+
<a href="#main-content" class="skip-link" style="position:absolute;left:-9999px;top:0;z-index:9999;padding:8px 16px;background:#4FB3FF;color:#000;font-weight:600;text-decoration:none;border-radius:0 0 4px 0;" onfocus="this.style.left='0'" onblur="this.style.left='-9999px'">Pular para conteudo</a>
|
|
837
|
+
|
|
838
|
+
<button class="theme-toggle" onclick="toggleTheme()" title="Alternar tema" aria-label="Alternar tema claro/escuro">
|
|
839
|
+
<span class="material-symbols-outlined icon-dark">light_mode</span>
|
|
840
|
+
<span class="material-symbols-outlined icon-light">dark_mode</span>
|
|
841
|
+
</button>
|
|
842
|
+
<button class="print-btn" onclick="window.print()" title="Imprimir" aria-label="Imprimir">
|
|
843
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 8a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1z"/><path d="M5 1a1 1 0 0 0-1 1v1H3.5A1.5 1.5 0 0 0 2 4.5v3A1.5 1.5 0 0 0 3.5 9H4v2.5a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V9h.5A1.5 1.5 0 0 0 14 7.5v-3A1.5 1.5 0 0 0 12.5 3H12V2a1 1 0 0 0-1-1H5zm6 1v1H5V2h6zm-6 7v3h6V9H5z"/></svg>
|
|
844
|
+
</button>
|
|
845
|
+
|
|
846
|
+
<div class="page">
|
|
847
|
+
<header class="cover">
|
|
848
|
+
<div class="brand">
|
|
849
|
+
<img src="${LOGO_BASE64}" alt="" role="presentation" class="brand-logo" />
|
|
850
|
+
<div>
|
|
851
|
+
<div class="eyebrow">Creative IA 50 · ${reportTypeLabels[reportType] || reportType}</div>
|
|
852
|
+
<h1>${title}</h1>
|
|
853
|
+
<p class="subtitle">Documento gerado automaticamente via MCP Server</p>
|
|
854
|
+
</div>
|
|
855
|
+
</div>
|
|
856
|
+
<div class="cover-meta">
|
|
857
|
+
<span class="pill" style="background:linear-gradient(135deg,#2F6DB3,#4FB3FF);color:#fff;font-weight:600">${reportType.toUpperCase()}</span>
|
|
858
|
+
<span class="pill">${dateStr}</span>
|
|
859
|
+
<span class="pill">MCP Server v1.0.0</span>
|
|
860
|
+
</div>
|
|
861
|
+
</header>
|
|
862
|
+
|
|
863
|
+
<div class="layout">
|
|
864
|
+
<aside class="toc" role="navigation" aria-label="Sumario">
|
|
865
|
+
<h2><span class="material-symbols-outlined" style="font-size:16px;vertical-align:middle">menu_book</span> Sumario</h2>
|
|
866
|
+
${tocHtml}
|
|
867
|
+
</aside>
|
|
868
|
+
|
|
869
|
+
<main class="content" id="main-content">
|
|
870
|
+
${sectionsHtml}
|
|
871
|
+
</main>
|
|
872
|
+
</div>
|
|
873
|
+
|
|
874
|
+
<div class="footer">
|
|
875
|
+
Creative IA 50 © ${now.getFullYear()} — Propriedade intelectual protegida. Distribuicao nao autorizada e proibida.
|
|
876
|
+
</div>
|
|
877
|
+
</div>
|
|
878
|
+
|
|
879
|
+
</body>
|
|
880
|
+
</html>`;
|
|
881
|
+
await writeFile(filepath, html, "utf-8");
|
|
882
|
+
return [
|
|
883
|
+
`Report generated successfully.`,
|
|
884
|
+
``,
|
|
885
|
+
`File: ${filepath}`,
|
|
886
|
+
`Type: ${reportType}`,
|
|
887
|
+
`Size: ${Buffer.byteLength(html, "utf-8")} bytes`,
|
|
888
|
+
``,
|
|
889
|
+
`Open in browser: file://${filepath}`,
|
|
890
|
+
].join("\n");
|
|
891
|
+
}
|