@ingcrea/aumic-tailwind-killer 3.1.7 → 4.1.3

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/dist/index.js CHANGED
@@ -21,9 +21,10 @@ const parser_1 = require("./ast/parser");
21
21
  const provider_1 = require("./ai/provider");
22
22
  const scraper_1 = require("./core/scraper");
23
23
  // Versión dinámica
24
- let pkgVersion = '2.0.x';
24
+ let userConfig = {};
25
+ let pkgVersion = '4.1.3';
25
26
  try {
26
- pkgVersion = JSON.parse(fs_extra_1.default.readFileSync(path_1.default.join(__dirname, '../package.json'), 'utf-8')).version;
27
+ pkgVersion = JSON.parse(fs_extra_1.default.readFileSync(path_1.default.join(__dirname, '../package.json'), 'utf-8').replace(/^\uFEFF/, '')).version;
27
28
  }
28
29
  catch (e) { }
29
30
  async function runConcurrent(items, concurrency, task) {
@@ -117,11 +118,11 @@ function generateReport(mapping, targetDir, isSimulate = true) {
117
118
  if (counts[cat] !== undefined)
118
119
  counts[cat]++;
119
120
  const c = categoryMeta[cat] || categoryMeta.molecules;
120
- rows.push(`<tr data-cat="${cat}">
121
- <td class="tw-cell copyable" onclick="copyText(this)">${orig}</td>
122
- <td class="aumic-cell copyable" onclick="copyText(this)">${map.aumicClass}</td>
123
- <td class="occ-cell" data-occ="${occ}"><div class="occ-bar" style="width: ${Math.min(100, occ * 2)}%; background: ${c.text}"></div><span>${occ}</span></td>
124
- <td><span class="badge" style="border-color:${c.border};color:${c.text};background:${c.bg}">${c.label}</span></td>
121
+ rows.push(`<tr data-cat="${cat}">
122
+ <td class="tw-cell copyable" onclick="copyText(this)">${orig}</td>
123
+ <td class="aumic-cell copyable" onclick="copyText(this)">${map.aumicClass}</td>
124
+ <td class="occ-cell" data-occ="${occ}"><div class="occ-bar" style="width: ${Math.min(100, occ * 2)}%; background: ${c.text}"></div><span>${occ}</span></td>
125
+ <td><span class="badge" style="border-color:${c.border};color:${c.text};background:${c.bg}">${c.label}</span></td>
125
126
  </tr>`);
126
127
  }
127
128
  const savedBytes = originalBytes - aumicBytes;
@@ -155,675 +156,675 @@ function generateReport(mapping, targetDir, isSimulate = true) {
155
156
  { label: 'Galaxias', value: counts.galaxies, color: '#facc15' }
156
157
  ];
157
158
  // Top 5 Criminals
158
- const top5 = sortedMappings.slice(0, 5).map((m, i) => `
159
- <div class="criminal">
160
- <div class="c-rank">#${i + 1}</div>
161
- <div class="c-info">
162
- <div class="c-class">${m[0]}</div>
163
- <div class="c-stats">Repeticiones: <strong>${m[1].occurrences || 1}</strong> | Categoría: ${m[1].category}</div>
164
- </div>
165
- </div>
159
+ const top5 = sortedMappings.slice(0, 5).map((m, i) => `
160
+ <div class="criminal">
161
+ <div class="c-rank">#${i + 1}</div>
162
+ <div class="c-info">
163
+ <div class="c-class">${m[0]}</div>
164
+ <div class="c-stats">Repeticiones: <strong>${m[1].occurrences || 1}</strong> | Categoría: ${m[1].category}</div>
165
+ </div>
166
+ </div>
166
167
  `).join('');
167
- const html = `<!DOCTYPE html>
168
- <html lang="es"><head><meta charset="UTF-8"><title>AUM-IC · Dashboard C4I</title><style>
169
- :root { --bg: #030712; --panel: rgba(15, 23, 42, 0.7); --panel-hover: #1e293b; --text: #e2e8f0; --muted: #64748b; --border: rgba(51, 65, 85, 0.6); --accent: #00e5ff; }
170
- *{box-sizing:border-box;margin:0;padding:0}
171
- body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;padding:2rem 3rem;position:relative;overflow-x:hidden;}
172
- #bgCanvas { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -1; pointer-events: none; }
173
- .content-wrapper { position: relative; z-index: 10; }
174
- header{display:flex;align-items:center;justify-content:space-between;margin-bottom:2rem;padding-bottom:1.5rem;border-bottom:1px solid var(--border)}
175
- header h1{font-size:1.8rem;font-weight:900;color:#fff;letter-spacing:-0.5px}header h1 span{color:var(--accent);text-shadow:0 0 10px rgba(0,229,255,0.4)}
176
- .subtitle{font-size:0.85rem;color:var(--muted);margin-top:6px}.safe-tag{background:rgba(0,229,255,0.1);border:1px solid rgba(0,229,255,0.3);color:var(--accent);font-size:0.75rem;padding:6px 16px;border-radius:999px;font-weight:700;box-shadow:0 0 15px rgba(0,229,255,0.1)}
177
- .dashboard-grid { display: grid; grid-template-columns: 320px 1fr; gap: 2rem; }
178
- .sidebar { display: flex; flex-direction: column; gap: 1.5rem; }
179
- .main-view { display: flex; flex-direction: column; gap: 1.5rem; }
180
-
181
- /* Panels */
182
- .panel { background: var(--panel); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); padding: 1.5rem; border-radius: 16px; border: 1px solid var(--border); box-shadow: 0 10px 30px rgba(0,0,0,0.3); }
183
- .panel h2 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 1px; color: var(--muted); margin-bottom: 1rem; }
184
-
185
- /* Canvas Chart */
186
- .chart-container { display: flex; justify-content: center; align-items: center; margin-bottom: 1rem; position: relative; height: 200px; }
187
- .chart-center-overlay { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; pointer-events: none; }
188
- .chart-center-overlay .score { font-size: 2.5rem; font-weight: 900; color: ${gradeColor}; line-height: 1; text-shadow: 0 0 15px ${gradeColor}44; }
189
- .chart-center-overlay .score-lbl { font-size: 0.6rem; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; margin-top: 4px; }
190
- .legend { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; font-size: 0.75rem; }
191
- .leg-item { display: flex; align-items: center; gap: 6px; }
192
- .leg-color { width: 10px; height: 10px; border-radius: 50%; }
193
-
194
- /* Hero Metrics */
195
- .metrics-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }
196
- .metric { background: rgba(0,0,0,0.3); padding: 1.2rem; border-radius: 12px; border: 1px solid rgba(255,255,255,0.05); backdrop-filter: blur(5px); }
197
- .metric h3 { font-size: 1.8rem; font-weight: 900; color: #fff; line-height: 1; margin-bottom: 4px; }
198
- .metric p { font-size: 0.7rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
199
- .highlight { color: #34d399 !important; text-shadow: 0 0 10px rgba(52,211,153,0.4); }
200
- .highlight-psi { color: #facc15 !important; }
201
-
202
- /* Criminals */
203
- .criminal { display: flex; gap: 1rem; align-items: center; padding: 10px; background: rgba(0,0,0,0.2); border-radius: 8px; margin-bottom: 8px; }
204
- .c-rank { font-size: 1.2rem; font-weight: 900; color: #ef4444; width: 30px; text-align: center; }
205
- .c-info { flex: 1; min-width: 0; }
206
- .c-class { font-family: 'Fira Code', monospace; font-size: 0.75rem; color: #f472b6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 4px; }
207
- .c-stats { font-size: 0.65rem; color: var(--muted); }
208
-
209
- /* Table Controls */
210
- .toolbar{display:flex;gap:0.5rem;align-items:center;flex-wrap:wrap; margin-bottom: 1.5rem;}
211
- .fbtn{background:rgba(0,0,0,0.2);border:1px solid var(--border);color:#94a3b8;font-size:0.75rem;padding:8px 16px;border-radius:8px;cursor:pointer;transition:all 0.2s;font-weight:500}
212
- .fbtn:hover,.fbtn.active{color:#fff;border-color:var(--accent);background:rgba(0,229,255,0.1);box-shadow:0 0 10px rgba(0,229,255,0.2)}
213
- .search{margin-left:auto;background:rgba(0,0,0,0.2);border:1px solid var(--border);color:#fff;font-size:0.8rem;padding:8px 16px;border-radius:8px;outline:none;width:220px;transition:all 0.2s}
214
- .search:focus{border-color:var(--accent);box-shadow:0 0 0 2px rgba(0,229,255,0.2)}
215
- .page-size-sel { background: rgba(15, 23, 42, 0.9); color: #fff; background:rgba(0,0,0,0.2);border:1px solid var(--border);color:#fff;font-size:0.75rem;padding:8px;border-radius:8px;outline:none; cursor:pointer;}
216
-
217
- /* Table */
218
- .table-container{border-radius:12px;border:1px solid var(--border);overflow:hidden;background:rgba(0,0,0,0.2);}
219
- table{width:100%;border-collapse:collapse;font-size:0.8rem}
220
- th{background:rgba(0,0,0,0.3);color:var(--muted);text-align:left;padding:12px 16px;border-bottom:1px solid var(--border);font-weight:600;text-transform:uppercase;letter-spacing:1px;font-size:0.7rem;cursor:pointer;user-select:none}
221
- th:hover{color:#fff}
222
- td{padding:12px 16px;border-bottom:1px solid rgba(255,255,255,0.02);vertical-align:middle;transition:background 0.15s}
223
- tr:hover td{background:rgba(255,255,255,0.05)}
224
- .tw-cell{font-family:'Fira Code',Consolas,monospace;color:#f472b6;font-size:0.75rem;max-width:300px;word-break:break-all}
225
- .aumic-cell{font-family:'Fira Code',Consolas,monospace;color:#38bdf8;font-size:0.75rem;white-space:nowrap;font-weight:600}
226
- .occ-cell{display:flex;align-items:center;gap:10px;font-family:'Fira Code',monospace;font-weight:bold;color:#fff}
227
- .occ-bar{height:6px;border-radius:3px;opacity:0.8}
228
- .badge{font-size:0.65rem;padding:4px 10px;border-radius:999px;border:1px solid;font-weight:800;white-space:nowrap;text-transform:uppercase;letter-spacing:0.5px}
229
-
230
- /* Pagination */
231
- .pagination { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; background: rgba(0,0,0,0.3); border-top: 1px solid var(--border); }
232
- .pag-controls { display: flex; gap: 6px; align-items: center; background: rgba(15,23,42,0.6); padding: 4px; border-radius: 8px; border: 1px solid var(--border); }
233
- .pag-btn { background: transparent; border: none; color: var(--muted); padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 0.75rem; transition: 0.2s; min-width: 32px; text-align: center; font-weight: 600; }
234
- .pag-btn:hover:not(:disabled) { background: rgba(255,255,255,0.05); color: #fff; }
235
- .pag-btn.active { background: rgba(0,229,255,0.15); color: var(--accent); box-shadow: inset 0 0 0 1px rgba(0,229,255,0.3); }
236
- .pag-btn:disabled { opacity: 0.3; cursor: not-allowed; }
237
- .pag-info { font-size: 0.75rem; color: var(--muted); }
238
- .pag-dots { color: var(--muted); font-size: 0.75rem; font-weight: bold; padding: 0 4px; }
239
-
240
- /* Footer */
241
- .footer { position: relative; bottom: 0; left: 0; width: 100%; text-align: center; padding: 20px; font-size: 0.75rem; color: var(--muted); border-top: 1px solid rgba(255,255,255,0.05); background: rgba(0,0,0,0.3); backdrop-filter: blur(5px); margin-top: auto; }
242
- .footer a { color: var(--accent); text-decoration: none; font-weight: 600; transition: color 0.2s; }
243
- .footer a:hover { color: #fff; text-shadow: 0 0 10px var(--accent); }
244
- .page-size-sel option { background: #0f172a; color: #fff; }
245
- /* Export & Toast */
246
- .export-group-top { display: flex; gap: 8px; margin-left: auto; align-items: center; } .export-group-top .fbtn { background: rgba(0,229,255,0.05); }
247
- .toast { position: fixed; bottom: 20px; right: 20px; background: rgba(0, 229, 255, 0.15); border: 1px solid var(--accent); color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 0.8rem; font-weight: 600; box-shadow: 0 5px 15px rgba(0,229,255,0.2); backdrop-filter: blur(5px); transform: translateY(100px); opacity: 0; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); z-index: 9999; }
248
- .toast.show { transform: translateY(0); opacity: 1; }
249
- .copyable { cursor: pointer; transition: color 0.2s; position: relative; }
250
- .copyable:hover { color: #fff; text-shadow: 0 0 8px rgba(255,255,255,0.5); }
251
- .copyable::after { content: 'Copiar'; position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.8); color: #fff; font-size: 0.6rem; padding: 2px 6px; border-radius: 4px; opacity: 0; pointer-events: none; transition: opacity 0.2s; }
252
- .copyable:hover::after { opacity: 1; }
253
-
254
- /* Sticky Header */
255
- .table-container { overflow-x: auto; }
256
-
257
-
258
- /* Print PDF (Mesa Directiva) */
259
- @media print {
260
- body { background: #fff !important; color: #000 !important; padding: 0 !important; font-size: 10pt; }
261
- #bgCanvas, .toolbar, .pagination, .safe-tag, .export-group-top, .footer, .toast { display: none !important; }
262
- .content-wrapper { padding: 0 !important; max-width: 100% !important; }
263
- header { border-bottom: 2px solid #000; padding-bottom: 10px; margin-bottom: 20px; flex-direction: column !important; align-items: flex-start !important; }
264
- header h1 { color: #000 !important; text-shadow: none !important; margin: 0; }
265
- header h1 span { color: #000 !important; }
266
-
267
- .dashboard-grid { display: block !important; }
268
- .sidebar { display: flex !important; flex-direction: row !important; gap: 20px !important; margin-bottom: 20px !important; page-break-inside: avoid; }
269
- .sidebar .panel { flex: 1; border: 1px solid #cbd5e1 !important; padding: 15px !important; border-radius: 8px !important; }
270
-
271
- .panel { background: none !important; border: none !important; box-shadow: none !important; padding: 0 !important; }
272
-
273
- .metrics-grid { display: grid !important; grid-template-columns: repeat(3, 1fr) !important; gap: 10px !important; margin-bottom: 20px !important; page-break-inside: avoid; }
274
- .metric { background: #f8fafc !important; border: 1px solid #cbd5e1 !important; padding: 10px !important; border-radius: 8px !important; }
275
- .metric h3 { color: #000 !important; text-shadow: none !important; font-size: 1.2rem; margin-bottom: 5px; }
276
- .metric p { color: #333 !important; font-size: 0.8rem; }
277
-
278
- .criteria-box { background: #f8fafc !important; border: 1px solid #cbd5e1 !important; color: #000 !important; }
279
- .criteria-box strong { color: #000 !important; }
280
- .criteria-box span { color: #000 !important; font-weight: bold; }
281
-
282
- .chart-center-overlay .score { color: #000 !important; text-shadow: none !important; }
283
- .chart-center-overlay .score-lbl { color: #333 !important; }
284
-
285
- .leg-item { color: #000 !important; }
286
-
287
- .table-container { max-height: none !important; border: none !important; overflow: visible !important; }
288
- table { border-collapse: collapse !important; width: 100% !important; page-break-inside: auto; }
289
- tr { page-break-inside: avoid; page-break-after: auto; }
290
- th, td { border: 1px solid #cbd5e1 !important; color: #000 !important; padding: 6px !important; font-size: 9pt !important; }
291
- th { background: #f1f5f9 !important; color: #000 !important; font-weight: bold; }
292
- .tw-cell, .aumic-cell { color: #000 !important; font-weight: normal; }
293
- .occ-bar { background: #94a3b8 !important; }
294
-
295
- @page { margin: 1cm; size: auto; }
296
- }
297
- }
298
- </style></head><body style="display: flex; flex-direction: column;">
299
-
300
- <canvas id="bgCanvas"></canvas>
301
-
302
- <div class="content-wrapper">
303
- <header>
304
- <div><h1>⚔️ <span>AUM-IC</span> · Centro de Inteligencia</h1>
305
- <p class="subtitle">Análisis Forense completado exitosamente sin mutación en disco.</p></div>
306
- <div class="export-group-top" style="display:flex; gap:8px; margin-left: 20px;">
307
- <button class="fbtn" onclick="exportCSV()">⬇️ CSV</button><button class="fbtn" onclick="exportMD()">⬇️ MD</button><button class="fbtn" onclick="window.print()">🖨️ PDF</button>
308
- </div>
309
- <div class="safe-tag" style="align-items: flex-end; display: flex; flex-direction: column;">
310
- <span>${isSimulate ? 'MODO DRY-RUN ACTIVO' : 'MUTACIÓN AUM-IC COMPLETADA'}</span>
311
- <span style="font-size: 0.65rem; opacity: 0.8; margin-top: 2px;">v${pkgVersion}</span>
312
- </div>
313
- </header>
314
-
315
- <main class="dashboard-grid">
316
- <aside class="sidebar">
317
- <article class="panel">
318
- <h2>Mapeo Estructural</h2>
319
- <div class="chart-container">
320
- <canvas id="donutCanvas" width="220" height="220"></canvas>
321
- <div class="chart-center-overlay">
322
- <div class="score">${grade}</div>
323
- <div class="score-lbl">Rank</div>
324
- </div>
325
- </div>
326
- <div class="legend">
327
- <div class="leg-item"><div class="leg-color" style="background:#22d3ee"></div>Átomos (${counts.atoms})</div>
328
- <div class="leg-item"><div class="leg-color" style="background:#a78bfa"></div>Moléculas (${counts.molecules})</div>
329
- <div class="leg-item"><div class="leg-color" style="background:#34d399"></div>Organismos (${counts.organisms})</div>
330
- <div class="leg-item"><div class="leg-color" style="background:#fb923c"></div>Ecosistemas (${counts.ecosystems})</div>
331
- <div class="leg-item"><div class="leg-color" style="background:#facc15"></div>Galaxias (${counts.galaxies})</div>
332
- </div>
333
- <div class="criteria-box" style="margin-top: 1.5rem; padding: 1rem; background: rgba(0,0,0,0.2); border-radius: 8px; border: 1px solid var(--border); font-size: 0.75rem; color: var(--muted); line-height: 1.5;">
334
- <strong style="color: #fff; margin-bottom: 0.5rem; display: block;">Criterio de Evaluación AUM-IC</strong>
335
- <ul style="margin: 0; padding-left: 1rem; list-style: square;">
336
- <li><span style="color:#22d3ee">Átomos:</span> Utilidades base simples (p-4, m-2, flex).</li>
337
- <li><span style="color:#a78bfa">Moléculas:</span> Estilos condicionales y variantes (hover:, focus:).</li>
338
- <li><span style="color:#34d399">Organismos:</span> Cadenas largas y componentes estructurales.</li>
339
- <li><span style="color:#fb923c">Ecosistemas:</span> Combinaciones de Layouts complejos (Grid/Flex).</li>
340
- <li><span style="color:#facc15">Galaxias:</span> Declaraciones globales masivas o de alta densidad.</li>
341
- </ul>
342
- <div style="margin-top: 0.8rem; padding-top: 0.8rem; border-top: 1px solid rgba(255,255,255,0.1);">
343
- <strong style="color: #fff; display:block; margin-bottom: 0.3rem;">🏆 Rank (Score) & Eficiencia</strong>
344
- <div style="margin-bottom: 0.6rem; color: #94a3b8;">La <b>Eficiencia de Refactorización</b> mide el porcentaje exacto de peso (bytes) que AUM-IC logró purgar del DOM frente al HTML inflado original.</div>
345
- <div style="display: flex; gap: 6px; flex-wrap: wrap; font-size: 0.7rem;">
346
- <span style="background: rgba(250,204,21,0.15); color:#facc15; padding: 3px 6px; border-radius: 4px; border: 1px solid #facc1544;"><b>S</b> ≥ 70%</span>
347
- <span style="background: rgba(52,211,153,0.15); color:#34d399; padding: 3px 6px; border-radius: 4px; border: 1px solid #34d39944;"><b>A</b> ≥ 50%</span>
348
- <span style="background: rgba(34,211,238,0.15); color:#22d3ee; padding: 3px 6px; border-radius: 4px; border: 1px solid #22d3ee44;"><b>B</b> ≥ 30%</span>
349
- <span style="background: rgba(167,139,250,0.15); color:#a78bfa; padding: 3px 6px; border-radius: 4px; border: 1px solid #a78bfa44;"><b>C</b> ≥ 15%</span>
350
- <span style="background: rgba(239,68,68,0.15); color:#ef4444; padding: 3px 6px; border-radius: 4px; border: 1px solid #ef444444;"><b>D</b> < 15%</span>
351
- </div>
352
- </div>
353
- </div>
354
- </article>
355
-
356
- <article class="panel">
357
- <h2>☠️ Top 5 Ofensores del DOM</h2>
358
- ${top5}
359
- </article>
360
- </aside>
361
-
362
- <section class="main-view">
363
- <div class="metrics-grid">
364
- <div class="metric">
365
- <h3 class="highlight">🔥 ${savedKB} KB</h3>
366
- <p>Peso/Tamaño Estimado Salvado</p>
367
- </div>
368
- <div class="metric">
369
- <h3 class="highlight-psi">🚀 +${psiImpact} pts</h3>
370
- <p>Impacto Estimado Google PSI</p>
371
- </div>
372
- <div class="metric">
373
- <h3>${efficiency}%</h3>
374
- <p>Eficiencia de Refactorización</p>
375
- </div>
376
- <div class="metric">
377
- <h3>${mapping.size}</h3>
378
- <p>Clases Únicas Tailwind</p>
379
- </div>
380
- <div class="metric">
381
- <h3>${totalOccurrences}</h3>
382
- <p>Instancias Totales Detectadas</p>
383
- </div>
384
- <div class="metric">
385
- <h3>${totalOccurrences - mapping.size}</h3>
386
- <p>Redundancia Estructural (Repetidas)</p>
387
- </div>
388
- </div>
389
-
390
- <article class="panel">
391
- <nav class="toolbar">
392
- <button class="fbtn active" onclick="filter('all',this)">Todos</button>
393
- <button class="fbtn" onclick="filter('atoms',this)">Átomos</button>
394
- <button class="fbtn" onclick="filter('molecules',this)">Moléculas</button>
395
- <button class="fbtn" onclick="filter('organisms',this)">Organismos</button>
396
- <button class="fbtn" onclick="filter('ecosystems',this)">Ecosistemas</button>
397
- <button class="fbtn" onclick="filter('galaxies',this)">Galaxias</button>
398
-
399
- <input class="search" type="text" id="searchInput" placeholder="Buscar clase (Regex)..." oninput="search(this.value)">
400
- <select class="page-size-sel" id="pageSizeSelect" onchange="changePageSize(this.value)">
401
- <option value="20">20 por pág</option>
402
- <option value="50">50 por pág</option>
403
- <option value="100">100 por pág</option>
404
- <option value="all">TODOS</option>
405
- </select>
406
- </nav>
407
- <section class="table-container">
408
- <table id="table">
409
- <thead><tr>
410
- <th onclick="sortTable(0, 'str')">Clase Original (Tailwind) ↕</th>
411
- <th onclick="sortTable(1, 'str')">Clase AUM-IC Transmutada ↕</th>
412
- <th onclick="sortTable(2, 'num')">Frecuencia ↕</th>
413
- <th onclick="sortTable(3, 'str')">Nivel ↕</th>
414
- </tr></thead>
415
- <tbody id="tbody">${rows.join('')}</tbody>
416
- </table>
417
- <div class="pagination">
418
- <span class="pag-info" id="page-info"></span>
419
- <div class="pag-controls" id="pag-controls"></div>
420
- </div>
421
- </section>
422
- </article>
423
- </section>
424
- </main>
425
- </div>
426
-
427
- <div id="toast" class="toast">✔ Copiado al portapapeles</div>
428
- <footer class="footer">
429
- Forged by <a href="https://ingcrea.com" target="_blank">INGENIERÍA CREATIVA Y DESARROLLOS TECNOLÓGICOS S.A.S.</a> |
430
- <a href="https://github.com/ingcrea/aumic-tailwind-killer" target="_blank">View on GitHub</a>
431
- </footer>
432
-
433
- <script>
434
- // --- MOTOR CANVAS: DONUT CHART ANIMADO ---
435
- const chartData = ${JSON.stringify(chartData)};
436
- const ctxChart = document.getElementById('donutCanvas').getContext('2d');
437
- let totalVal = chartData.reduce((acc, d) => acc + d.value, 0);
438
- if(totalVal === 0) totalVal = 1;
439
- let animationProgress = 0;
440
- let hoveredIndex = -1;
441
- let mousePos = {x: 0, y: 0};
442
-
443
- function drawDonut() {
444
- ctxChart.clearRect(0, 0, 220, 220);
445
- const cx = 110, cy = 110;
446
- const baseRadius = 80;
447
- const innerRadius = 55;
448
- let currentAngle = -Math.PI / 2;
449
-
450
- for (let i = 0; i < chartData.length; i++) {
451
- const d = chartData[i];
452
- if(d.value === 0) continue;
453
- const sliceAngle = (d.value / totalVal) * (Math.PI * 2) * animationProgress;
454
- const isHovered = (i === hoveredIndex);
455
- const outerRadius = isHovered ? baseRadius + 10 : baseRadius;
456
-
457
- ctxChart.beginPath();
458
- ctxChart.arc(cx, cy, outerRadius, currentAngle, currentAngle + sliceAngle);
459
- ctxChart.arc(cx, cy, innerRadius, currentAngle + sliceAngle, currentAngle, true);
460
- ctxChart.closePath();
461
-
462
- ctxChart.fillStyle = d.color;
463
- // Hover glow effect
464
- if (isHovered) {
465
- ctxChart.shadowBlur = 15;
466
- ctxChart.shadowColor = d.color;
467
- } else {
468
- ctxChart.shadowBlur = 0;
469
- }
470
- ctxChart.fill();
471
- ctxChart.shadowBlur = 0; // reset
472
-
473
- // Store angles for hit detection
474
- d.startAngle = currentAngle;
475
- d.endAngle = currentAngle + sliceAngle;
476
- currentAngle += sliceAngle;
477
- }
478
-
479
- // Draw tooltip
480
- if (hoveredIndex > -1 && animationProgress > 0.9) {
481
- const d = chartData[hoveredIndex];
482
- const pct = ((d.value / totalVal) * 100).toFixed(1) + '%';
483
-
484
- ctxChart.fillStyle = 'rgba(15, 23, 42, 0.9)';
485
- ctxChart.strokeStyle = d.color;
486
- ctxChart.lineWidth = 1;
487
- ctxChart.beginPath();
488
- ctxChart.roundRect(mousePos.x + 15, mousePos.y - 30, 90, 45, 6);
489
- ctxChart.fill();
490
- ctxChart.stroke();
491
-
492
- ctxChart.fillStyle = '#fff';
493
- ctxChart.font = 'bold 12px Inter';
494
- ctxChart.fillText(pct, mousePos.x + 25, mousePos.y - 10);
495
- ctxChart.fillStyle = '#cbd5e1';
496
- ctxChart.font = '10px Inter';
497
- ctxChart.fillText(d.value + ' items', mousePos.x + 25, mousePos.y + 5);
498
- }
499
- }
500
-
501
- function animateDonut() {
502
- if (animationProgress < 1) {
503
- animationProgress += 0.04;
504
- drawDonut();
505
- requestAnimationFrame(animateDonut);
506
- } else {
507
- drawDonut();
508
- }
509
- }
510
- animateDonut();
511
-
512
- document.getElementById('donutCanvas').addEventListener('mousemove', (e) => {
513
- const rect = e.target.getBoundingClientRect();
514
- const x = e.clientX - rect.left;
515
- const y = e.clientY - rect.top;
516
- mousePos = {x, y};
517
-
518
- const cx = 110, cy = 110;
519
- const dx = x - cx;
520
- const dy = y - cy;
521
- const dist = Math.sqrt(dx*dx + dy*dy);
522
- let angle = Math.atan2(dy, dx);
523
- if (angle < -Math.PI / 2) angle += Math.PI * 2;
524
-
525
- let found = -1;
526
- if (dist >= 55 && dist <= 90) {
527
- for (let i = 0; i < chartData.length; i++) {
528
- if (chartData[i].value === 0) continue;
529
- let start = chartData[i].startAngle;
530
- let end = chartData[i].endAngle;
531
- // Normalize angles relative to -PI/2
532
- let nAngle = angle;
533
- if (start > Math.PI && nAngle < 0) nAngle += Math.PI * 2;
534
- if (nAngle >= start && nAngle <= end) {
535
- found = i; break;
536
- }
537
- }
538
- }
539
-
540
- if (found !== hoveredIndex) {
541
- hoveredIndex = found;
542
- document.getElementById('donutCanvas').style.cursor = found > -1 ? 'pointer' : 'default';
543
- drawDonut();
544
- } else if (found > -1) {
545
- drawDonut(); // redraw tooltip position
546
- }
547
- });
548
- document.getElementById('donutCanvas').addEventListener('mouseout', () => {
549
- hoveredIndex = -1;
550
- drawDonut();
551
- });
552
-
553
-
554
- // --- MOTOR CANVAS: FONDO DE NODOS AUM-IC ---
555
- const canvas = document.getElementById('bgCanvas');
556
- const ctx = canvas.getContext('2d');
557
- let width, height;
558
- let particles = [];
559
- const mouse = { x: null, y: null };
560
-
561
- function initBackground() {
562
- width = canvas.width = window.innerWidth;
563
- height = canvas.height = window.innerHeight;
564
- particles = [];
565
- const numParticles = Math.min(100, Math.floor((width * height) / 15000));
566
-
567
- const colors = ['#22d3ee', '#a78bfa', '#34d399'];
568
- for (let i = 0; i < numParticles; i++) {
569
- particles.push({
570
- x: Math.random() * width,
571
- y: Math.random() * height,
572
- vx: (Math.random() - 0.5) * 1.5,
573
- vy: (Math.random() - 0.5) * 1.5,
574
- radius: Math.random() * 2 + 1,
575
- color: colors[Math.floor(Math.random() * colors.length)]
576
- });
577
- }
578
- }
579
- window.addEventListener('resize', initBackground);
580
- window.addEventListener('mousemove', (e) => { mouse.x = e.clientX; mouse.y = e.clientY; });
581
- window.addEventListener('mouseout', () => { mouse.x = null; mouse.y = null; });
582
-
583
- function animateBackground() {
584
- ctx.clearRect(0, 0, width, height);
585
-
586
- // Update and draw particles
587
- for (let i = 0; i < particles.length; i++) {
588
- const p = particles[i];
589
- p.x += p.vx;
590
- p.y += p.vy;
591
- if (p.x < 0 || p.x > width) p.vx *= -1;
592
- if (p.y < 0 || p.y > height) p.vy *= -1;
593
-
594
- ctx.beginPath();
595
- ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
596
- ctx.fillStyle = p.color;
597
- ctx.fill();
598
- }
599
-
600
- // Connect particles
601
- for (let i = 0; i < particles.length; i++) {
602
- for (let j = i + 1; j < particles.length; j++) {
603
- const p1 = particles[i], p2 = particles[j];
604
- const dx = p1.x - p2.x, dy = p1.y - p2.y;
605
- const dist = Math.sqrt(dx*dx + dy*dy);
606
- if (dist < 120) {
607
- ctx.beginPath();
608
- ctx.strokeStyle = \`rgba(0, 229, 255, \${0.15 - dist/120 * 0.15})\`;
609
- ctx.lineWidth = 1.2;
610
- ctx.moveTo(p1.x, p1.y);
611
- ctx.lineTo(p2.x, p2.y);
612
- ctx.stroke();
613
- }
614
- }
615
-
616
- // Connect to mouse
617
- if (mouse.x != null) {
618
- const dx = particles[i].x - mouse.x, dy = particles[i].y - mouse.y;
619
- const dist = Math.sqrt(dx*dx + dy*dy);
620
- if (dist < 150) {
621
- ctx.beginPath();
622
- ctx.strokeStyle = \`rgba(167, 139, 250, \${0.2 - dist/150 * 0.2})\`;
623
- ctx.lineWidth = 2.0;
624
- ctx.moveTo(particles[i].x, particles[i].y);
625
- ctx.lineTo(mouse.x, mouse.y);
626
- ctx.stroke();
627
- }
628
- }
629
- }
630
- requestAnimationFrame(animateBackground);
631
- }
632
- initBackground();
633
- animateBackground();
634
-
635
-
636
- // --- LOGICA DE TABLA (PAGINADOR & ORDEN) ---
637
- let sortAsc = false;
638
- let currentPage = 1;
639
- let pageSize = 20;
640
- let filteredRows = [];
641
- let allRows = [];
642
-
643
- window.onload = () => {
644
- allRows = Array.from(document.querySelectorAll('#tbody tr'));
645
- filteredRows = [...allRows];
646
- sortAsc = true;
647
- sortTable(2, 'num');
648
- };
649
-
650
- function changePageSize(val) {
651
- if (val === 'all') {
652
- pageSize = filteredRows.length > 0 ? filteredRows.length : 10000;
653
- } else {
654
- pageSize = parseInt(val);
655
- }
656
- currentPage = 1;
657
- renderTable();
658
- }
659
-
660
- function renderTable() {
661
- const tbody = document.getElementById('tbody');
662
- tbody.innerHTML = '';
663
- const start = (currentPage - 1) * pageSize;
664
- const end = start + pageSize;
665
- const pageRows = filteredRows.slice(start, end);
666
- pageRows.forEach(r => tbody.appendChild(r));
667
-
668
- const totalPages = Math.ceil(filteredRows.length / pageSize) || 1;
669
- document.getElementById('page-info').textContent = \`Mostrando \${start+1} - \${Math.min(end, filteredRows.length)} de \${filteredRows.length} registros\`;
670
-
671
- renderPaginationControls(totalPages);
672
- }
673
-
674
- function renderPaginationControls(totalPages) {
675
- const cont = document.getElementById('pag-controls');
676
- cont.innerHTML = '';
677
- if (totalPages <= 1) return;
678
-
679
- // Prev Button
680
- const btnPrev = document.createElement('button');
681
- btnPrev.className = 'pag-btn';
682
- btnPrev.textContent = '«';
683
- btnPrev.disabled = currentPage === 1;
684
- btnPrev.onclick = () => { currentPage--; renderTable(); };
685
- cont.appendChild(btnPrev);
686
-
687
- // Page Numbers
688
- let startPage = Math.max(1, currentPage - 2);
689
- let endPage = Math.min(totalPages, currentPage + 2);
690
-
691
- if (startPage > 1) {
692
- cont.appendChild(createPageBtn(1));
693
- if (startPage > 2) { const d = document.createElement('span'); d.className='pag-dots'; d.textContent='...'; cont.appendChild(d); }
694
- }
695
-
696
- for (let i = startPage; i <= endPage; i++) {
697
- cont.appendChild(createPageBtn(i));
698
- }
699
-
700
- if (endPage < totalPages) {
701
- if (endPage < totalPages - 1) { const d = document.createElement('span'); d.className='pag-dots'; d.textContent='...'; cont.appendChild(d); }
702
- cont.appendChild(createPageBtn(totalPages));
703
- }
704
-
705
- // Next Button
706
- const btnNext = document.createElement('button');
707
- btnNext.className = 'pag-btn';
708
- btnNext.textContent = '»';
709
- btnNext.disabled = currentPage === totalPages;
710
- btnNext.onclick = () => { currentPage++; renderTable(); };
711
- cont.appendChild(btnNext);
712
- }
713
-
714
- function createPageBtn(num) {
715
- const btn = document.createElement('button');
716
- btn.className = 'pag-btn' + (num === currentPage ? ' active' : '');
717
- btn.textContent = num;
718
- btn.onclick = () => { currentPage = num; renderTable(); };
719
- return btn;
720
- }
721
-
722
- function sortTable(colIdx, type) {
723
- sortAsc = !sortAsc;
724
- filteredRows.sort((a, b) => {
725
- let valA, valB;
726
- if (type === 'num') {
727
- valA = parseInt(a.children[colIdx].getAttribute('data-occ') || '0');
728
- valB = parseInt(b.children[colIdx].getAttribute('data-occ') || '0');
729
- } else {
730
- valA = a.children[colIdx].textContent.trim();
731
- valB = b.children[colIdx].textContent.trim();
732
- }
733
- if (valA < valB) return sortAsc ? -1 : 1;
734
- if (valA > valB) return sortAsc ? 1 : -1;
735
- return 0;
736
- });
737
- currentPage = 1;
738
- renderTable();
739
- }
740
-
741
- let activeCat = 'all';
742
- let searchQuery = '';
743
-
744
- function filter(cat, btn) {
745
- document.querySelectorAll('.fbtn').forEach(b=>b.classList.remove('active'));
746
- if(btn) btn.classList.add('active');
747
- activeCat = cat;
748
- applyFilters();
749
- }
750
-
751
- function search(q) {
752
- searchQuery = q;
753
- applyFilters();
754
- }
755
-
756
- function applyFilters() {
757
- let regex = null;
758
- let lq = searchQuery.toLowerCase();
759
- try { if (searchQuery) regex = new RegExp(searchQuery, 'i'); } catch(e) {}
760
-
761
- filteredRows = allRows.filter(r => {
762
- const catMatch = (activeCat === 'all' || r.getAttribute('data-cat') === activeCat);
763
- if (!catMatch) return false;
764
-
765
- const txt = r.textContent;
766
- if (!searchQuery) return true;
767
- if (regex && regex.test(txt)) return true;
768
- return txt.toLowerCase().includes(lq);
769
- });
770
-
771
- currentPage = 1;
772
- renderTable();
773
- }
774
-
775
- function copyText(el) {
776
- const text = el.innerText;
777
- navigator.clipboard.writeText(text).then(() => {
778
- const toast = document.getElementById('toast');
779
- toast.classList.add('show');
780
- setTimeout(() => toast.classList.remove('show'), 2000);
781
- });
782
- }
783
-
784
- function exportCSV() {
785
- let csv = "Tailwind Original,AUM-IC Transmutada,Frecuencia,Nivel\\n";
786
- filteredRows.forEach(r => {
787
- const cols = r.querySelectorAll('td');
788
- const orig = cols[0].innerText;
789
- const aumic = cols[1].innerText;
790
- const occ = cols[2].querySelector('span').innerText;
791
- const lvl = cols[3].innerText;
792
- csv += String.fromCharCode(34) + orig + String.fromCharCode(34) + "," + String.fromCharCode(34) + aumic + String.fromCharCode(34) + "," + occ + "," + String.fromCharCode(34) + lvl + String.fromCharCode(34) + "\\n";
793
- });
794
- const blob = new Blob([csv], { type: 'text/csv' });
795
- const url = window.URL.createObjectURL(blob);
796
- const a = document.createElement('a');
797
- a.href = url;
798
- a.download = 'aumic-report.csv';
799
- a.click();
800
- }
801
-
802
- function exportMD() {
803
- let md = "| Tailwind Original | AUM-IC Transmutada | Frecuencia | Nivel |\\n|---|---|---|---|\\n";
804
- filteredRows.forEach(r => {
805
- const cols = r.querySelectorAll('td');
806
- const orig = cols[0].innerText.replace(/\|/g, '&#124;');
807
- const aumic = cols[1].innerText.replace(/\|/g, '&#124;');
808
- const occ = cols[2].querySelector('span').innerText;
809
- const lvl = cols[3].innerText;
810
- md += "| " + orig + " | " + aumic + " | " + occ + " | " + lvl + " |\\n";
811
- });
812
- const blob = new Blob([md], { type: 'text/markdown' });
813
- const url = window.URL.createObjectURL(blob);
814
- const a = document.createElement('a');
815
- a.href = url;
816
- a.download = 'aumic-report.md';
817
- a.click();
818
- }
819
-
820
- document.addEventListener('keydown', (e) => {
821
- if (e.key === '/' && document.activeElement.id !== 'searchInput') {
822
- e.preventDefault();
823
- document.getElementById('searchInput').focus();
824
- }
825
- });
826
-
168
+ const html = `<!DOCTYPE html>
169
+ <html lang="es"><head><meta charset="UTF-8"><title>AUM-IC · Dashboard C4I</title><style>
170
+ :root { --bg: #030712; --panel: rgba(15, 23, 42, 0.7); --panel-hover: #1e293b; --text: #e2e8f0; --muted: #64748b; --border: rgba(51, 65, 85, 0.6); --accent: #00e5ff; }
171
+ *{box-sizing:border-box;margin:0;padding:0}
172
+ body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;padding:2rem 3rem;position:relative;overflow-x:hidden;}
173
+ #bgCanvas { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -1; pointer-events: none; }
174
+ .content-wrapper { position: relative; z-index: 10; }
175
+ header{display:flex;align-items:center;justify-content:space-between;margin-bottom:2rem;padding-bottom:1.5rem;border-bottom:1px solid var(--border)}
176
+ header h1{font-size:1.8rem;font-weight:900;color:#fff;letter-spacing:-0.5px}header h1 span{color:var(--accent);text-shadow:0 0 10px rgba(0,229,255,0.4)}
177
+ .subtitle{font-size:0.85rem;color:var(--muted);margin-top:6px}.safe-tag{background:rgba(0,229,255,0.1);border:1px solid rgba(0,229,255,0.3);color:var(--accent);font-size:0.75rem;padding:6px 16px;border-radius:999px;font-weight:700;box-shadow:0 0 15px rgba(0,229,255,0.1)}
178
+ .dashboard-grid { display: grid; grid-template-columns: 320px 1fr; gap: 2rem; }
179
+ .sidebar { display: flex; flex-direction: column; gap: 1.5rem; }
180
+ .main-view { display: flex; flex-direction: column; gap: 1.5rem; }
181
+
182
+ /* Panels */
183
+ .panel { background: var(--panel); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); padding: 1.5rem; border-radius: 16px; border: 1px solid var(--border); box-shadow: 0 10px 30px rgba(0,0,0,0.3); }
184
+ .panel h2 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 1px; color: var(--muted); margin-bottom: 1rem; }
185
+
186
+ /* Canvas Chart */
187
+ .chart-container { display: flex; justify-content: center; align-items: center; margin-bottom: 1rem; position: relative; height: 200px; }
188
+ .chart-center-overlay { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; pointer-events: none; }
189
+ .chart-center-overlay .score { font-size: 2.5rem; font-weight: 900; color: ${gradeColor}; line-height: 1; text-shadow: 0 0 15px ${gradeColor}44; }
190
+ .chart-center-overlay .score-lbl { font-size: 0.6rem; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; margin-top: 4px; }
191
+ .legend { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; font-size: 0.75rem; }
192
+ .leg-item { display: flex; align-items: center; gap: 6px; }
193
+ .leg-color { width: 10px; height: 10px; border-radius: 50%; }
194
+
195
+ /* Hero Metrics */
196
+ .metrics-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }
197
+ .metric { background: rgba(0,0,0,0.3); padding: 1.2rem; border-radius: 12px; border: 1px solid rgba(255,255,255,0.05); backdrop-filter: blur(5px); }
198
+ .metric h3 { font-size: 1.8rem; font-weight: 900; color: #fff; line-height: 1; margin-bottom: 4px; }
199
+ .metric p { font-size: 0.7rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
200
+ .highlight { color: #34d399 !important; text-shadow: 0 0 10px rgba(52,211,153,0.4); }
201
+ .highlight-psi { color: #facc15 !important; }
202
+
203
+ /* Criminals */
204
+ .criminal { display: flex; gap: 1rem; align-items: center; padding: 10px; background: rgba(0,0,0,0.2); border-radius: 8px; margin-bottom: 8px; }
205
+ .c-rank { font-size: 1.2rem; font-weight: 900; color: #ef4444; width: 30px; text-align: center; }
206
+ .c-info { flex: 1; min-width: 0; }
207
+ .c-class { font-family: 'Fira Code', monospace; font-size: 0.75rem; color: #f472b6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 4px; }
208
+ .c-stats { font-size: 0.65rem; color: var(--muted); }
209
+
210
+ /* Table Controls */
211
+ .toolbar{display:flex;gap:0.5rem;align-items:center;flex-wrap:wrap; margin-bottom: 1.5rem;}
212
+ .fbtn{background:rgba(0,0,0,0.2);border:1px solid var(--border);color:#94a3b8;font-size:0.75rem;padding:8px 16px;border-radius:8px;cursor:pointer;transition:all 0.2s;font-weight:500}
213
+ .fbtn:hover,.fbtn.active{color:#fff;border-color:var(--accent);background:rgba(0,229,255,0.1);box-shadow:0 0 10px rgba(0,229,255,0.2)}
214
+ .search{margin-left:auto;background:rgba(0,0,0,0.2);border:1px solid var(--border);color:#fff;font-size:0.8rem;padding:8px 16px;border-radius:8px;outline:none;width:220px;transition:all 0.2s}
215
+ .search:focus{border-color:var(--accent);box-shadow:0 0 0 2px rgba(0,229,255,0.2)}
216
+ .page-size-sel { background: rgba(15, 23, 42, 0.9); color: #fff; background:rgba(0,0,0,0.2);border:1px solid var(--border);color:#fff;font-size:0.75rem;padding:8px;border-radius:8px;outline:none; cursor:pointer;}
217
+
218
+ /* Table */
219
+ .table-container{border-radius:12px;border:1px solid var(--border);overflow:hidden;background:rgba(0,0,0,0.2);}
220
+ table{width:100%;border-collapse:collapse;font-size:0.8rem}
221
+ th{background:rgba(0,0,0,0.3);color:var(--muted);text-align:left;padding:12px 16px;border-bottom:1px solid var(--border);font-weight:600;text-transform:uppercase;letter-spacing:1px;font-size:0.7rem;cursor:pointer;user-select:none}
222
+ th:hover{color:#fff}
223
+ td{padding:12px 16px;border-bottom:1px solid rgba(255,255,255,0.02);vertical-align:middle;transition:background 0.15s}
224
+ tr:hover td{background:rgba(255,255,255,0.05)}
225
+ .tw-cell{font-family:'Fira Code',Consolas,monospace;color:#f472b6;font-size:0.75rem;max-width:300px;word-break:break-all}
226
+ .aumic-cell{font-family:'Fira Code',Consolas,monospace;color:#38bdf8;font-size:0.75rem;white-space:nowrap;font-weight:600}
227
+ .occ-cell{display:flex;align-items:center;gap:10px;font-family:'Fira Code',monospace;font-weight:bold;color:#fff}
228
+ .occ-bar{height:6px;border-radius:3px;opacity:0.8}
229
+ .badge{font-size:0.65rem;padding:4px 10px;border-radius:999px;border:1px solid;font-weight:800;white-space:nowrap;text-transform:uppercase;letter-spacing:0.5px}
230
+
231
+ /* Pagination */
232
+ .pagination { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; background: rgba(0,0,0,0.3); border-top: 1px solid var(--border); }
233
+ .pag-controls { display: flex; gap: 6px; align-items: center; background: rgba(15,23,42,0.6); padding: 4px; border-radius: 8px; border: 1px solid var(--border); }
234
+ .pag-btn { background: transparent; border: none; color: var(--muted); padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 0.75rem; transition: 0.2s; min-width: 32px; text-align: center; font-weight: 600; }
235
+ .pag-btn:hover:not(:disabled) { background: rgba(255,255,255,0.05); color: #fff; }
236
+ .pag-btn.active { background: rgba(0,229,255,0.15); color: var(--accent); box-shadow: inset 0 0 0 1px rgba(0,229,255,0.3); }
237
+ .pag-btn:disabled { opacity: 0.3; cursor: not-allowed; }
238
+ .pag-info { font-size: 0.75rem; color: var(--muted); }
239
+ .pag-dots { color: var(--muted); font-size: 0.75rem; font-weight: bold; padding: 0 4px; }
240
+
241
+ /* Footer */
242
+ .footer { position: relative; bottom: 0; left: 0; width: 100%; text-align: center; padding: 20px; font-size: 0.75rem; color: var(--muted); border-top: 1px solid rgba(255,255,255,0.05); background: rgba(0,0,0,0.3); backdrop-filter: blur(5px); margin-top: auto; }
243
+ .footer a { color: var(--accent); text-decoration: none; font-weight: 600; transition: color 0.2s; }
244
+ .footer a:hover { color: #fff; text-shadow: 0 0 10px var(--accent); }
245
+ .page-size-sel option { background: #0f172a; color: #fff; }
246
+ /* Export & Toast */
247
+ .export-group-top { display: flex; gap: 8px; margin-left: auto; align-items: center; } .export-group-top .fbtn { background: rgba(0,229,255,0.05); }
248
+ .toast { position: fixed; bottom: 20px; right: 20px; background: rgba(0, 229, 255, 0.15); border: 1px solid var(--accent); color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 0.8rem; font-weight: 600; box-shadow: 0 5px 15px rgba(0,229,255,0.2); backdrop-filter: blur(5px); transform: translateY(100px); opacity: 0; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); z-index: 9999; }
249
+ .toast.show { transform: translateY(0); opacity: 1; }
250
+ .copyable { cursor: pointer; transition: color 0.2s; position: relative; }
251
+ .copyable:hover { color: #fff; text-shadow: 0 0 8px rgba(255,255,255,0.5); }
252
+ .copyable::after { content: 'Copiar'; position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.8); color: #fff; font-size: 0.6rem; padding: 2px 6px; border-radius: 4px; opacity: 0; pointer-events: none; transition: opacity 0.2s; }
253
+ .copyable:hover::after { opacity: 1; }
254
+
255
+ /* Sticky Header */
256
+ .table-container { overflow-x: auto; }
257
+
258
+
259
+ /* Print PDF (Mesa Directiva) */
260
+ @media print {
261
+ body { background: #fff !important; color: #000 !important; padding: 0 !important; font-size: 10pt; }
262
+ #bgCanvas, .toolbar, .pagination, .safe-tag, .export-group-top, .footer, .toast { display: none !important; }
263
+ .content-wrapper { padding: 0 !important; max-width: 100% !important; }
264
+ header { border-bottom: 2px solid #000; padding-bottom: 10px; margin-bottom: 20px; flex-direction: column !important; align-items: flex-start !important; }
265
+ header h1 { color: #000 !important; text-shadow: none !important; margin: 0; }
266
+ header h1 span { color: #000 !important; }
267
+
268
+ .dashboard-grid { display: block !important; }
269
+ .sidebar { display: flex !important; flex-direction: row !important; gap: 20px !important; margin-bottom: 20px !important; page-break-inside: avoid; }
270
+ .sidebar .panel { flex: 1; border: 1px solid #cbd5e1 !important; padding: 15px !important; border-radius: 8px !important; }
271
+
272
+ .panel { background: none !important; border: none !important; box-shadow: none !important; padding: 0 !important; }
273
+
274
+ .metrics-grid { display: grid !important; grid-template-columns: repeat(3, 1fr) !important; gap: 10px !important; margin-bottom: 20px !important; page-break-inside: avoid; }
275
+ .metric { background: #f8fafc !important; border: 1px solid #cbd5e1 !important; padding: 10px !important; border-radius: 8px !important; }
276
+ .metric h3 { color: #000 !important; text-shadow: none !important; font-size: 1.2rem; margin-bottom: 5px; }
277
+ .metric p { color: #333 !important; font-size: 0.8rem; }
278
+
279
+ .criteria-box { background: #f8fafc !important; border: 1px solid #cbd5e1 !important; color: #000 !important; }
280
+ .criteria-box strong { color: #000 !important; }
281
+ .criteria-box span { color: #000 !important; font-weight: bold; }
282
+
283
+ .chart-center-overlay .score { color: #000 !important; text-shadow: none !important; }
284
+ .chart-center-overlay .score-lbl { color: #333 !important; }
285
+
286
+ .leg-item { color: #000 !important; }
287
+
288
+ .table-container { max-height: none !important; border: none !important; overflow: visible !important; }
289
+ table { border-collapse: collapse !important; width: 100% !important; page-break-inside: auto; }
290
+ tr { page-break-inside: avoid; page-break-after: auto; }
291
+ th, td { border: 1px solid #cbd5e1 !important; color: #000 !important; padding: 6px !important; font-size: 9pt !important; }
292
+ th { background: #f1f5f9 !important; color: #000 !important; font-weight: bold; }
293
+ .tw-cell, .aumic-cell { color: #000 !important; font-weight: normal; }
294
+ .occ-bar { background: #94a3b8 !important; }
295
+
296
+ @page { margin: 1cm; size: auto; }
297
+ }
298
+ }
299
+ </style></head><body style="display: flex; flex-direction: column;">
300
+
301
+ <canvas id="bgCanvas"></canvas>
302
+
303
+ <div class="content-wrapper">
304
+ <header>
305
+ <div><h1>⚔️ <span>AUM-IC</span> · Centro de Inteligencia</h1>
306
+ <p class="subtitle">Análisis Forense completado exitosamente sin mutación en disco.</p></div>
307
+ <div class="export-group-top" style="display:flex; gap:8px; margin-left: 20px;">
308
+ <button class="fbtn" onclick="exportCSV()">⬇️ CSV</button><button class="fbtn" onclick="exportMD()">⬇️ MD</button><button class="fbtn" onclick="window.print()">🖨️ PDF</button>
309
+ </div>
310
+ <div class="safe-tag" style="align-items: flex-end; display: flex; flex-direction: column;">
311
+ <span>${isSimulate ? 'MODO DRY-RUN ACTIVO' : 'MUTACIÓN AUM-IC COMPLETADA'}</span>
312
+ <span style="font-size: 0.65rem; opacity: 0.8; margin-top: 2px;">v${pkgVersion}</span>
313
+ </div>
314
+ </header>
315
+
316
+ <main class="dashboard-grid">
317
+ <aside class="sidebar">
318
+ <article class="panel">
319
+ <h2>Mapeo Estructural</h2>
320
+ <div class="chart-container">
321
+ <canvas id="donutCanvas" width="220" height="220"></canvas>
322
+ <div class="chart-center-overlay">
323
+ <div class="score">${grade}</div>
324
+ <div class="score-lbl">Rank</div>
325
+ </div>
326
+ </div>
327
+ <div class="legend">
328
+ <div class="leg-item"><div class="leg-color" style="background:#22d3ee"></div>Átomos (${counts.atoms})</div>
329
+ <div class="leg-item"><div class="leg-color" style="background:#a78bfa"></div>Moléculas (${counts.molecules})</div>
330
+ <div class="leg-item"><div class="leg-color" style="background:#34d399"></div>Organismos (${counts.organisms})</div>
331
+ <div class="leg-item"><div class="leg-color" style="background:#fb923c"></div>Ecosistemas (${counts.ecosystems})</div>
332
+ <div class="leg-item"><div class="leg-color" style="background:#facc15"></div>Galaxias (${counts.galaxies})</div>
333
+ </div>
334
+ <div class="criteria-box" style="margin-top: 1.5rem; padding: 1rem; background: rgba(0,0,0,0.2); border-radius: 8px; border: 1px solid var(--border); font-size: 0.75rem; color: var(--muted); line-height: 1.5;">
335
+ <strong style="color: #fff; margin-bottom: 0.5rem; display: block;">Criterio de Evaluación AUM-IC</strong>
336
+ <ul style="margin: 0; padding-left: 1rem; list-style: square;">
337
+ <li><span style="color:#22d3ee">Átomos:</span> Utilidades base simples (p-4, m-2, flex).</li>
338
+ <li><span style="color:#a78bfa">Moléculas:</span> Estilos condicionales y variantes (hover:, focus:).</li>
339
+ <li><span style="color:#34d399">Organismos:</span> Cadenas largas y componentes estructurales.</li>
340
+ <li><span style="color:#fb923c">Ecosistemas:</span> Combinaciones de Layouts complejos (Grid/Flex).</li>
341
+ <li><span style="color:#facc15">Galaxias:</span> Declaraciones globales masivas o de alta densidad.</li>
342
+ </ul>
343
+ <div style="margin-top: 0.8rem; padding-top: 0.8rem; border-top: 1px solid rgba(255,255,255,0.1);">
344
+ <strong style="color: #fff; display:block; margin-bottom: 0.3rem;">🏆 Rank (Score) & Eficiencia</strong>
345
+ <div style="margin-bottom: 0.6rem; color: #94a3b8;">La <b>Eficiencia de Refactorización</b> mide el porcentaje exacto de peso (bytes) que AUM-IC logró purgar del DOM frente al HTML inflado original.</div>
346
+ <div style="display: flex; gap: 6px; flex-wrap: wrap; font-size: 0.7rem;">
347
+ <span style="background: rgba(250,204,21,0.15); color:#facc15; padding: 3px 6px; border-radius: 4px; border: 1px solid #facc1544;"><b>S</b> ≥ 70%</span>
348
+ <span style="background: rgba(52,211,153,0.15); color:#34d399; padding: 3px 6px; border-radius: 4px; border: 1px solid #34d39944;"><b>A</b> ≥ 50%</span>
349
+ <span style="background: rgba(34,211,238,0.15); color:#22d3ee; padding: 3px 6px; border-radius: 4px; border: 1px solid #22d3ee44;"><b>B</b> ≥ 30%</span>
350
+ <span style="background: rgba(167,139,250,0.15); color:#a78bfa; padding: 3px 6px; border-radius: 4px; border: 1px solid #a78bfa44;"><b>C</b> ≥ 15%</span>
351
+ <span style="background: rgba(239,68,68,0.15); color:#ef4444; padding: 3px 6px; border-radius: 4px; border: 1px solid #ef444444;"><b>D</b> < 15%</span>
352
+ </div>
353
+ </div>
354
+ </div>
355
+ </article>
356
+
357
+ <article class="panel">
358
+ <h2>☠️ Top 5 Ofensores del DOM</h2>
359
+ ${top5}
360
+ </article>
361
+ </aside>
362
+
363
+ <section class="main-view">
364
+ <div class="metrics-grid">
365
+ <div class="metric">
366
+ <h3 class="highlight">🔥 ${savedKB} KB</h3>
367
+ <p>Peso/Tamaño Estimado Salvado</p>
368
+ </div>
369
+ <div class="metric">
370
+ <h3 class="highlight-psi">🚀 +${psiImpact} pts</h3>
371
+ <p>Impacto Estimado Google PSI</p>
372
+ </div>
373
+ <div class="metric">
374
+ <h3>${efficiency}%</h3>
375
+ <p>Eficiencia de Refactorización</p>
376
+ </div>
377
+ <div class="metric">
378
+ <h3>${mapping.size}</h3>
379
+ <p>Clases Únicas Tailwind</p>
380
+ </div>
381
+ <div class="metric">
382
+ <h3>${totalOccurrences}</h3>
383
+ <p>Instancias Totales Detectadas</p>
384
+ </div>
385
+ <div class="metric">
386
+ <h3>${totalOccurrences - mapping.size}</h3>
387
+ <p>Redundancia Estructural (Repetidas)</p>
388
+ </div>
389
+ </div>
390
+
391
+ <article class="panel">
392
+ <nav class="toolbar">
393
+ <button class="fbtn active" onclick="filter('all',this)">Todos</button>
394
+ <button class="fbtn" onclick="filter('atoms',this)">Átomos</button>
395
+ <button class="fbtn" onclick="filter('molecules',this)">Moléculas</button>
396
+ <button class="fbtn" onclick="filter('organisms',this)">Organismos</button>
397
+ <button class="fbtn" onclick="filter('ecosystems',this)">Ecosistemas</button>
398
+ <button class="fbtn" onclick="filter('galaxies',this)">Galaxias</button>
399
+
400
+ <input class="search" type="text" id="searchInput" placeholder="Buscar clase (Regex)..." oninput="search(this.value)">
401
+ <select class="page-size-sel" id="pageSizeSelect" onchange="changePageSize(this.value)">
402
+ <option value="20">20 por pág</option>
403
+ <option value="50">50 por pág</option>
404
+ <option value="100">100 por pág</option>
405
+ <option value="all">TODOS</option>
406
+ </select>
407
+ </nav>
408
+ <section class="table-container">
409
+ <table id="table">
410
+ <thead><tr>
411
+ <th onclick="sortTable(0, 'str')">Clase Original (Tailwind) ↕</th>
412
+ <th onclick="sortTable(1, 'str')">Clase AUM-IC Transmutada ↕</th>
413
+ <th onclick="sortTable(2, 'num')">Frecuencia ↕</th>
414
+ <th onclick="sortTable(3, 'str')">Nivel ↕</th>
415
+ </tr></thead>
416
+ <tbody id="tbody">${rows.join('')}</tbody>
417
+ </table>
418
+ <div class="pagination">
419
+ <span class="pag-info" id="page-info"></span>
420
+ <div class="pag-controls" id="pag-controls"></div>
421
+ </div>
422
+ </section>
423
+ </article>
424
+ </section>
425
+ </main>
426
+ </div>
427
+
428
+ <div id="toast" class="toast">✔ Copiado al portapapeles</div>
429
+ <footer class="footer">
430
+ Forged by <a href="https://ingcrea.com" target="_blank">INGENIERÍA CREATIVA Y DESARROLLOS TECNOLÓGICOS S.A.S.</a> |
431
+ <a href="https://github.com/ingcrea/aumic-tailwind-killer" target="_blank">View on GitHub</a>
432
+ </footer>
433
+
434
+ <script>
435
+ // --- MOTOR CANVAS: DONUT CHART ANIMADO ---
436
+ const chartData = ${JSON.stringify(chartData)};
437
+ const ctxChart = document.getElementById('donutCanvas').getContext('2d');
438
+ let totalVal = chartData.reduce((acc, d) => acc + d.value, 0);
439
+ if(totalVal === 0) totalVal = 1;
440
+ let animationProgress = 0;
441
+ let hoveredIndex = -1;
442
+ let mousePos = {x: 0, y: 0};
443
+
444
+ function drawDonut() {
445
+ ctxChart.clearRect(0, 0, 220, 220);
446
+ const cx = 110, cy = 110;
447
+ const baseRadius = 80;
448
+ const innerRadius = 55;
449
+ let currentAngle = -Math.PI / 2;
450
+
451
+ for (let i = 0; i < chartData.length; i++) {
452
+ const d = chartData[i];
453
+ if(d.value === 0) continue;
454
+ const sliceAngle = (d.value / totalVal) * (Math.PI * 2) * animationProgress;
455
+ const isHovered = (i === hoveredIndex);
456
+ const outerRadius = isHovered ? baseRadius + 10 : baseRadius;
457
+
458
+ ctxChart.beginPath();
459
+ ctxChart.arc(cx, cy, outerRadius, currentAngle, currentAngle + sliceAngle);
460
+ ctxChart.arc(cx, cy, innerRadius, currentAngle + sliceAngle, currentAngle, true);
461
+ ctxChart.closePath();
462
+
463
+ ctxChart.fillStyle = d.color;
464
+ // Hover glow effect
465
+ if (isHovered) {
466
+ ctxChart.shadowBlur = 15;
467
+ ctxChart.shadowColor = d.color;
468
+ } else {
469
+ ctxChart.shadowBlur = 0;
470
+ }
471
+ ctxChart.fill();
472
+ ctxChart.shadowBlur = 0; // reset
473
+
474
+ // Store angles for hit detection
475
+ d.startAngle = currentAngle;
476
+ d.endAngle = currentAngle + sliceAngle;
477
+ currentAngle += sliceAngle;
478
+ }
479
+
480
+ // Draw tooltip
481
+ if (hoveredIndex > -1 && animationProgress > 0.9) {
482
+ const d = chartData[hoveredIndex];
483
+ const pct = ((d.value / totalVal) * 100).toFixed(1) + '%';
484
+
485
+ ctxChart.fillStyle = 'rgba(15, 23, 42, 0.9)';
486
+ ctxChart.strokeStyle = d.color;
487
+ ctxChart.lineWidth = 1;
488
+ ctxChart.beginPath();
489
+ ctxChart.roundRect(mousePos.x + 15, mousePos.y - 30, 90, 45, 6);
490
+ ctxChart.fill();
491
+ ctxChart.stroke();
492
+
493
+ ctxChart.fillStyle = '#fff';
494
+ ctxChart.font = 'bold 12px Inter';
495
+ ctxChart.fillText(pct, mousePos.x + 25, mousePos.y - 10);
496
+ ctxChart.fillStyle = '#cbd5e1';
497
+ ctxChart.font = '10px Inter';
498
+ ctxChart.fillText(d.value + ' items', mousePos.x + 25, mousePos.y + 5);
499
+ }
500
+ }
501
+
502
+ function animateDonut() {
503
+ if (animationProgress < 1) {
504
+ animationProgress += 0.04;
505
+ drawDonut();
506
+ requestAnimationFrame(animateDonut);
507
+ } else {
508
+ drawDonut();
509
+ }
510
+ }
511
+ animateDonut();
512
+
513
+ document.getElementById('donutCanvas').addEventListener('mousemove', (e) => {
514
+ const rect = e.target.getBoundingClientRect();
515
+ const x = e.clientX - rect.left;
516
+ const y = e.clientY - rect.top;
517
+ mousePos = {x, y};
518
+
519
+ const cx = 110, cy = 110;
520
+ const dx = x - cx;
521
+ const dy = y - cy;
522
+ const dist = Math.sqrt(dx*dx + dy*dy);
523
+ let angle = Math.atan2(dy, dx);
524
+ if (angle < -Math.PI / 2) angle += Math.PI * 2;
525
+
526
+ let found = -1;
527
+ if (dist >= 55 && dist <= 90) {
528
+ for (let i = 0; i < chartData.length; i++) {
529
+ if (chartData[i].value === 0) continue;
530
+ let start = chartData[i].startAngle;
531
+ let end = chartData[i].endAngle;
532
+ // Normalize angles relative to -PI/2
533
+ let nAngle = angle;
534
+ if (start > Math.PI && nAngle < 0) nAngle += Math.PI * 2;
535
+ if (nAngle >= start && nAngle <= end) {
536
+ found = i; break;
537
+ }
538
+ }
539
+ }
540
+
541
+ if (found !== hoveredIndex) {
542
+ hoveredIndex = found;
543
+ document.getElementById('donutCanvas').style.cursor = found > -1 ? 'pointer' : 'default';
544
+ drawDonut();
545
+ } else if (found > -1) {
546
+ drawDonut(); // redraw tooltip position
547
+ }
548
+ });
549
+ document.getElementById('donutCanvas').addEventListener('mouseout', () => {
550
+ hoveredIndex = -1;
551
+ drawDonut();
552
+ });
553
+
554
+
555
+ // --- MOTOR CANVAS: FONDO DE NODOS AUM-IC ---
556
+ const canvas = document.getElementById('bgCanvas');
557
+ const ctx = canvas.getContext('2d');
558
+ let width, height;
559
+ let particles = [];
560
+ const mouse = { x: null, y: null };
561
+
562
+ function initBackground() {
563
+ width = canvas.width = window.innerWidth;
564
+ height = canvas.height = window.innerHeight;
565
+ particles = [];
566
+ const numParticles = Math.min(100, Math.floor((width * height) / 15000));
567
+
568
+ const colors = ['#22d3ee', '#a78bfa', '#34d399'];
569
+ for (let i = 0; i < numParticles; i++) {
570
+ particles.push({
571
+ x: Math.random() * width,
572
+ y: Math.random() * height,
573
+ vx: (Math.random() - 0.5) * 1.5,
574
+ vy: (Math.random() - 0.5) * 1.5,
575
+ radius: Math.random() * 2 + 1,
576
+ color: colors[Math.floor(Math.random() * colors.length)]
577
+ });
578
+ }
579
+ }
580
+ window.addEventListener('resize', initBackground);
581
+ window.addEventListener('mousemove', (e) => { mouse.x = e.clientX; mouse.y = e.clientY; });
582
+ window.addEventListener('mouseout', () => { mouse.x = null; mouse.y = null; });
583
+
584
+ function animateBackground() {
585
+ ctx.clearRect(0, 0, width, height);
586
+
587
+ // Update and draw particles
588
+ for (let i = 0; i < particles.length; i++) {
589
+ const p = particles[i];
590
+ p.x += p.vx;
591
+ p.y += p.vy;
592
+ if (p.x < 0 || p.x > width) p.vx *= -1;
593
+ if (p.y < 0 || p.y > height) p.vy *= -1;
594
+
595
+ ctx.beginPath();
596
+ ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
597
+ ctx.fillStyle = p.color;
598
+ ctx.fill();
599
+ }
600
+
601
+ // Connect particles
602
+ for (let i = 0; i < particles.length; i++) {
603
+ for (let j = i + 1; j < particles.length; j++) {
604
+ const p1 = particles[i], p2 = particles[j];
605
+ const dx = p1.x - p2.x, dy = p1.y - p2.y;
606
+ const dist = Math.sqrt(dx*dx + dy*dy);
607
+ if (dist < 120) {
608
+ ctx.beginPath();
609
+ ctx.strokeStyle = \`rgba(0, 229, 255, \${0.15 - dist/120 * 0.15})\`;
610
+ ctx.lineWidth = 1.2;
611
+ ctx.moveTo(p1.x, p1.y);
612
+ ctx.lineTo(p2.x, p2.y);
613
+ ctx.stroke();
614
+ }
615
+ }
616
+
617
+ // Connect to mouse
618
+ if (mouse.x != null) {
619
+ const dx = particles[i].x - mouse.x, dy = particles[i].y - mouse.y;
620
+ const dist = Math.sqrt(dx*dx + dy*dy);
621
+ if (dist < 150) {
622
+ ctx.beginPath();
623
+ ctx.strokeStyle = \`rgba(167, 139, 250, \${0.2 - dist/150 * 0.2})\`;
624
+ ctx.lineWidth = 2.0;
625
+ ctx.moveTo(particles[i].x, particles[i].y);
626
+ ctx.lineTo(mouse.x, mouse.y);
627
+ ctx.stroke();
628
+ }
629
+ }
630
+ }
631
+ requestAnimationFrame(animateBackground);
632
+ }
633
+ initBackground();
634
+ animateBackground();
635
+
636
+
637
+ // --- LOGICA DE TABLA (PAGINADOR & ORDEN) ---
638
+ let sortAsc = false;
639
+ let currentPage = 1;
640
+ let pageSize = 20;
641
+ let filteredRows = [];
642
+ let allRows = [];
643
+
644
+ window.onload = () => {
645
+ allRows = Array.from(document.querySelectorAll('#tbody tr'));
646
+ filteredRows = [...allRows];
647
+ sortAsc = true;
648
+ sortTable(2, 'num');
649
+ };
650
+
651
+ function changePageSize(val) {
652
+ if (val === 'all') {
653
+ pageSize = filteredRows.length > 0 ? filteredRows.length : 10000;
654
+ } else {
655
+ pageSize = parseInt(val);
656
+ }
657
+ currentPage = 1;
658
+ renderTable();
659
+ }
660
+
661
+ function renderTable() {
662
+ const tbody = document.getElementById('tbody');
663
+ tbody.innerHTML = '';
664
+ const start = (currentPage - 1) * pageSize;
665
+ const end = start + pageSize;
666
+ const pageRows = filteredRows.slice(start, end);
667
+ pageRows.forEach(r => tbody.appendChild(r));
668
+
669
+ const totalPages = Math.ceil(filteredRows.length / pageSize) || 1;
670
+ document.getElementById('page-info').textContent = \`Mostrando \${start+1} - \${Math.min(end, filteredRows.length)} de \${filteredRows.length} registros\`;
671
+
672
+ renderPaginationControls(totalPages);
673
+ }
674
+
675
+ function renderPaginationControls(totalPages) {
676
+ const cont = document.getElementById('pag-controls');
677
+ cont.innerHTML = '';
678
+ if (totalPages <= 1) return;
679
+
680
+ // Prev Button
681
+ const btnPrev = document.createElement('button');
682
+ btnPrev.className = 'pag-btn';
683
+ btnPrev.textContent = '«';
684
+ btnPrev.disabled = currentPage === 1;
685
+ btnPrev.onclick = () => { currentPage--; renderTable(); };
686
+ cont.appendChild(btnPrev);
687
+
688
+ // Page Numbers
689
+ let startPage = Math.max(1, currentPage - 2);
690
+ let endPage = Math.min(totalPages, currentPage + 2);
691
+
692
+ if (startPage > 1) {
693
+ cont.appendChild(createPageBtn(1));
694
+ if (startPage > 2) { const d = document.createElement('span'); d.className='pag-dots'; d.textContent='...'; cont.appendChild(d); }
695
+ }
696
+
697
+ for (let i = startPage; i <= endPage; i++) {
698
+ cont.appendChild(createPageBtn(i));
699
+ }
700
+
701
+ if (endPage < totalPages) {
702
+ if (endPage < totalPages - 1) { const d = document.createElement('span'); d.className='pag-dots'; d.textContent='...'; cont.appendChild(d); }
703
+ cont.appendChild(createPageBtn(totalPages));
704
+ }
705
+
706
+ // Next Button
707
+ const btnNext = document.createElement('button');
708
+ btnNext.className = 'pag-btn';
709
+ btnNext.textContent = '»';
710
+ btnNext.disabled = currentPage === totalPages;
711
+ btnNext.onclick = () => { currentPage++; renderTable(); };
712
+ cont.appendChild(btnNext);
713
+ }
714
+
715
+ function createPageBtn(num) {
716
+ const btn = document.createElement('button');
717
+ btn.className = 'pag-btn' + (num === currentPage ? ' active' : '');
718
+ btn.textContent = num;
719
+ btn.onclick = () => { currentPage = num; renderTable(); };
720
+ return btn;
721
+ }
722
+
723
+ function sortTable(colIdx, type) {
724
+ sortAsc = !sortAsc;
725
+ filteredRows.sort((a, b) => {
726
+ let valA, valB;
727
+ if (type === 'num') {
728
+ valA = parseInt(a.children[colIdx].getAttribute('data-occ') || '0');
729
+ valB = parseInt(b.children[colIdx].getAttribute('data-occ') || '0');
730
+ } else {
731
+ valA = a.children[colIdx].textContent.trim();
732
+ valB = b.children[colIdx].textContent.trim();
733
+ }
734
+ if (valA < valB) return sortAsc ? -1 : 1;
735
+ if (valA > valB) return sortAsc ? 1 : -1;
736
+ return 0;
737
+ });
738
+ currentPage = 1;
739
+ renderTable();
740
+ }
741
+
742
+ let activeCat = 'all';
743
+ let searchQuery = '';
744
+
745
+ function filter(cat, btn) {
746
+ document.querySelectorAll('.fbtn').forEach(b=>b.classList.remove('active'));
747
+ if(btn) btn.classList.add('active');
748
+ activeCat = cat;
749
+ applyFilters();
750
+ }
751
+
752
+ function search(q) {
753
+ searchQuery = q;
754
+ applyFilters();
755
+ }
756
+
757
+ function applyFilters() {
758
+ let regex = null;
759
+ let lq = searchQuery.toLowerCase();
760
+ try { if (searchQuery) regex = new RegExp(searchQuery, 'i'); } catch(e) {}
761
+
762
+ filteredRows = allRows.filter(r => {
763
+ const catMatch = (activeCat === 'all' || r.getAttribute('data-cat') === activeCat);
764
+ if (!catMatch) return false;
765
+
766
+ const txt = r.textContent;
767
+ if (!searchQuery) return true;
768
+ if (regex && regex.test(txt)) return true;
769
+ return txt.toLowerCase().includes(lq);
770
+ });
771
+
772
+ currentPage = 1;
773
+ renderTable();
774
+ }
775
+
776
+ function copyText(el) {
777
+ const text = el.innerText;
778
+ navigator.clipboard.writeText(text).then(() => {
779
+ const toast = document.getElementById('toast');
780
+ toast.classList.add('show');
781
+ setTimeout(() => toast.classList.remove('show'), 2000);
782
+ });
783
+ }
784
+
785
+ function exportCSV() {
786
+ let csv = "Tailwind Original,AUM-IC Transmutada,Frecuencia,Nivel\\n";
787
+ filteredRows.forEach(r => {
788
+ const cols = r.querySelectorAll('td');
789
+ const orig = cols[0].innerText;
790
+ const aumic = cols[1].innerText;
791
+ const occ = cols[2].querySelector('span').innerText;
792
+ const lvl = cols[3].innerText;
793
+ csv += String.fromCharCode(34) + orig + String.fromCharCode(34) + "," + String.fromCharCode(34) + aumic + String.fromCharCode(34) + "," + occ + "," + String.fromCharCode(34) + lvl + String.fromCharCode(34) + "\\n";
794
+ });
795
+ const blob = new Blob([csv], { type: 'text/csv' });
796
+ const url = window.URL.createObjectURL(blob);
797
+ const a = document.createElement('a');
798
+ a.href = url;
799
+ a.download = 'aumic-report.csv';
800
+ a.click();
801
+ }
802
+
803
+ function exportMD() {
804
+ let md = "| Tailwind Original | AUM-IC Transmutada | Frecuencia | Nivel |\\n|---|---|---|---|\\n";
805
+ filteredRows.forEach(r => {
806
+ const cols = r.querySelectorAll('td');
807
+ const orig = cols[0].innerText.replace(/\|/g, '&#124;');
808
+ const aumic = cols[1].innerText.replace(/\|/g, '&#124;');
809
+ const occ = cols[2].querySelector('span').innerText;
810
+ const lvl = cols[3].innerText;
811
+ md += "| " + orig + " | " + aumic + " | " + occ + " | " + lvl + " |\\n";
812
+ });
813
+ const blob = new Blob([md], { type: 'text/markdown' });
814
+ const url = window.URL.createObjectURL(blob);
815
+ const a = document.createElement('a');
816
+ a.href = url;
817
+ a.download = 'aumic-report.md';
818
+ a.click();
819
+ }
820
+
821
+ document.addEventListener('keydown', (e) => {
822
+ if (e.key === '/' && document.activeElement.id !== 'searchInput') {
823
+ e.preventDefault();
824
+ document.getElementById('searchInput').focus();
825
+ }
826
+ });
827
+
827
828
  </script></body></html>`;
828
829
  fs_extra_1.default.writeFileSync(reportPath, html);
829
830
  return reportPath;
@@ -1162,9 +1163,37 @@ async function run() {
1162
1163
  allUniqueTokens.add(t.trim()); });
1163
1164
  }
1164
1165
  const testHtml = Array.from(allUniqueTokens).map(t => `<div class="${t}"></div>`).join('\n');
1166
+ const configPath = require('fs').existsSync(require('path').resolve(TARGET_DIR, 'tailwind.config.js')) ? require('path').resolve(TARGET_DIR, 'tailwind.config.js') : (require('fs').existsSync(require('path').resolve(TARGET_DIR, 'tailwind.config.mjs')) ? require('path').resolve(TARGET_DIR, 'tailwind.config.mjs') : null);
1167
+ if (configPath) {
1168
+ try {
1169
+ const dynamicImport = new Function('modulePath', 'return import(modulePath)');
1170
+ userConfig = (await dynamicImport(require('url').pathToFileURL(configPath).href)).default || require(configPath);
1171
+ }
1172
+ catch (e) {
1173
+ try {
1174
+ userConfig = require(configPath);
1175
+ }
1176
+ catch (err) {
1177
+ try {
1178
+ const fsExt = require('fs');
1179
+ const tempPath = configPath + '.mjs';
1180
+ let raw = fsExt.readFileSync(configPath, 'utf8');
1181
+ raw = raw.replace(/module\.exports\s*=\s*/, 'export default ');
1182
+ fsExt.writeFileSync(tempPath, raw, 'utf8');
1183
+ const dynamicImport = new Function('modulePath', 'return import(modulePath)');
1184
+ userConfig = (await dynamicImport(require('url').pathToFileURL(tempPath).href)).default;
1185
+ fsExt.unlinkSync(tempPath);
1186
+ }
1187
+ catch (fatal) {
1188
+ console.log('[AUM-IC] Fallo al cargar Tailwind Config:', fatal.message);
1189
+ }
1190
+ }
1191
+ }
1192
+ }
1193
+ let mergedConfig = { content: [{ raw: testHtml, extension: 'html' }], corePlugins: { preflight: false }, theme: userConfig.theme || {} };
1165
1194
  let testPlugin;
1166
1195
  try {
1167
- testPlugin = require('tailwindcss')({ content: [{ raw: testHtml, extension: 'html' }], corePlugins: { preflight: false } });
1196
+ testPlugin = require('tailwindcss')(mergedConfig);
1168
1197
  }
1169
1198
  catch (e) {
1170
1199
  testPlugin = require('tailwindcss');
@@ -1182,6 +1211,36 @@ async function run() {
1182
1211
  }
1183
1212
  });
1184
1213
  spinner.succeed(`Filtro JIT completado: ${validTwClasses.size} utilidades nativas reconocidas.`);
1214
+ spinner.start('Analizando entorno y dependencias de Tailwind...');
1215
+ const envStatus = { version: 3, usesAstro: false, usesVite: false, usesPostcss: false, importsPreflight: false, importsUtilities: false, cssEntrypoint: '' };
1216
+ try {
1217
+ const p = require(path_1.default.join(TARGET_DIR, 'package.json'));
1218
+ const deps = { ...(p.dependencies || {}), ...(p.devDependencies || {}) };
1219
+ if (deps['tailwindcss'])
1220
+ envStatus.version = deps['tailwindcss'].startsWith('^4') || deps['tailwindcss'].startsWith('4') ? 4 : 3;
1221
+ else if (deps['@tailwindcss/vite'] || deps['@tailwindcss/postcss'])
1222
+ envStatus.version = 4;
1223
+ envStatus.usesAstro = !!deps['@astrojs/tailwind'];
1224
+ envStatus.usesVite = !!deps['@tailwindcss/vite'];
1225
+ envStatus.usesPostcss = !!deps['@tailwindcss/postcss'];
1226
+ }
1227
+ catch (e) { }
1228
+ const rootCssFiles = await (0, glob_1.glob)('**/*.{css,scss,sass,less,pcss}', { cwd: TARGET_DIR, absolute: true, ignore: ['node_modules/**', 'dist/**', 'public/**'] });
1229
+ for (const file of rootCssFiles) {
1230
+ const c = await fs_extra_1.default.readFile(file, 'utf-8');
1231
+ if (c.includes('@tailwind') || c.includes('tailwindcss')) {
1232
+ envStatus.cssEntrypoint = file;
1233
+ if (c.includes('@tailwind base') || c.includes('tailwindcss/preflight') || c.includes('@import "tailwindcss"'))
1234
+ envStatus.importsPreflight = true;
1235
+ if (c.includes('@tailwind utilities') || c.includes('tailwindcss/utilities') || c.includes('@import "tailwindcss"'))
1236
+ envStatus.importsUtilities = true;
1237
+ }
1238
+ }
1239
+ if (envStatus.usesAstro) {
1240
+ envStatus.importsPreflight = true;
1241
+ envStatus.importsUtilities = true;
1242
+ }
1243
+ spinner.succeed(picocolors_1.default.cyan(`Entorno JIT Dinámico: Tailwind v${envStatus.version} | Preflight: ${envStatus.importsPreflight ? 'Activo' : 'Inactivo'} | Integración: ${envStatus.usesVite ? 'Vite' : envStatus.usesAstro ? 'Astro' : 'PostCSS'}`));
1185
1244
  const classMapping = new Map();
1186
1245
  let customRules = '';
1187
1246
  const rcPath = path_1.default.join(TARGET_DIR, '.aumicrc.json');
@@ -1368,13 +1427,70 @@ async function run() {
1368
1427
  spinner.succeed(`CSS extranjero hackeado y reescrito a la doctrina ${aumicLink}.`);
1369
1428
  }
1370
1429
  else {
1371
- spinner.start(`Fase 4: Forzando JIT de Tailwind (v${twVersion})...`);
1372
- const virtualHtml = Array.from(classMapping.values()).map(map => `<div class="${map.tailwindOnlyArray.join(' ')}"></div>`).join('\n');
1430
+ spinner.start(`Fase 4: Ejecutando L1 Cache (DuckDB) y delegando L2 (JIT v${twVersion})...`);
1431
+ // --- L1 DUCKDB CACHE ---
1432
+ const duckdb = require('duckdb');
1433
+ const dbPath = path_1.default.join(__dirname, '../aumic-lexicon.duckdb');
1434
+ const db = new duckdb.Database(dbPath);
1435
+ let l1Css = '';
1436
+ let unresolvedClasses = new Set();
1437
+ const allTwClasses = new Set();
1438
+ for (const map of classMapping.values()) {
1439
+ map.tailwindOnlyArray.forEach(cls => allTwClasses.add(cls));
1440
+ }
1441
+ const classArray = Array.from(allTwClasses);
1442
+ const l1Promises = classArray.map(cls => {
1443
+ return new Promise((resolve) => {
1444
+ db.all("SELECT css FROM tw_lexicon WHERE version = ? AND class = ?", [twVersion, cls], (err, rows) => {
1445
+ if (err || rows.length === 0) {
1446
+ unresolvedClasses.add(cls);
1447
+ }
1448
+ else {
1449
+ l1Css += `\n/* tw: ${cls} (L1) */\n.${escapeCssSelector(cls)} { ${rows[0].css} }\n`;
1450
+ }
1451
+ resolve();
1452
+ });
1453
+ });
1454
+ });
1455
+ await Promise.all(l1Promises);
1456
+ db.close();
1457
+ // --- L2 DYNAMIC JIT ---
1458
+ const virtualHtml = Array.from(unresolvedClasses).map(cls => `<div class="${cls}"></div>`).join('\n');
1459
+ const projectTwPath = path_1.default.join(TARGET_DIR, 'node_modules', 'tailwindcss');
1373
1460
  let twPlugin;
1374
- twPlugin = require('tailwindcss')({ content: [{ raw: virtualHtml, extension: 'html' }], corePlugins: { preflight: false } });
1461
+ if (fs_extra_1.default.existsSync(projectTwPath)) {
1462
+ twPlugin = require(projectTwPath)({ content: [{ raw: virtualHtml, extension: 'html' }], corePlugins: { preflight: false }, theme: userConfig.theme || {} });
1463
+ }
1464
+ else {
1465
+ twPlugin = require('tailwindcss')({ content: [{ raw: virtualHtml, extension: 'html' }], corePlugins: { preflight: false }, theme: userConfig.theme || {} });
1466
+ }
1375
1467
  await piscinaPool.destroy();
1376
1468
  const jitResult = await (0, postcss_1.default)([twPlugin]).process(`@tailwind utilities;`, { from: undefined });
1377
- const root = postcss_1.default.parse(jitResult.css);
1469
+ const root = postcss_1.default.parse(l1Css + '\n' + jitResult.css);
1470
+ // =======================================
1471
+ // AUM-IC PREFLIGHT INJECTOR
1472
+ // =======================================
1473
+ let preflightCss = '';
1474
+ if (envStatus.importsPreflight) {
1475
+ try {
1476
+ let pfPlugin;
1477
+ if (fs_extra_1.default.existsSync(projectTwPath)) {
1478
+ pfPlugin = require(projectTwPath)({ content: [{ raw: '<div class="a"></div>', extension: 'html' }], theme: userConfig.theme || {} });
1479
+ }
1480
+ else {
1481
+ pfPlugin = require('tailwindcss')({ content: [{ raw: '<div class="a"></div>', extension: 'html' }], theme: userConfig.theme || {} });
1482
+ }
1483
+ const pfRes = await (0, postcss_1.default)([pfPlugin]).process('@tailwind base;', { from: undefined });
1484
+ preflightCss = pfRes.css;
1485
+ }
1486
+ catch (e) {
1487
+ console.warn("No se pudo extraer Preflight.");
1488
+ }
1489
+ } // end if importsPreflight
1490
+ if (preflightCss) {
1491
+ const pfAst = postcss_1.default.parse(preflightCss);
1492
+ globalRoot.prepend(pfAst);
1493
+ }
1378
1494
  root.walkRules((rule) => {
1379
1495
  for (const [escapedUtil, targets] of utilityToAumic) {
1380
1496
  if (rule.selector.includes(escapedUtil)) {
@@ -1396,7 +1512,16 @@ async function run() {
1396
1512
  }
1397
1513
  });
1398
1514
  if (answers.outputMode === 'global') {
1399
- await fs_extra_1.default.writeFile(path_1.default.join(TARGET_DIR, 'aumic-styles.css'), themeVars + globalRoot.toString(), 'utf-8');
1515
+ const outPath = path_1.default.join(TARGET_DIR, 'src', 'styles');
1516
+ require('fs-extra').ensureDirSync(outPath);
1517
+ await fs_extra_1.default.writeFile(path_1.default.join(outPath, 'aumic-styles.css'), themeVars + globalRoot.toString(), 'utf-8');
1518
+ const globalScss = path_1.default.join(outPath, 'global.scss');
1519
+ if (fs_extra_1.default.existsSync(globalScss)) {
1520
+ let scss = fs_extra_1.default.readFileSync(globalScss, 'utf8');
1521
+ if (!scss.includes('aumic-styles')) {
1522
+ fs_extra_1.default.writeFileSync(globalScss, scss + '\n@import "./aumic-styles.css";\n', 'utf8');
1523
+ }
1524
+ }
1400
1525
  }
1401
1526
  else {
1402
1527
  for (const [cat, cssRoot] of Object.entries(categoryRoots)) {
@@ -1430,7 +1555,47 @@ async function run() {
1430
1555
  fs_extra_1.default.renameSync(confPath, `${confPath}.aumic-bak`);
1431
1556
  }
1432
1557
  });
1433
- (0, child_process_1.execSync)('npm uninstall tailwindcss postcss @tailwindcss/postcss', { cwd: TARGET_DIR, stdio: 'ignore' });
1558
+ // ==========================================
1559
+ // UNIVERSAL TAILWIND ERADICATOR (v1 - v4)
1560
+ // ==========================================
1561
+ // 1. Respaldar y neutralizar archivos de configuración (v1, v2, v3)
1562
+ const twConfigFiles = ['tailwind.config.js', 'tailwind.config.ts', 'tailwind.config.cjs', 'tailwind.config.mjs', 'tailwind.js'];
1563
+ twConfigFiles.forEach(conf => {
1564
+ const confPath = require('path').join(TARGET_DIR, conf);
1565
+ if (fs_extra_1.default.existsSync(confPath))
1566
+ fs_extra_1.default.renameSync(confPath, `${confPath}.aumic-bak`);
1567
+ });
1568
+ // 2. Limpieza de Integradores (v4 Vite, Astro, PostCSS)
1569
+ const integrations = [
1570
+ { file: 'astro.config.mjs', regex: /import\s+tailwind\s+from\s+['"]@astrojs\/tailwind['"];?\n?/, regex2: /tailwind\(\)\s*,?/ },
1571
+ { file: 'astro.config.ts', regex: /import\s+tailwind\s+from\s+['"]@astrojs\/tailwind['"];?\n?/, regex2: /tailwind\(\)\s*,?/ },
1572
+ { file: 'vite.config.js', regex: /import\s+tailwindcss\s+from\s+['"]@tailwindcss\/vite['"];?\n?/, regex2: /tailwindcss\(\)\s*,?/ },
1573
+ { file: 'vite.config.ts', regex: /import\s+tailwindcss\s+from\s+['"]@tailwindcss\/vite['"];?\n?/, regex2: /tailwindcss\(\)\s*,?/ }
1574
+ ];
1575
+ integrations.forEach(intg => {
1576
+ const intgPath = require('path').join(TARGET_DIR, intg.file);
1577
+ if (fs_extra_1.default.existsSync(intgPath)) {
1578
+ let c = fs_extra_1.default.readFileSync(intgPath, 'utf8');
1579
+ c = c.replace(intg.regex, '');
1580
+ c = c.replace(intg.regex2, '');
1581
+ fs_extra_1.default.writeFileSync(intgPath, c, 'utf8');
1582
+ }
1583
+ });
1584
+ // 3. Desinstalación Dinámica Universal (Identifica dependencias exactas en package.json)
1585
+ try {
1586
+ const pkg = require(require('path').join(TARGET_DIR, 'package.json'));
1587
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
1588
+ const targets = ['tailwindcss', '@tailwindcss/vite', '@tailwindcss/postcss', '@tailwindcss/cli', '@astrojs/tailwind'];
1589
+ // Si encontramos tailwind v1 o v2, muchas veces se usaba autoprefixer a la par estrictamente.
1590
+ if (allDeps['tailwindcss'] && (allDeps['tailwindcss'].startsWith('^1') || allDeps['tailwindcss'].startsWith('^2'))) {
1591
+ // Solo como heurística
1592
+ }
1593
+ const toUninstall = targets.filter(t => allDeps[t]);
1594
+ if (toUninstall.length > 0) {
1595
+ require('child_process').execSync(`npm uninstall ${toUninstall.join(' ')}`, { cwd: TARGET_DIR, stdio: 'ignore' });
1596
+ }
1597
+ }
1598
+ catch (e) { }
1434
1599
  spinner.succeed(picocolors_1.default.green('Tailwind ha sido purgado completamente. (Configuraciones y package.json respaldados en .aumic-bak)'));
1435
1600
  }
1436
1601
  catch (e) {
@@ -1439,7 +1604,7 @@ async function run() {
1439
1604
  }
1440
1605
  const duration = ((Date.now() - startTime) / 1000).toFixed(2);
1441
1606
  logAudit(`[EJECUCIÓN] Operación completada exitosamente en ${duration} segundos.`);
1442
- console.log(picocolors_1.default.green(picocolors_1.default.bold(`\n[✔] PROYECTO TRANSMUTADO A LA DOCTRINA ${aumicLink}. (v2.0.24)\n`)));
1607
+ console.log(picocolors_1.default.green(picocolors_1.default.bold(`\n[✔] PROYECTO TRANSMUTADO A LA DOCTRINA ${aumicLink}. (v${pkgVersion})\n`)));
1443
1608
  // Generate Final Report
1444
1609
  spinner.start('Generando reporte post-mortem...');
1445
1610
  const postReportPath = generateReport(classMapping, TARGET_DIR, false);