@ingcrea/aumic-tailwind-killer 3.1.7 → 4.1.4

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;
@@ -1010,8 +1011,11 @@ async function run() {
1010
1011
  const content = await fs_extra_1.default.readFile(file, 'utf-8');
1011
1012
  try {
1012
1013
  const { newCode } = astEngine.processFrameworkFile(content, file, (twClass) => {
1013
- return lockData[twClass] || twClass;
1014
- });
1014
+ // Dividimos por si hay múltiples hashes en el mismo string
1015
+ const classes = twClass.split(" ");
1016
+ const restored = classes.map(c => lockData[c] || c).join(" ");
1017
+ return restored;
1018
+ }, true);
1015
1019
  if (newCode !== content) {
1016
1020
  await fs_extra_1.default.writeFile(file, newCode, 'utf-8');
1017
1021
  restoredCount++;
@@ -1162,9 +1166,37 @@ async function run() {
1162
1166
  allUniqueTokens.add(t.trim()); });
1163
1167
  }
1164
1168
  const testHtml = Array.from(allUniqueTokens).map(t => `<div class="${t}"></div>`).join('\n');
1169
+ 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);
1170
+ if (configPath) {
1171
+ try {
1172
+ const dynamicImport = new Function('modulePath', 'return import(modulePath)');
1173
+ userConfig = (await dynamicImport(require('url').pathToFileURL(configPath).href)).default || require(configPath);
1174
+ }
1175
+ catch (e) {
1176
+ try {
1177
+ userConfig = require(configPath);
1178
+ }
1179
+ catch (err) {
1180
+ try {
1181
+ const fsExt = require('fs');
1182
+ const tempPath = configPath + '.mjs';
1183
+ let raw = fsExt.readFileSync(configPath, 'utf8');
1184
+ raw = raw.replace(/module\.exports\s*=\s*/, 'export default ');
1185
+ fsExt.writeFileSync(tempPath, raw, 'utf8');
1186
+ const dynamicImport = new Function('modulePath', 'return import(modulePath)');
1187
+ userConfig = (await dynamicImport(require('url').pathToFileURL(tempPath).href)).default;
1188
+ fsExt.unlinkSync(tempPath);
1189
+ }
1190
+ catch (fatal) {
1191
+ console.log('[AUM-IC] Fallo al cargar Tailwind Config:', fatal.message);
1192
+ }
1193
+ }
1194
+ }
1195
+ }
1196
+ let mergedConfig = { content: [{ raw: testHtml, extension: 'html' }], corePlugins: { preflight: false }, theme: userConfig.theme || {} };
1165
1197
  let testPlugin;
1166
1198
  try {
1167
- testPlugin = require('tailwindcss')({ content: [{ raw: testHtml, extension: 'html' }], corePlugins: { preflight: false } });
1199
+ testPlugin = require('tailwindcss')(mergedConfig);
1168
1200
  }
1169
1201
  catch (e) {
1170
1202
  testPlugin = require('tailwindcss');
@@ -1182,6 +1214,36 @@ async function run() {
1182
1214
  }
1183
1215
  });
1184
1216
  spinner.succeed(`Filtro JIT completado: ${validTwClasses.size} utilidades nativas reconocidas.`);
1217
+ spinner.start('Analizando entorno y dependencias de Tailwind...');
1218
+ const envStatus = { version: 3, usesAstro: false, usesVite: false, usesPostcss: false, importsPreflight: false, importsUtilities: false, cssEntrypoint: '' };
1219
+ try {
1220
+ const p = require(path_1.default.join(TARGET_DIR, 'package.json'));
1221
+ const deps = { ...(p.dependencies || {}), ...(p.devDependencies || {}) };
1222
+ if (deps['tailwindcss'])
1223
+ envStatus.version = deps['tailwindcss'].startsWith('^4') || deps['tailwindcss'].startsWith('4') ? 4 : 3;
1224
+ else if (deps['@tailwindcss/vite'] || deps['@tailwindcss/postcss'])
1225
+ envStatus.version = 4;
1226
+ envStatus.usesAstro = !!deps['@astrojs/tailwind'];
1227
+ envStatus.usesVite = !!deps['@tailwindcss/vite'];
1228
+ envStatus.usesPostcss = !!deps['@tailwindcss/postcss'];
1229
+ }
1230
+ catch (e) { }
1231
+ const rootCssFiles = await (0, glob_1.glob)('**/*.{css,scss,sass,less,pcss}', { cwd: TARGET_DIR, absolute: true, ignore: ['node_modules/**', 'dist/**', 'public/**'] });
1232
+ for (const file of rootCssFiles) {
1233
+ const c = await fs_extra_1.default.readFile(file, 'utf-8');
1234
+ if (c.includes('@tailwind') || c.includes('tailwindcss')) {
1235
+ envStatus.cssEntrypoint = file;
1236
+ if (c.includes('@tailwind base') || c.includes('tailwindcss/preflight') || c.includes('@import "tailwindcss"'))
1237
+ envStatus.importsPreflight = true;
1238
+ if (c.includes('@tailwind utilities') || c.includes('tailwindcss/utilities') || c.includes('@import "tailwindcss"'))
1239
+ envStatus.importsUtilities = true;
1240
+ }
1241
+ }
1242
+ if (envStatus.usesAstro) {
1243
+ envStatus.importsPreflight = true;
1244
+ envStatus.importsUtilities = true;
1245
+ }
1246
+ 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
1247
  const classMapping = new Map();
1186
1248
  let customRules = '';
1187
1249
  const rcPath = path_1.default.join(TARGET_DIR, '.aumicrc.json');
@@ -1368,13 +1430,70 @@ async function run() {
1368
1430
  spinner.succeed(`CSS extranjero hackeado y reescrito a la doctrina ${aumicLink}.`);
1369
1431
  }
1370
1432
  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');
1433
+ spinner.start(`Fase 4: Ejecutando L1 Cache (DuckDB) y delegando L2 (JIT v${twVersion})...`);
1434
+ // --- L1 DUCKDB CACHE ---
1435
+ const duckdb = require('duckdb');
1436
+ const dbPath = path_1.default.join(__dirname, '../aumic-lexicon.duckdb');
1437
+ const db = new duckdb.Database(dbPath);
1438
+ let l1Css = '';
1439
+ let unresolvedClasses = new Set();
1440
+ const allTwClasses = new Set();
1441
+ for (const map of classMapping.values()) {
1442
+ map.tailwindOnlyArray.forEach(cls => allTwClasses.add(cls));
1443
+ }
1444
+ const classArray = Array.from(allTwClasses);
1445
+ const l1Promises = classArray.map(cls => {
1446
+ return new Promise((resolve) => {
1447
+ db.all("SELECT css FROM tw_lexicon WHERE version = ? AND class = ?", [twVersion, cls], (err, rows) => {
1448
+ if (err || rows.length === 0) {
1449
+ unresolvedClasses.add(cls);
1450
+ }
1451
+ else {
1452
+ l1Css += `\n/* tw: ${cls} (L1) */\n.${escapeCssSelector(cls)} { ${rows[0].css} }\n`;
1453
+ }
1454
+ resolve();
1455
+ });
1456
+ });
1457
+ });
1458
+ await Promise.all(l1Promises);
1459
+ db.close();
1460
+ // --- L2 DYNAMIC JIT ---
1461
+ const virtualHtml = Array.from(unresolvedClasses).map(cls => `<div class="${cls}"></div>`).join('\n');
1462
+ const projectTwPath = path_1.default.join(TARGET_DIR, 'node_modules', 'tailwindcss');
1373
1463
  let twPlugin;
1374
- twPlugin = require('tailwindcss')({ content: [{ raw: virtualHtml, extension: 'html' }], corePlugins: { preflight: false } });
1464
+ if (fs_extra_1.default.existsSync(projectTwPath)) {
1465
+ twPlugin = require(projectTwPath)({ content: [{ raw: virtualHtml, extension: 'html' }], corePlugins: { preflight: false }, theme: userConfig.theme || {} });
1466
+ }
1467
+ else {
1468
+ twPlugin = require('tailwindcss')({ content: [{ raw: virtualHtml, extension: 'html' }], corePlugins: { preflight: false }, theme: userConfig.theme || {} });
1469
+ }
1375
1470
  await piscinaPool.destroy();
1376
1471
  const jitResult = await (0, postcss_1.default)([twPlugin]).process(`@tailwind utilities;`, { from: undefined });
1377
- const root = postcss_1.default.parse(jitResult.css);
1472
+ const root = postcss_1.default.parse(l1Css + '\n' + jitResult.css);
1473
+ // =======================================
1474
+ // AUM-IC PREFLIGHT INJECTOR
1475
+ // =======================================
1476
+ let preflightCss = '';
1477
+ if (envStatus.importsPreflight) {
1478
+ try {
1479
+ let pfPlugin;
1480
+ if (fs_extra_1.default.existsSync(projectTwPath)) {
1481
+ pfPlugin = require(projectTwPath)({ content: [{ raw: '<div class="a"></div>', extension: 'html' }], theme: userConfig.theme || {} });
1482
+ }
1483
+ else {
1484
+ pfPlugin = require('tailwindcss')({ content: [{ raw: '<div class="a"></div>', extension: 'html' }], theme: userConfig.theme || {} });
1485
+ }
1486
+ const pfRes = await (0, postcss_1.default)([pfPlugin]).process('@tailwind base;', { from: undefined });
1487
+ preflightCss = pfRes.css;
1488
+ }
1489
+ catch (e) {
1490
+ console.warn("No se pudo extraer Preflight.");
1491
+ }
1492
+ } // end if importsPreflight
1493
+ if (preflightCss) {
1494
+ const pfAst = postcss_1.default.parse(preflightCss);
1495
+ globalRoot.prepend(pfAst);
1496
+ }
1378
1497
  root.walkRules((rule) => {
1379
1498
  for (const [escapedUtil, targets] of utilityToAumic) {
1380
1499
  if (rule.selector.includes(escapedUtil)) {
@@ -1396,7 +1515,16 @@ async function run() {
1396
1515
  }
1397
1516
  });
1398
1517
  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');
1518
+ const outPath = path_1.default.join(TARGET_DIR, 'src', 'styles');
1519
+ require('fs-extra').ensureDirSync(outPath);
1520
+ await fs_extra_1.default.writeFile(path_1.default.join(outPath, 'aumic-styles.css'), themeVars + globalRoot.toString(), 'utf-8');
1521
+ const globalScss = path_1.default.join(outPath, 'global.scss');
1522
+ if (fs_extra_1.default.existsSync(globalScss)) {
1523
+ let scss = fs_extra_1.default.readFileSync(globalScss, 'utf8');
1524
+ if (!scss.includes('aumic-styles')) {
1525
+ fs_extra_1.default.writeFileSync(globalScss, scss + '\n@import "./aumic-styles.css";\n', 'utf8');
1526
+ }
1527
+ }
1400
1528
  }
1401
1529
  else {
1402
1530
  for (const [cat, cssRoot] of Object.entries(categoryRoots)) {
@@ -1430,7 +1558,47 @@ async function run() {
1430
1558
  fs_extra_1.default.renameSync(confPath, `${confPath}.aumic-bak`);
1431
1559
  }
1432
1560
  });
1433
- (0, child_process_1.execSync)('npm uninstall tailwindcss postcss @tailwindcss/postcss', { cwd: TARGET_DIR, stdio: 'ignore' });
1561
+ // ==========================================
1562
+ // UNIVERSAL TAILWIND ERADICATOR (v1 - v4)
1563
+ // ==========================================
1564
+ // 1. Respaldar y neutralizar archivos de configuración (v1, v2, v3)
1565
+ const twConfigFiles = ['tailwind.config.js', 'tailwind.config.ts', 'tailwind.config.cjs', 'tailwind.config.mjs', 'tailwind.js'];
1566
+ twConfigFiles.forEach(conf => {
1567
+ const confPath = require('path').join(TARGET_DIR, conf);
1568
+ if (fs_extra_1.default.existsSync(confPath))
1569
+ fs_extra_1.default.renameSync(confPath, `${confPath}.aumic-bak`);
1570
+ });
1571
+ // 2. Limpieza de Integradores (v4 Vite, Astro, PostCSS)
1572
+ const integrations = [
1573
+ { file: 'astro.config.mjs', regex: /import\s+tailwind\s+from\s+['"]@astrojs\/tailwind['"];?\n?/, regex2: /tailwind\(\)\s*,?/ },
1574
+ { file: 'astro.config.ts', regex: /import\s+tailwind\s+from\s+['"]@astrojs\/tailwind['"];?\n?/, regex2: /tailwind\(\)\s*,?/ },
1575
+ { file: 'vite.config.js', regex: /import\s+tailwindcss\s+from\s+['"]@tailwindcss\/vite['"];?\n?/, regex2: /tailwindcss\(\)\s*,?/ },
1576
+ { file: 'vite.config.ts', regex: /import\s+tailwindcss\s+from\s+['"]@tailwindcss\/vite['"];?\n?/, regex2: /tailwindcss\(\)\s*,?/ }
1577
+ ];
1578
+ integrations.forEach(intg => {
1579
+ const intgPath = require('path').join(TARGET_DIR, intg.file);
1580
+ if (fs_extra_1.default.existsSync(intgPath)) {
1581
+ let c = fs_extra_1.default.readFileSync(intgPath, 'utf8');
1582
+ c = c.replace(intg.regex, '');
1583
+ c = c.replace(intg.regex2, '');
1584
+ fs_extra_1.default.writeFileSync(intgPath, c, 'utf8');
1585
+ }
1586
+ });
1587
+ // 3. Desinstalación Dinámica Universal (Identifica dependencias exactas en package.json)
1588
+ try {
1589
+ const pkg = require(require('path').join(TARGET_DIR, 'package.json'));
1590
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
1591
+ const targets = ['tailwindcss', '@tailwindcss/vite', '@tailwindcss/postcss', '@tailwindcss/cli', '@astrojs/tailwind'];
1592
+ // Si encontramos tailwind v1 o v2, muchas veces se usaba autoprefixer a la par estrictamente.
1593
+ if (allDeps['tailwindcss'] && (allDeps['tailwindcss'].startsWith('^1') || allDeps['tailwindcss'].startsWith('^2'))) {
1594
+ // Solo como heurística
1595
+ }
1596
+ const toUninstall = targets.filter(t => allDeps[t]);
1597
+ if (toUninstall.length > 0) {
1598
+ require('child_process').execSync(`npm uninstall ${toUninstall.join(' ')}`, { cwd: TARGET_DIR, stdio: 'ignore' });
1599
+ }
1600
+ }
1601
+ catch (e) { }
1434
1602
  spinner.succeed(picocolors_1.default.green('Tailwind ha sido purgado completamente. (Configuraciones y package.json respaldados en .aumic-bak)'));
1435
1603
  }
1436
1604
  catch (e) {
@@ -1439,7 +1607,7 @@ async function run() {
1439
1607
  }
1440
1608
  const duration = ((Date.now() - startTime) / 1000).toFixed(2);
1441
1609
  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`)));
1610
+ console.log(picocolors_1.default.green(picocolors_1.default.bold(`\n[✔] PROYECTO TRANSMUTADO A LA DOCTRINA ${aumicLink}. (v${pkgVersion})\n`)));
1443
1611
  // Generate Final Report
1444
1612
  spinner.start('Generando reporte post-mortem...');
1445
1613
  const postReportPath = generateReport(classMapping, TARGET_DIR, false);