import pandas as pd
import json

# ---- Carrega o novo banco de dados ----
path = r'D:\Evolução categorias\temp_bd2.xlsx'
df_raw = pd.read_excel(path, sheet_name='Planilha1', header=None)

# Extrair datas validas (row 0, ignorar NaN/NaT/zero e meses futuros sem dados)
raw_dates = df_raw.iloc[0, 1:].tolist()
valid_dates = []
for i, d in enumerate(raw_dates):
    try:
        ts = pd.Timestamp(d)
        if pd.notna(ts):
            valid_dates.append((i + 1, ts))
    except Exception:
        pass

valid_cols = [c for c, d in valid_dates]
date_labels_all = [d.strftime('%b/%Y') for c, d in valid_dates]

# Extrair categorias (linhas 2 a 15)
data_raw = {}
for row_idx in range(2, 17):
    row = df_raw.iloc[row_idx]
    cat = str(row[0]).strip()
    if pd.isna(row[0]) or cat in ('nan', '0', ''):
        continue
    values = []
    for c in valid_cols:
        v = row[c]
        values.append(float(v) if pd.notna(v) else 0.0)
    data_raw[cat] = values

# Remover meses do inicio sem nenhum dado real
n_cols = len(valid_cols)
first_real = 0
for i in range(n_cols):
    if any(data_raw[cat][i] > 0 for cat in data_raw):
        first_real = i
        break

# Remover meses do final sem nenhum dado real
last_real = n_cols - 1
for i in range(n_cols - 1, -1, -1):
    if any(data_raw[cat][i] > 0 for cat in data_raw):
        last_real = i
        break

data = {}
for cat in data_raw:
    data[cat] = data_raw[cat][first_real:last_real + 1]

date_labels = date_labels_all[first_real:last_real + 1]
labels = date_labels
n_months = len(labels)

import plotly.express as px
colors_list = px.colors.qualitative.Set2 + px.colors.qualitative.Pastel1
cat_totals_all = {cat: sum(vals) for cat, vals in data.items()}
cat_sorted = sorted(cat_totals_all, key=lambda x: cat_totals_all[x], reverse=True)
cat_colors = {cat: colors_list[i % len(colors_list)] for i, cat in enumerate(cat_sorted)}

db_json = json.dumps({
    'labels': labels,
    'categories': cat_sorted,
    'data': {cat: data[cat] for cat in cat_sorted},
    'colors': cat_colors,
})

html = f"""<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Dashboard - Evolucao das Categorias</title>
<script src="https://cdn.plot.ly/plotly-2.35.2.min.js"></script>
<script src="https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js"></script>
<style>
  *, *::before, *::after {{ box-sizing: border-box; margin: 0; padding: 0; }}
  body {{ font-family: 'Segoe UI', Arial, sans-serif; background: #f0f4f8; color: #1f2937; }}

  /* HEADER */
  .header {{
    background: linear-gradient(135deg, #1e3a5f 0%, #2563eb 100%);
    color: #fff; padding: 18px 32px 14px;
    display: flex; align-items: center; justify-content: space-between;
    box-shadow: 0 2px 8px rgba(0,0,0,.25);
  }}
  .header h1 {{ font-size: 1.4rem; font-weight: 700; }}
  .header p  {{ font-size: .82rem; opacity: .8; margin-top: 2px; }}
  .header-right {{ display: flex; align-items: center; gap: 12px; }}
  .theme-btn {{
    display: flex; align-items: center; gap: 7px;
    background: rgba(255,255,255,.15); border: 1.5px solid rgba(255,255,255,.35);
    color: #fff; border-radius: 24px; padding: 6px 14px;
    font-size: .82rem; font-weight: 600; cursor: pointer; transition: background .2s;
  }}
  .theme-btn:hover {{ background: rgba(255,255,255,.25); }}

  /* TABS */
  .tabs {{
    display: flex; gap: 0;
    background: #1e293b; padding: 0 32px;
    border-bottom: 3px solid #2563eb;
  }}
  .tab-btn {{
    padding: 11px 26px; font-size: .88rem; font-weight: 600;
    color: #94a3b8; background: none; border: none;
    cursor: pointer; border-bottom: 3px solid transparent;
    margin-bottom: -3px; transition: color .15s, border-color .15s;
    letter-spacing: .02em;
  }}
  .tab-btn:hover {{ color: #e2e8f0; }}
  .tab-btn.active {{ color: #fff; border-bottom-color: #38bdf8; }}

  /* KPI BAR */
  .kpi-bar {{
    display: flex; gap: 12px; flex-wrap: wrap;
    padding: 12px 32px; background: #fff;
    border-bottom: 1px solid #e5e7eb;
  }}
  .kpi {{
    flex: 1 1 150px; background: #f8fafc; border: 1px solid #e2e8f0;
    border-radius: 10px; padding: 10px 16px;
  }}
  .kpi-label {{ font-size: .68rem; text-transform: uppercase; letter-spacing: .08em; color: #64748b; }}
  .kpi-value {{ font-size: 1.25rem; font-weight: 700; color: #1e3a5f; margin-top: 2px; }}
  .kpi-sub   {{ font-size: .75rem; margin-top: 2px; color: #64748b; }}
  .pos {{ color: #16a34a; }} .neg {{ color: #dc2626; }}

  /* FILTER PANEL */
  /* ---- FILTER PANEL REDESIGN ---- */
  .filter-panel {{
    background: #fff; border-bottom: 1px solid #e5e7eb;
    padding: 0; display: flex; flex-direction: column;
  }}
  .filter-top {{
    display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
    padding: 10px 32px; border-bottom: 1px solid #f1f5f9; cursor: pointer;
    user-select: none;
  }}
  .filter-top:hover {{ background: #f8fafc; }}
  .filter-toggle-icon {{ font-size: .85rem; color: #64748b; margin-left: auto; transition: transform .2s; }}
  .filter-toggle-icon.open {{ transform: rotate(180deg); }}
  .filter-body {{
    padding: 14px 32px 16px;
    display: flex; gap: 24px; flex-wrap: wrap; align-items: flex-start;
    border-top: 1px solid #f1f5f9;
  }}
  .filter-body.hidden {{ display: none; }}

  .filter-group {{ display: flex; flex-direction: column; gap: 6px; }}
  .filter-group label.title {{
    font-size: .68rem; text-transform: uppercase; letter-spacing: .08em;
    color: #475569; font-weight: 700;
  }}
  .date-range {{ display: flex; gap: 8px; align-items: center; }}
  .date-range select {{
    border: 1px solid #cbd5e1; border-radius: 6px; padding: 5px 10px;
    font-size: .82rem; background: #f8fafc; cursor: pointer;
  }}
  .date-range select:focus {{ outline: 2px solid #3b82f6; border-color: transparent; }}

  /* Cat grid — NOVO VISUAL */
  .cat-filter-wrap {{ display: flex; flex-direction: column; gap: 8px; flex: 1 1 500px; }}
  .cat-filter-header {{ display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }}
  .cat-search {{
    border: 1px solid #cbd5e1; border-radius: 8px; padding: 5px 12px 5px 30px;
    font-size: .8rem; background: #f8fafc url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' fill='%2394a3b8' viewBox='0 0 16 16'%3E%3Cpath d='M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398l3.85 3.85a1 1 0 0 0 1.415-1.415l-3.868-3.833zm-5.242 1.156a5 5 0 1 1 0-10 5 5 0 0 1 0 10z'/%3E%3C/svg%3E") no-repeat 9px center;
    width: 190px; outline: none; transition: border-color .15s, box-shadow .15s;
  }}
  .cat-search:focus {{ border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59,130,246,.12); }}
  .cat-counter {{
    font-size: .72rem; color: #64748b; background: #f1f5f9;
    border-radius: 20px; padding: 3px 10px; font-weight: 700;
    border: 1px solid #e2e8f0;
  }}
  .btn-row {{ display: flex; gap: 5px; flex-wrap: wrap; }}
  .btn {{
    border: none; border-radius: 7px; padding: 5px 13px; font-size: .75rem;
    cursor: pointer; font-weight: 600; transition: all .15s;
  }}
  .btn:hover {{ filter: brightness(1.08); transform: translateY(-1px); }}
  .btn:active {{ transform: translateY(0); }}
  .btn-all  {{ background: #2563eb; color: #fff; box-shadow: 0 1px 4px rgba(37,99,235,.3); }}
  .btn-none {{ background: #e5e7eb; color: #374151; }}

  /* Cards de categoria */
  .cat-checkboxes {{
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
    gap: 7px;
  }}
  .cat-card {{
    position: relative; overflow: hidden;
    border-radius: 10px; padding: 9px 12px 9px 14px;
    cursor: pointer; user-select: none;
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    border: 2px solid transparent;
    background: #f1f5f9;
    transition: transform .15s, box-shadow .15s, background .15s, border-color .15s;
  }}
  .cat-card::before {{
    content: ''; position: absolute; left: 0; top: 0; bottom: 0;
    width: 4px; border-radius: 10px 0 0 10px;
    background: var(--cat-color);
    transition: width .15s;
  }}
  .cat-card:hover {{ transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,.1); }}
  .cat-card.active {{
    background: color-mix(in srgb, var(--cat-color) 12%, white);
    border-color: var(--cat-color);
    box-shadow: 0 2px 8px color-mix(in srgb, var(--cat-color) 25%, transparent);
  }}
  .cat-card.active::before {{ width: 5px; }}
  .cat-card.hidden-cat {{ display: none; }}
  .cat-card input {{ display: none; }}
  .cat-card-left {{ display: flex; align-items: center; gap: 7px; min-width: 0; }}
  .cat-card-name {{
    font-size: .76rem; font-weight: 600; color: #475569;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    transition: color .15s;
  }}
  .cat-card.active .cat-card-name {{ color: color-mix(in srgb, var(--cat-color) 80%, #000); }}

  /* Toggle switch */
  .toggle-sw {{
    flex-shrink: 0; width: 30px; height: 17px; border-radius: 20px;
    background: #cbd5e1; position: relative; transition: background .2s;
  }}
  .toggle-sw::after {{
    content: ''; position: absolute; top: 2px; left: 2px;
    width: 13px; height: 13px; border-radius: 50%;
    background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.25);
    transition: transform .2s;
  }}
  .cat-card.active .toggle-sw {{
    background: var(--cat-color);
  }}
  .cat-card.active .toggle-sw::after {{ transform: translateX(13px); }}

  /* Dark mode cats */
  body.dark .cat-card {{ background: #1e293b; }}
  body.dark .cat-card.active {{
    background: color-mix(in srgb, var(--cat-color) 18%, #0f172a);
  }}
  body.dark .cat-card-name {{ color: #94a3b8; }}
  body.dark .cat-card.active .cat-card-name {{
    color: color-mix(in srgb, var(--cat-color) 90%, white);
  }}
  body.dark .toggle-sw {{ background: #334155; }}

  /* Summary bar (collapsed state) */
  .filter-summary {{ display:flex; gap:6px; flex-wrap:wrap; align-items:center; }}
  .filter-pill {{
    display: inline-flex; align-items: center; gap: 4px;
    background: #dbeafe; border: 1px solid #93c5fd;
    border-radius: 20px; padding: 2px 9px;
    font-size: .72rem; color: #1e40af; font-weight: 600;
  }}
  .filter-pill .dot {{ width:8px; height:8px; border-radius:50%; flex-shrink:0; }}

  /* TAB PAGES */
  .tab-page {{ display: none; }}
  .tab-page.active {{ display: block; }}

  /* CHARTS GRID */
  .charts {{ padding: 18px 32px 28px; display: grid; gap: 16px;
    grid-template-columns: 1fr 1fr; }}
  .chart-card {{
    background: #fff; border-radius: 12px;
    box-shadow: 0 1px 5px rgba(0,0,0,.08);
    padding: 14px 12px 8px; overflow: hidden;
  }}
  .chart-card.wide {{ grid-column: span 2; }}
  .chart-card h3 {{
    font-size: .75rem; text-transform: uppercase; letter-spacing: .07em;
    color: #64748b; margin-bottom: 7px; padding-left: 3px;
  }}

  /* RANKING TAB */
  .rank-page {{ padding: 20px 32px 32px; display: flex; flex-direction: column; gap: 18px; }}
  .rank-top {{ display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }}
  .rank-table-wrap {{
    background: #fff; border-radius: 12px;
    box-shadow: 0 1px 5px rgba(0,0,0,.08);
    padding: 16px; overflow: auto;
  }}
  .rank-table-wrap h3 {{
    font-size: .75rem; text-transform: uppercase; letter-spacing: .07em;
    color: #64748b; margin-bottom: 12px;
  }}
  table.rank-tbl {{ width: 100%; border-collapse: collapse; font-size: .84rem; }}
  table.rank-tbl thead th {{
    background: #1e3a5f; color: #fff;
    padding: 8px 12px; text-align: left; white-space: nowrap;
    cursor: pointer; user-select: none; font-weight: 600;
  }}
  table.rank-tbl thead th:hover {{ background: #2563eb; }}
  table.rank-tbl thead th.asc::after  {{ content: ' ▲'; font-size:.7rem; }}
  table.rank-tbl thead th.desc::after {{ content: ' ▼'; font-size:.7rem; }}
  table.rank-tbl tbody tr {{ transition: background .1s; }}
  table.rank-tbl tbody tr:nth-child(even) {{ background: #f8fafc; }}
  table.rank-tbl tbody tr:hover {{ background: #dbeafe; }}
  table.rank-tbl td {{ padding: 8px 12px; border-bottom: 1px solid #e5e7eb; white-space: nowrap; }}
  table.rank-tbl td.r {{ text-align: right; }}
  table.rank-tbl td.c {{ text-align: center; }}
  .rank-medal {{ font-size: 1rem; }}
  .bar-inline {{ display:inline-block; height:8px; border-radius:4px; vertical-align:middle; margin-right:5px; }}
  table.rank-tbl tfoot td {{
    font-weight: 700; background: #f1f5f9; border-top: 2px solid #cbd5e1;
    padding: 8px 12px;
  }}

  /* PCT TABLE */
  .pct-table {{ width: 100%; border-collapse: collapse; font-size: .8rem; }}
  .pct-table thead th {{
    position: sticky; top: 0; z-index: 2;
    background: #1e3a5f; color: #fff;
    padding: 7px 10px; text-align: center; font-weight: 600; white-space: nowrap;
    border-right: 1px solid rgba(255,255,255,.15);
  }}
  .pct-table thead th:first-child {{ text-align: left; min-width: 140px; }}
  .pct-table tbody tr:nth-child(even) {{ background: #f8fafc; }}
  .pct-table tbody tr:hover {{ background: #eff6ff; }}
  .pct-table td {{ padding: 6px 10px; border-bottom: 1px solid #e5e7eb; border-right: 1px solid #f1f5f9; white-space: nowrap; }}
  .pct-table td:first-child {{ font-weight: 600; color: #1e3a5f; text-align: left; display:flex; align-items:center; gap:7px; }}
  .pct-table td.num {{ text-align: center; }}
  .pct-cell {{ display:inline-flex; align-items:center; justify-content:center; width:100%; gap:5px; }}
  .pct-bar-wrap {{ width:38px; height:8px; background:#e5e7eb; border-radius:4px; overflow:hidden; flex-shrink:0; }}
  .pct-bar {{ height:100%; border-radius:4px; }}
  .pct-val {{ min-width:36px; text-align:right; color:#374151; font-weight:500; }}
  .pct-table tfoot td {{ font-weight:700; background:#f1f5f9; color:#1e3a5f; padding:7px 10px; text-align:center; border-top:2px solid #cbd5e1; }}
  .pct-table tfoot td:first-child {{ text-align:left; }}

  /* DARK */
  body.dark {{ background:#0f172a; color:#e2e8f0; }}
  body.dark .tabs {{ background:#0f172a; }}
  body.dark .tab-btn {{ color:#64748b; }}
  body.dark .tab-btn:hover {{ color:#cbd5e1; }}
  body.dark .tab-btn.active {{ color:#fff; }}
  body.dark .kpi-bar {{ background:#1e293b; border-color:#334155; }}
  body.dark .kpi {{ background:#0f172a; border-color:#334155; }}
  body.dark .kpi-label {{ color:#94a3b8; }}
  body.dark .kpi-value {{ color:#93c5fd; }}
  body.dark .kpi-sub {{ color:#64748b; }}
  body.dark .filter-panel {{ background:#1e293b; border-color:#334155; }}
  body.dark .filter-top {{ border-color:#334155; }}
  body.dark .filter-top:hover {{ background:#162032; }}
  body.dark .filter-toggle-icon {{ color:#64748b; }}
  body.dark .filter-body {{ border-color:#334155; }}
  body.dark .filter-group label.title {{ color:#94a3b8; }}
  body.dark .date-range select {{ background:#0f172a; border-color:#475569; color:#e2e8f0; }}
  body.dark .cat-search {{ background:#0f172a; border-color:#475569; color:#e2e8f0; }}
  body.dark .cat-counter {{ background:#334155; color:#94a3b8; }}
  body.dark .cat-checkboxes label {{ background:#0f172a; border-color:#475569; color:#cbd5e1; }}
  body.dark .cat-checkboxes label:hover {{ background:#1e3a5f; border-color:#60a5fa; }}
  body.dark .cat-checkboxes label.checked {{ background:#1e3a5f; border-color:#3b82f6; color:#93c5fd; }}
  body.dark .btn-none {{ background:#334155; color:#cbd5e1; }}
  body.dark .filter-pill {{ background:#1e3a5f; border-color:#3b82f6; color:#93c5fd; }}
  body.dark .chart-card {{ background:#1e293b; }}
  body.dark .chart-card h3 {{ color:#94a3b8; }}
  body.dark .rank-table-wrap {{ background:#1e293b; }}
  body.dark .rank-table-wrap h3 {{ color:#94a3b8; }}
  body.dark table.rank-tbl tbody tr:nth-child(even) {{ background:#162032; }}
  body.dark table.rank-tbl tbody tr:hover {{ background:#1e3a5f; }}
  body.dark table.rank-tbl td {{ border-color:#334155; color:#e2e8f0; }}
  body.dark table.rank-tbl tfoot td {{ background:#162032; border-color:#475569; color:#93c5fd; }}
  body.dark .pct-table tbody tr:nth-child(even) {{ background:#162032; }}
  body.dark .pct-table tbody tr:hover {{ background:#1e3a5f; }}
  body.dark .pct-table td {{ border-color:#334155; color:#e2e8f0; }}
  body.dark .pct-table td:first-child {{ color:#93c5fd; }}
  body.dark .pct-bar-wrap {{ background:#334155; }}
  body.dark .pct-table tfoot td {{ background:#162032; color:#93c5fd; border-color:#475569; }}

  /* PROJECAO */
  .proj-page {{ padding: 18px 32px 32px; display:flex; flex-direction:column; gap:16px; }}
  .proj-controls {{
    background:#fff; border-radius:12px; box-shadow:0 1px 5px rgba(0,0,0,.08);
    padding:16px 20px; display:flex; gap:32px; flex-wrap:wrap; align-items:flex-start;
  }}
  .proj-ctrl-group {{ display:flex; flex-direction:column; gap:7px; }}
  .proj-ctrl-group .title {{
    font-size:.68rem; text-transform:uppercase; letter-spacing:.08em;
    color:#475569; font-weight:600;
  }}
  .proj-methods {{ display:flex; gap:8px; flex-wrap:wrap; }}
  .method-btn {{
    padding:5px 14px; border-radius:20px; border:1.5px solid #e2e8f0;
    background:#f1f5f9; font-size:.78rem; cursor:pointer; transition:all .15s;
    display:flex; align-items:center; gap:5px;
  }}
  .method-btn input {{ display:none; }}
  .method-btn:hover {{ border-color:#fcd34d; background:#fffbeb; }}
  .method-btn.active {{ background:#fef3c7; border-color:#f59e0b; color:#92400e; font-weight:600; }}

  .proj-kpis {{ display:flex; gap:12px; flex-wrap:wrap; }}
  .proj-kpi {{
    flex:1 1 160px; background:#fff; border:1px solid #e2e8f0;
    border-radius:10px; padding:12px 16px;
    box-shadow:0 1px 4px rgba(0,0,0,.06);
  }}
  .proj-kpi.accent {{ background:#fffbeb; border-color:#f59e0b; }}
  .proj-kpi.accent .kpi-value {{ color:#b45309; }}

  body.dark .proj-controls {{ background:#1e293b; }}
  body.dark .proj-ctrl-group .title {{ color:#94a3b8; }}
  body.dark .method-btn {{ background:#0f172a; border-color:#475569; color:#cbd5e1; }}
  body.dark .method-btn.active {{ background:#422006; border-color:#f59e0b; color:#fcd34d; }}
  body.dark .proj-kpi {{ background:#1e293b; border-color:#334155; }}
  body.dark .proj-kpi.accent {{ background:#1c1400; border-color:#f59e0b; }}
  body.dark .proj-kpi.accent .kpi-value {{ color:#fcd34d; }}

  /* COMPARACAO */
  .comp-page {{ padding:18px 32px 32px; display:flex; flex-direction:column; gap:16px; }}
  .comp-controls {{
    background:#fff; border-radius:12px; box-shadow:0 1px 5px rgba(0,0,0,.08);
    padding:16px 20px; display:flex; gap:32px; flex-wrap:wrap; align-items:flex-start;
  }}
  body.dark .comp-controls {{ background:#1e293b; }}

  /* ============================
     RESPONSIVO MOBILE
     ============================ */
  @media (max-width: 768px) {{
    .header {{
      padding: 12px 16px; flex-direction: column;
      align-items: flex-start; gap: 10px;
    }}
    .header h1 {{ font-size: 1.05rem; }}
    .header p  {{ font-size: .75rem; }}
    .header-right {{ width: 100%; justify-content: flex-end; gap: 8px; }}
    .theme-btn {{ padding: 6px 12px; font-size: .75rem; min-height: 36px; }}

    .tabs {{
      padding: 0 6px; overflow-x: auto;
      -webkit-overflow-scrolling: touch;
      scrollbar-width: none;
    }}
    .tabs::-webkit-scrollbar {{ display: none; }}
    .tab-btn {{ padding: 9px 12px; font-size: .76rem; letter-spacing: 0; white-space: nowrap; }}

    .kpi-bar {{ padding: 8px 12px; gap: 7px; }}
    .kpi {{ flex: 1 1 calc(50% - 7px); padding: 8px 10px; }}
    .kpi-value {{ font-size: .95rem; }}
    .kpi-sub {{ font-size: .68rem; }}

    .filter-panel {{ padding: 0; }}
    .filter-top {{ padding: 8px 12px; gap: 8px; }}
    .filter-body {{ padding: 10px 12px; flex-direction: column; gap: 12px; }}
    .cat-search {{ width: 140px; }}
    .cat-checkboxes {{
      grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
    }}
    .btn {{ padding: 5px 12px; font-size: .75rem; min-height: 34px; }}

    .charts {{
      padding: 10px 12px 20px;
      grid-template-columns: 1fr;
      gap: 12px;
    }}
    .chart-card.wide {{ grid-column: span 1; }}
    .chart-card h3 {{ font-size: .68rem; margin-bottom: 5px; }}

    .rank-page {{ padding: 10px 12px 20px; gap: 12px; }}
    .rank-top {{ grid-template-columns: 1fr; gap: 12px; }}
    .rank-table-wrap {{ padding: 12px; }}
    .rank-table-wrap h3 {{ font-size: .68rem; }}
    table.rank-tbl thead th {{ padding: 6px 8px; font-size: .72rem; }}
    table.rank-tbl td {{ padding: 5px 8px; font-size: .74rem; }}

    .comp-page {{ padding: 10px 12px 20px; gap: 12px; }}
    .comp-controls {{ padding: 12px; flex-direction: column; gap: 12px; }}
    .proj-page {{ padding: 10px 12px 20px; gap: 12px; }}
    .proj-controls {{ flex-direction: column; gap: 12px; padding: 12px; }}
    .proj-kpis {{ gap: 7px; }}
    .proj-kpi {{ flex: 1 1 calc(50% - 7px); padding: 8px 10px; }}

    .pct-table thead th {{ padding: 5px 8px; font-size: .7rem; }}
    .pct-table td {{ padding: 4px 8px; font-size: .7rem; }}
    .pct-bar-wrap {{ width: 28px; }}

    .method-btn {{ padding: 5px 10px; font-size: .74rem; }}
    .proj-methods {{ gap: 6px; }}
  }}

  @media (max-width: 480px) {{
    .kpi {{ flex: 1 1 100%; }}
    .proj-kpi {{ flex: 1 1 100%; }}
    .tab-btn {{ padding: 8px 10px; font-size: .7rem; }}
    .header h1 {{ font-size: .95rem; }}
  }}
</style>
</head>
<body>

<!-- HEADER -->
<div class="header">
  <div>
    <h1>Dashboard &mdash; Evolucao das Categorias</h1>
    <p>Bd_categorias &nbsp;|&nbsp; Fev/2022 a Mar/2026</p>
  </div>
  <div class="header-right">
    <button class="theme-btn" style="background:rgba(34,197,94,.2);border-color:rgba(34,197,94,.5)" onclick="exportExcel()">
      &#128190; Exportar Excel
    </button>
    <button class="theme-btn" onclick="toggleTheme()">
      <span id="theme-icon">&#9790;</span>
      <span id="theme-label">Modo Noite</span>
    </button>
  </div>
</div>

<!-- TABS -->
<div class="tabs">
  <button class="tab-btn active" onclick="switchTab('visao-geral',this)">Visao Geral</button>
  <button class="tab-btn" onclick="switchTab('ranking',this)">Ranking</button>
  <button class="tab-btn" onclick="switchTab('participacao',this)">Participacao %</button>
  <button class="tab-btn" style="color:#f59e0b;font-weight:700" onclick="switchTab('projecao',this)">&#128200; Projecao</button>
  <button class="tab-btn" style="color:#a78bfa;font-weight:700" onclick="switchTab('comparacao',this)">&#9878; Comparacao</button>
</div>

<!-- KPI BAR (sempre visivel) -->
<div class="kpi-bar">
  <div class="kpi">
    <div class="kpi-label">Total Acumulado</div>
    <div class="kpi-value" id="kpi-total">—</div>
    <div class="kpi-sub" id="kpi-period"></div>
  </div>
  <div class="kpi">
    <div class="kpi-label">Ultimo Mes</div>
    <div class="kpi-value" id="kpi-last">—</div>
    <div class="kpi-sub" id="kpi-last-label"></div>
  </div>
  <div class="kpi">
    <div class="kpi-label">Variacao MoM</div>
    <div class="kpi-value" id="kpi-mom">—</div>
    <div class="kpi-sub" id="kpi-mom-sub"></div>
  </div>
  <div class="kpi">
    <div class="kpi-label">Media Mensal</div>
    <div class="kpi-value" id="kpi-avg">—</div>
    <div class="kpi-sub" id="kpi-cat-count"></div>
  </div>
  <div class="kpi">
    <div class="kpi-label">Melhor Categoria</div>
    <div class="kpi-value" id="kpi-best-cat" style="font-size:.95rem">—</div>
    <div class="kpi-sub" id="kpi-best-val"></div>
  </div>
</div>

<!-- FILTER PANEL -->
<div class="filter-panel">
  <!-- Barra superior recolhivel -->
  <div class="filter-top" onclick="toggleFilter()" id="filter-top">
    <span style="font-size:.7rem;text-transform:uppercase;letter-spacing:.08em;color:#475569;font-weight:700">&#128196; Filtros</span>
    <div class="filter-summary" id="filter-summary"></div>
    <span class="filter-toggle-icon open" id="filter-toggle-icon">&#9660;</span>
  </div>

  <!-- Corpo expansivel -->
  <div class="filter-body" id="filter-body">
    <!-- Periodo -->
    <div class="filter-group">
      <label class="title">&#128197; Periodo</label>
      <div class="date-range">
        <select id="sel-start"></select>
        <span style="color:#94a3b8;font-size:.8rem">&#8594;</span>
        <select id="sel-end"></select>
      </div>
    </div>

    <!-- Categorias -->
    <div class="cat-filter-wrap">
      <label class="title">&#127991; Categorias</label>
      <div class="cat-filter-header">
        <input class="cat-search" id="cat-search" type="text" placeholder="&#128269; Buscar categoria..." oninput="filterCatSearch(this.value)">
        <span class="cat-counter" id="cat-counter">0 / 0</span>
        <div class="btn-row">
          <button class="btn btn-all"  onclick="selectAllCats()">Todas</button>
          <button class="btn btn-none" onclick="clearAllCats()">Limpar</button>
        </div>
      </div>
      <div class="cat-checkboxes" id="cat-checkboxes"></div>
    </div>
  </div>
</div>

<!-- ============ ABA: VISAO GERAL ============ -->
<div id="tab-visao-geral" class="tab-page active">
  <div class="charts">
    <div class="chart-card wide"><h3>Evolucao Mensal do Faturamento Total</h3><div id="chart-total" style="height:250px"></div></div>
    <div class="chart-card"><h3>Participacao por Categoria (%)</h3><div id="chart-pie" style="height:290px"></div></div>
    <div class="chart-card"><h3>Ranking — Total e Participacao</h3><div id="chart-rank" style="height:290px"></div></div>
    <div class="chart-card wide"><h3>Evolucao por Categoria</h3><div id="chart-lines" style="height:270px"></div></div>
  </div>
</div>

<!-- ============ ABA: RANKING ============ -->
<div id="tab-ranking" class="tab-page">
  <div class="rank-page">
    <div class="rank-top">
      <div class="chart-card"><h3>Ranking por Faturamento Total</h3><div id="chart-rank2" style="height:320px"></div></div>
      <div class="chart-card"><h3>Variacao MoM — Ultimo Periodo</h3><div id="chart-mom" style="height:320px"></div></div>
    </div>
    <div class="rank-table-wrap">
      <h3>Tabela Completa de Ranking</h3>
      <div style="overflow-x:auto">
        <table class="rank-tbl" id="rank-tbl">
          <thead>
            <tr>
              <th onclick="sortRank(0)">#</th>
              <th onclick="sortRank(1)">Categoria</th>
              <th onclick="sortRank(2)">Total Periodo</th>
              <th onclick="sortRank(3)">Participacao</th>
              <th onclick="sortRank(4)">Media Mensal</th>
              <th onclick="sortRank(5)">Melhor Mes</th>
              <th onclick="sortRank(6)">Pior Mes</th>
              <th onclick="sortRank(7)">Var. MoM</th>
              <th onclick="sortRank(8)">Tendencia</th>
            </tr>
          </thead>
          <tbody id="rank-tbl-body"></tbody>
          <tfoot id="rank-tbl-foot"></tfoot>
        </table>
      </div>
    </div>
  </div>
</div>

<!-- ============ ABA: PARTICIPACAO % ============ -->
<div id="tab-participacao" class="tab-page">
  <div class="charts">
    <div class="chart-card wide">
      <h3>Participacao % por Mes — Tabela</h3>
      <div id="table-pct-wrap" style="overflow-x:auto; max-height:380px; overflow-y:auto;"></div>
    </div>
  </div>
</div>

<!-- ============ ABA: PROJECAO ============ -->
<div id="tab-projecao" class="tab-page">
  <div class="proj-page">

    <!-- Controles de metodo -->
    <div class="proj-controls">
      <div class="proj-ctrl-group">
        <label class="title">Metodo de Projecao</label>
        <div class="proj-methods">
          <label class="method-btn active" id="met-mm3"><input type="radio" name="metodo" value="mm3" checked> Media Movel 3 meses</label>
          <label class="method-btn" id="met-mm6"><input type="radio" name="metodo" value="mm6"> Media Movel 6 meses</label>
          <label class="method-btn" id="met-trend"><input type="radio" name="metodo" value="trend"> Tendencia Linear</label>
          <label class="method-btn" id="met-yoy"><input type="radio" name="metodo" value="yoy"> Mesmo Mes Ano Anterior</label>
        </div>
      </div>
      <div class="proj-ctrl-group">
        <label class="title">Ajuste Manual (%)</label>
        <div style="display:flex;align-items:center;gap:10px">
          <input type="range" id="adj-slider" min="-30" max="30" value="0" step="1"
            style="width:160px;accent-color:#f59e0b" oninput="adjChange(this.value)">
          <span id="adj-val" style="font-weight:700;color:#f59e0b;min-width:40px">0%</span>
          <button class="btn" style="background:#e5e7eb;color:#374151;padding:3px 10px;font-size:.75rem"
            onclick="document.getElementById('adj-slider').value=0;adjChange(0)">Reset</button>
        </div>
      </div>
    </div>

    <!-- KPIs da projecao -->
    <div class="proj-kpis">
      <div class="proj-kpi accent">
        <div class="kpi-label">Projecao Total</div>
        <div class="kpi-value" id="proj-kpi-total">—</div>
        <div class="kpi-sub" id="proj-kpi-mes">—</div>
      </div>
      <div class="proj-kpi">
        <div class="kpi-label">vs Ultimo Mes Real</div>
        <div class="kpi-value" id="proj-kpi-mom">—</div>
        <div class="kpi-sub">variacao esperada</div>
      </div>
      <div class="proj-kpi">
        <div class="kpi-label">Media dos Ultimos 3 Meses</div>
        <div class="kpi-value" id="proj-kpi-mm3">—</div>
        <div class="kpi-sub">base de referencia</div>
      </div>
      <div class="proj-kpi">
        <div class="kpi-label">Metodo Ativo</div>
        <div class="kpi-value" id="proj-kpi-metodo" style="font-size:.9rem">—</div>
        <div class="kpi-sub" id="proj-kpi-adj">ajuste: 0%</div>
      </div>
    </div>

    <!-- Graficos -->
    <div class="charts" style="padding:0 0 0 0">
      <div class="chart-card wide"><h3>Historico + Projecao por Categoria</h3><div id="chart-proj-linha" style="height:310px"></div></div>
      <div class="chart-card"><h3>Projecao por Categoria — Proximo Mes</h3><div id="chart-proj-bar" style="height:320px"></div></div>
      <div class="chart-card"><h3>Comparativo: Real vs Projecao</h3><div id="chart-proj-comp" style="height:320px"></div></div>
    </div>

    <!-- Tabela de projecao -->
    <div class="rank-table-wrap" style="margin:0">
      <h3>Tabela de Projecao por Categoria — Proximo Mes</h3>
      <div style="overflow-x:auto">
        <table class="rank-tbl">
          <thead><tr>
            <th>Categoria</th>
            <th>Ultimo Real</th>
            <th>Projecao</th>
            <th>Variacao</th>
            <th>Participacao Proj.</th>
            <th>Intervalo Min</th>
            <th>Intervalo Max</th>
          </tr></thead>
          <tbody id="proj-tbl-body"></tbody>
          <tfoot id="proj-tbl-foot"></tfoot>
        </table>
      </div>
    </div>
  </div>
</div>

<!-- ============ ABA: COMPARACAO ============ -->
<div id="tab-comparacao" class="tab-page">
  <div class="comp-page">

    <!-- Seletor de categorias para comparar -->
    <div class="comp-controls">
      <div class="proj-ctrl-group">
        <label class="title">Selecione as categorias para comparar (2 a 6)</label>
        <div class="cat-checkboxes" id="comp-cat-checks"></div>
        <div class="btn-row" style="margin-top:6px">
          <button class="btn btn-all"  onclick="compSelAll()">Todas</button>
          <button class="btn btn-none" onclick="compSelNone()">Limpar</button>
          <button class="btn" style="background:#a78bfa;color:#fff" onclick="compSelTop(3)">Top 3</button>
          <button class="btn" style="background:#7c3aed;color:#fff" onclick="compSelTop(5)">Top 5</button>
        </div>
      </div>
      <div class="proj-ctrl-group">
        <label class="title">Tipo de Escala</label>
        <div class="proj-methods">
          <label class="method-btn active" id="scale-abs"><input type="radio" name="escala" value="abs" checked> Valor Absoluto (R$)</label>
          <label class="method-btn" id="scale-idx"><input type="radio" name="escala" value="idx"> Indexado (base 100)</label>
          <label class="method-btn" id="scale-pct"><input type="radio" name="escala" value="pct"> Variacao Acumulada (%)</label>
        </div>
      </div>
    </div>

    <!-- KPIs comparativos -->
    <div class="proj-kpis" id="comp-kpis"></div>

    <!-- Graficos 2x2 -->
    <div class="charts" style="padding:0">
      <div class="chart-card wide"><h3>Evolucao Comparativa ao Longo do Tempo</h3><div id="comp-linha" style="height:300px"></div></div>
      <div class="chart-card"><h3>Total Acumulado por Categoria</h3><div id="comp-bar-total" style="height:300px"></div></div>
      <div class="chart-card"><h3>Radar — Multiplos Indicadores</h3><div id="comp-radar" style="height:300px"></div></div>
      <div class="chart-card wide"><h3>Variacao Mensal (%) — Categorias Comparadas</h3><div id="comp-mom-linha" style="height:260px"></div></div>
      <div class="chart-card wide"><h3>Dispersao — Media Mensal vs Crescimento Total</h3><div id="comp-scatter" style="height:280px"></div></div>
    </div>

  </div>
</div>

<script>
const DB = {db_json};
const allLabels = DB.labels;
const allCats   = DB.categories;
const colors    = DB.colors;
const rawData   = DB.data;

let selStart = 0;
let selEnd   = allLabels.length - 1;
let selCats  = new Set(allCats);
let activeTab = 'visao-geral';

// ---- Date selects ----
const selStartEl = document.getElementById('sel-start');
const selEndEl   = document.getElementById('sel-end');
allLabels.forEach((l, i) => {{
  selStartEl.innerHTML += `<option value="${{i}}">${{l}}</option>`;
  selEndEl.innerHTML   += `<option value="${{i}}">${{l}}</option>`;
}});

// Padrao: Jan/2025 como inicio
const defaultStart = allLabels.indexOf('Jan/2025');
selStart = defaultStart >= 0 ? defaultStart : 0;
selStartEl.value = selStart;
selEndEl.value = allLabels.length - 1;
selStartEl.addEventListener('change', () => {{
  selStart = +selStartEl.value;
  if (selStart > selEnd) {{ selEnd = selStart; selEndEl.value = selEnd; }}
  updateFilterSummary(); update();
}});
selEndEl.addEventListener('change', () => {{
  selEnd = +selEndEl.value;
  if (selEnd < selStart) {{ selStart = selEnd; selStartEl.value = selStart; }}
  updateFilterSummary(); update();
}});

// ---- Filter toggle ----
let filterOpen = true;
function toggleFilter() {{
  filterOpen = !filterOpen;
  document.getElementById('filter-body').classList.toggle('hidden', !filterOpen);
  document.getElementById('filter-toggle-icon').classList.toggle('open', filterOpen);
}}

// ---- Cat search ----
function filterCatSearch(q) {{
  const term = q.toLowerCase();
  cbContainer.querySelectorAll('.cat-card').forEach(card => {{
    const name = card.getAttribute('data-cat').toLowerCase();
    card.classList.toggle('hidden-cat', term && !name.includes(term));
  }});
}}

// ---- Filter summary (pills) ----
function updateFilterSummary() {{
  const labels = allLabels.slice(selStart, selEnd+1);
  const cats   = allCats.filter(c => selCats.has(c));
  const n      = labels.length;
  let html = '';
  html += `<span class="filter-pill">&#128197; ${{allLabels[selStart]}} &#8594; ${{allLabels[selEnd]}} (${{n}} meses)</span>`;
  if (cats.length === allCats.length) {{
    html += `<span class="filter-pill">&#127991; Todas as categorias (${{cats.length}})</span>`;
  }} else {{
    cats.slice(0, 4).forEach(c => {{
      html += `<span class="filter-pill"><span class="dot" style="background:${{colors[c]}}"></span>${{c}}</span>`;
    }});
    if (cats.length > 4) html += `<span class="filter-pill">+${{cats.length - 4}} mais</span>`;
  }}
  document.getElementById('filter-summary').innerHTML = html;
}}

// ---- Cat counter ----
function updateCatCounter() {{
  const total = allCats.length;
  const sel   = allCats.filter(c => selCats.has(c)).length;
  document.getElementById('cat-counter').textContent = `${{sel}} / ${{total}} selecionadas`;
}}
// ---- Category checkboxes ----
const cbContainer = document.getElementById('cat-checkboxes');
allCats.forEach(cat => {{
  const card = document.createElement('label');
  card.className = 'cat-card active';
  card.setAttribute('data-cat', cat);
  card.style.setProperty('--cat-color', colors[cat]);
  card.innerHTML = `<input type="checkbox" checked>
    <div class="cat-card-left">
      <span class="cat-card-name">${{cat}}</span>
    </div>
    <div class="toggle-sw"></div>`;
  card.querySelector('input').addEventListener('change', e => {{
    if (e.target.checked) selCats.add(cat); else selCats.delete(cat);
    card.classList.toggle('active', e.target.checked);
    updateCatCounter(); updateFilterSummary(); update();
  }});
  cbContainer.appendChild(card);
}});
updateCatCounter();
updateFilterSummary();

function selectAllCats() {{
  selCats = new Set(allCats);
  cbContainer.querySelectorAll('input').forEach(cb => cb.checked = true);
  cbContainer.querySelectorAll('.cat-card').forEach(c => c.classList.add('active'));
  updateCatCounter(); updateFilterSummary(); update();
}}
function clearAllCats() {{
  selCats.clear();
  cbContainer.querySelectorAll('input').forEach(cb => cb.checked = false);
  cbContainer.querySelectorAll('.cat-card').forEach(c => c.classList.remove('active'));
  updateCatCounter(); updateFilterSummary(); update();
}}

// ---- Comparacao: checkboxes proprios ----
let compCats = new Set(allCats.slice(0, 3));  // top 3 por padrao
let escala   = 'abs';

const compContainer = document.getElementById('comp-cat-checks');
allCats.forEach(cat => {{
  const lbl = document.createElement('label');
  lbl.className = compCats.has(cat) ? 'checked' : '';
  lbl.setAttribute('data-cat', cat);
  lbl.innerHTML = `<input type="checkbox" ${{compCats.has(cat)?'checked':''}}><span style="width:9px;height:9px;border-radius:50%;background:${{colors[cat]}};display:inline-block;flex-shrink:0"></span>${{cat}}`;
  lbl.querySelector('input').addEventListener('change', e => {{
    if (e.target.checked) compCats.add(cat); else compCats.delete(cat);
    lbl.classList.toggle('checked', e.target.checked);
    if (activeTab === 'comparacao') update();
  }});
  compContainer.appendChild(lbl);
}});

function compSelAll()  {{
  compCats = new Set(allCats);
  compContainer.querySelectorAll('input').forEach(cb => cb.checked = true);
  compContainer.querySelectorAll('label').forEach(l => l.classList.add('checked'));
  if (activeTab === 'comparacao') update();
}}
function compSelNone() {{
  compCats.clear();
  compContainer.querySelectorAll('input').forEach(cb => cb.checked = false);
  compContainer.querySelectorAll('label').forEach(l => l.classList.remove('checked'));
  if (activeTab === 'comparacao') update();
}}
function compSelTop(k) {{
  const labels = allLabels.slice(selStart, selEnd + 1);
  const cats   = allCats.filter(c => selCats.has(c));
  const sliced = {{}};
  cats.forEach(c => {{ sliced[c] = rawData[c].slice(selStart, selEnd + 1); }});
  const catTotals = {{}};
  cats.forEach(c => {{ catTotals[c] = sliced[c].reduce((a,b)=>a+b,0); }});
  const top = [...cats].sort((a,b)=>catTotals[b]-catTotals[a]).slice(0, k);
  compCats = new Set(top);
  compContainer.querySelectorAll('label').forEach(l => {{
    const c = l.getAttribute('data-cat');
    const checked = compCats.has(c);
    l.querySelector('input').checked = checked;
    l.classList.toggle('checked', checked);
  }});
  if (activeTab === 'comparacao') update();
}}

document.querySelectorAll('input[name="escala"]').forEach(r => {{
  r.addEventListener('change', e => {{
    escala = e.target.value;
    document.querySelectorAll('#scale-abs,#scale-idx,#scale-pct').forEach(b => b.classList.remove('active'));
    e.target.closest('.method-btn').classList.add('active');
    if (activeTab === 'comparacao') update();
  }});
}});

// ---- Tab switching ----
function switchTab(name, btn) {{
  activeTab = name;
  document.querySelectorAll('.tab-page').forEach(p => p.classList.remove('active'));
  document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
  document.getElementById('tab-' + name).classList.add('active');
  btn.classList.add('active');
  update();
}}

// ---- Helpers ----
const fmt    = v => 'R$ ' + v.toLocaleString('pt-BR', {{minimumFractionDigits:0, maximumFractionDigits:0}});
const fmtPct = v => (v >= 0 ? '+' : '') + v.toFixed(1) + '%';
const medals = ['🥇','🥈','🥉'];

// ---- Helpers responsivos ----
const isMobile = () => window.innerWidth < 768;
function chartH(desktop, mobile=200) {{ return isMobile() ? mobile : desktop; }}
function layoutM(l=50, r=20, t=10, b=50) {{
  return isMobile()
    ? {{l: Math.max(10, l-20), r: Math.max(10, r), t: t, b: Math.max(30, b-15)}}
    : {{l, r, t, b}};
}}
// Redimensionar graficos ao girar tela
window.addEventListener('resize', () => {{ update(); }});

const cfg = {{responsive: true, displayModeBar: false}};

// ---- Tema (declarado antes de getLayout) ----
let isDark = false;

// ---- Layout base dinamico (respeita dark mode) ----
function getLayout() {{
  const dark = isDark;
  return {{
    paper_bgcolor: 'transparent',
    plot_bgcolor:  dark ? '#1a2535' : '#f8fafc',
    font: {{family: 'Segoe UI, Arial', size: 11, color: dark ? '#cbd5e1' : '#374151'}},
    hovermode: 'closest',
    xaxis: {{
      gridcolor:     dark ? '#2d3f55' : '#e5e7eb',
      linecolor:     dark ? '#2d3f55' : '#e5e7eb',
      zerolinecolor: dark ? '#2d3f55' : '#e5e7eb',
      tickfont:      {{color: dark ? '#94a3b8' : '#374151'}},
    }},
    yaxis: {{
      gridcolor:     dark ? '#2d3f55' : '#e5e7eb',
      linecolor:     dark ? '#2d3f55' : '#e5e7eb',
      zerolinecolor: dark ? '#2d3f55' : '#e5e7eb',
      tickfont:      {{color: dark ? '#94a3b8' : '#374151'}},
    }},
    hoverlabel: {{
      bgcolor:     dark ? '#1e3a5f' : '#fff',
      bordercolor: dark ? '#3b82f6' : '#e5e7eb',
      font:        {{color: dark ? '#e2e8f0' : '#1f2937', size: 12}},
    }},
    legend: {{
      bgcolor:     dark ? 'rgba(15,23,42,0.75)' : 'rgba(255,255,255,0.85)',
      bordercolor: dark ? '#334155' : '#e5e7eb',
      borderwidth: 1,
      font:        {{color: dark ? '#cbd5e1' : '#374151'}},
    }},
  }};
}}
let layout_base = getLayout();

// ---- Rank table sort state ----
let rankData = [];
let sortCol = 2; let sortDir = -1;

function sortRank(col) {{
  if (sortCol === col) sortDir *= -1; else {{ sortCol = col; sortDir = -1; }}
  document.querySelectorAll('.rank-tbl thead th').forEach((th, i) => {{
    th.classList.remove('asc','desc');
    if (i === col) th.classList.add(sortDir === 1 ? 'asc' : 'desc');
  }});
  renderRankTable();
}}

function renderRankTable() {{
  const sorted = [...rankData].sort((a, b) => {{
    let va = a[sortCol], vb = b[sortCol];
    if (typeof va === 'string') return sortDir * va.localeCompare(vb);
    return sortDir * (va - vb);
  }});
  const grandTotal = rankData.reduce((s, r) => s + r[2], 0);
  let html = '';
  sorted.forEach((r, idx) => {{
    const medal = idx < 3 ? medals[idx] : '';
    const barW  = grandTotal > 0 ? (r[2] / grandTotal * 100).toFixed(1) : 0;
    const momCls = r[7] >= 0 ? 'pos' : 'neg';
    const trend  = r[8] >= 0 ? '&#8599;' : '&#8600;';
    const trendCls = r[8] >= 0 ? 'pos' : 'neg';
    html += `<tr>
      <td class="c">${{medal || (idx+1)}}</td>
      <td><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${{r[9]}};margin-right:6px;vertical-align:middle"></span>${{r[1]}}</td>
      <td class="r">
        <span class="bar-inline" style="width:${{barW}}px;max-width:60px;background:${{r[9]}}"></span>
        ${{fmt(r[2])}}
      </td>
      <td class="c">${{r[3].toFixed(1)}}%</td>
      <td class="r">${{fmt(r[4])}}</td>
      <td class="c"><span style="font-size:.75rem;color:#64748b">${{r[5][0]}}</span><br><b>${{fmt(r[5][1])}}</b></td>
      <td class="c"><span style="font-size:.75rem;color:#64748b">${{r[6][0]}}</span><br><b style="color:#dc2626">${{fmt(r[6][1])}}</b></td>
      <td class="c ${{momCls}}">${{fmtPct(r[7])}}</td>
      <td class="c ${{trendCls}}">${{trend}}</td>
    </tr>`;
  }});
  document.getElementById('rank-tbl-body').innerHTML = html;
  // footer
  const sumTotal = rankData.reduce((s,r)=>s+r[2],0);
  const sumAvg   = rankData.reduce((s,r)=>s+r[4],0);
  document.getElementById('rank-tbl-foot').innerHTML =
    `<tr><td colspan="2">TOTAL</td><td class="r">${{fmt(sumTotal)}}</td><td class="c">100,0%</td><td class="r">${{fmt(sumAvg)}}</td><td colspan="4"></td></tr>`;
}}

// ---- Main update ----
function update() {{
  layout_base = getLayout();  // atualiza tema antes de renderizar
  const labels = allLabels.slice(selStart, selEnd + 1);
  const cats   = allCats.filter(c => selCats.has(c));
  const n      = labels.length;

  const sliced = {{}};
  cats.forEach(cat => {{ sliced[cat] = rawData[cat].slice(selStart, selEnd + 1); }});

  const totals = labels.map((_, i) => cats.reduce((s, c) => s + (sliced[c][i] || 0), 0));
  const catTotals = {{}};
  cats.forEach(cat => {{ catTotals[cat] = sliced[cat].reduce((a, b) => a + b, 0); }});
  const grandTotal  = totals.reduce((a, b) => a + b, 0);
  const avgMonthly  = n > 0 ? grandTotal / n : 0;
  const sortedCats  = [...cats].sort((a, b) => catTotals[b] - catTotals[a]);
  const lastTotal   = totals[n-1] || 0;
  const prevTotal   = n >= 2 ? totals[n-2] : 0;
  const momPct      = prevTotal ? (lastTotal - prevTotal) / prevTotal * 100 : 0;
  const bestCat     = sortedCats[0] || '—';

  // KPIs
  document.getElementById('kpi-total').textContent = fmt(grandTotal);
  document.getElementById('kpi-period').textContent = n > 0 ? labels[0] + ' a ' + labels[n-1] : '';
  document.getElementById('kpi-last').textContent  = fmt(lastTotal);
  document.getElementById('kpi-last-label').textContent = n > 0 ? labels[n-1] : '';
  const momEl = document.getElementById('kpi-mom');
  momEl.textContent = fmtPct(momPct);
  momEl.className = 'kpi-value ' + (momPct >= 0 ? 'pos' : 'neg');
  document.getElementById('kpi-mom-sub').textContent = n >= 2 ? labels[n-2] + ' → ' + labels[n-1] : '';
  document.getElementById('kpi-avg').textContent = fmt(avgMonthly);
  document.getElementById('kpi-cat-count').textContent = cats.length + ' cat. / ' + n + ' meses';
  document.getElementById('kpi-best-cat').textContent = bestCat;
  document.getElementById('kpi-best-val').textContent = bestCat !== '—' ? fmt(catTotals[bestCat]) : '';

  // ===== ABA VISAO GERAL =====
  if (activeTab === 'visao-geral') {{
    Plotly.react('chart-total', [{{
      x: labels, y: totals, type: 'scatter', mode: 'lines+markers',
      line: {{color:'#2563EB',width:3}}, marker:{{size:7,color:'#2563EB'}},
      fill:'tozeroy', fillcolor:'rgba(37,99,235,0.10)',
      hovertemplate:'<b>%{{x}}</b><br>Total: R$ %{{y:,.0f}}<extra></extra>',
    }}], {{ ...layout_base, margin:layoutM(70,20,10,50),
      xaxis:{{...layout_base.xaxis,tickangle:-35}},
      yaxis:{{...layout_base.yaxis,tickformat:',.0f',tickprefix:'R$ '}}, }}, cfg);

    const rankCats = [...sortedCats].reverse();
    const rankPcts = rankCats.map(c => grandTotal > 0 ? catTotals[c]/grandTotal*100 : 0);
    Plotly.react('chart-rank', [
      {{ type:'bar', orientation:'h', y:rankCats, x:rankCats.map(c=>catTotals[c]),
         marker:{{color:rankCats.map(c=>colors[c])}},
         hovertemplate:'<b>%{{y}}</b><br>R$ %{{x:,.0f}}<extra></extra>', xaxis:'x', yaxis:'y' }},
      {{ type:'scatter', mode:'text', y:rankCats, x:rankCats.map(c=>catTotals[c]),
         text:rankPcts.map(p=>'  '+p.toFixed(1)+'%'), textposition:'middle right',
         textfont:{{size:isMobile()?9:10,color:'#374151'}}, hoverinfo:'skip', showlegend:false, xaxis:'x', yaxis:'y' }}
    ], {{ ...layout_base, margin:layoutM(140,65,10,40), showlegend:false,
      xaxis:{{...layout_base.xaxis,tickformat:',.0f',tickprefix:'R$ '}},
      yaxis:{{...layout_base.yaxis,automargin:true,tickfont:{{size:isMobile()?8:10}}}}, }}, cfg);

    Plotly.react('chart-pie', [{{
      type:'pie', labels:sortedCats, values:sortedCats.map(c=>catTotals[c]),
      hole:0.42, marker:{{colors:sortedCats.map(c=>colors[c])}},
      textinfo:'percent',
      hovertemplate:'<b>%{{label}}</b><br>R$ %{{value:,.0f}}<br>%{{percent}}<extra></extra>',
    }}], {{ ...layout_base, margin:{{l:10,r:10,t:10,b:10}},
      legend:{{font:{{size:isMobile()?9:10}},orientation: isMobile()?'h':'v'}}, showlegend:true }}, cfg);

    const lineTraces = cats.map(cat => ({{
      x:labels, y:sliced[cat], type:'scatter', mode:'lines', name:cat,
      line:{{color:colors[cat],width:2}},
      hovertemplate:`<b>${{cat}}</b><br>%{{x}}<br>R$ %{{y:,.0f}}<extra></extra>`,
    }}));
    Plotly.react('chart-lines', lineTraces, {{ ...layout_base,
      margin:layoutM(70,20,10,50),
      xaxis:{{...layout_base.xaxis,tickangle:-35}},
      yaxis:{{...layout_base.yaxis,tickformat:',.0f',tickprefix:'R$ '}},
      legend:{{orientation:'h',y:-0.32,font:{{size:isMobile()?9:10}}}}, showlegend:true }}, cfg);
  }}

  // ===== ABA RANKING =====
  if (activeTab === 'ranking') {{
    // Preparar rankData
    rankData = cats.map(cat => {{
      const vals   = sliced[cat];
      const total  = catTotals[cat];
      const avg    = n > 0 ? total / n : 0;
      const pct    = grandTotal > 0 ? total / grandTotal * 100 : 0;
      const maxVal = Math.max(...vals); const maxIdx = vals.indexOf(maxVal);
      const minVal = Math.min(...vals.filter(v=>v>0) || [0]);
      const minIdx = vals.indexOf(minVal);
      const lv = vals[n-1] || 0; const pv = n>=2 ? vals[n-2] : 0;
      const mom = pv ? (lv-pv)/pv*100 : 0;
      // tendencia: slope simples
      const midX = (n-1)/2;
      const slope = vals.reduce((s,v,i)=>s+(i-midX)*v,0);
      return [0, cat, total, pct, avg,
              [labels[maxIdx]||'', maxVal],
              [labels[minIdx]||'', minVal],
              mom, slope, colors[cat]];
    }});
    // Adicionar posicao
    rankData.sort((a,b)=>b[2]-a[2]).forEach((r,i)=>r[0]=i+1);
    renderRankTable();

    // Grafico ranking2
    const rc2 = [...sortedCats].reverse();
    Plotly.react('chart-rank2', [
      {{ type:'bar', orientation:'h', y:rc2, x:rc2.map(c=>catTotals[c]),
         marker:{{color:rc2.map(c=>colors[c])}},
         hovertemplate:'<b>%{{y}}</b><br>R$ %{{x:,.0f}}<extra></extra>' }},
      {{ type:'scatter', mode:'text', y:rc2, x:rc2.map(c=>catTotals[c]),
         text:rc2.map(c=>'  '+(grandTotal>0?(catTotals[c]/grandTotal*100).toFixed(1):'0')+'%'),
         textposition:'middle right', textfont:{{size:isMobile()?8:10,color:'#374151'}},
         hoverinfo:'skip', showlegend:false }}
    ], {{ ...layout_base, margin:layoutM(150,70,10,40), showlegend:false,
      xaxis:{{...layout_base.xaxis,tickformat:',.0f',tickprefix:'R$ '}},
      yaxis:{{...layout_base.yaxis,automargin:true,tickfont:{{size:isMobile()?8:10}}}}, }}, cfg);

    // MoM
    if (n >= 2) {{
      const momVals   = cats.map(cat => {{
        const lv = sliced[cat][n-1]||0; const pv = sliced[cat][n-2]||0;
        return pv ? (lv-pv)/pv*100 : 0;
      }});
      Plotly.react('chart-mom', [{{
        type:'bar', x:cats, y:momVals,
        marker:{{color:momVals.map(v=>v>=0?'#16a34a':'#dc2626')}},
        hovertemplate:'<b>%{{x}}</b><br>Var: %{{y:.1f}}%<extra></extra>',
      }}], {{ ...layout_base, margin:layoutM(50,20,10,80),
        xaxis:{{...layout_base.xaxis,tickangle:-40,tickfont:{{size:isMobile()?8:9}}}},
        yaxis:{{...layout_base.yaxis,tickformat:'.1f',ticksuffix:'%'}},
        shapes:[{{type:'line',x0:0,x1:1,xref:'paper',y0:0,y1:0,yref:'y',
                  line:{{dash:'dash',color:'#94a3b8',width:1}}}}] }}, cfg);
    }}
  }}

  // ===== ABA PARTICIPACAO =====
  if (activeTab === 'participacao') {{
    const wrap = document.getElementById('table-pct-wrap');
    let th = '<thead><tr><th>Categoria</th>';
    labels.forEach(l => {{ th += `<th>${{l}}</th>`; }});
    th += '<th>Media</th></tr></thead>';
    let tb = '<tbody>';
    sortedCats.forEach(cat => {{
      const rowPcts = labels.map((_, i) => totals[i]>0?(sliced[cat][i]||0)/totals[i]*100:0);
      const avg = rowPcts.length ? rowPcts.reduce((a,b)=>a+b,0)/rowPcts.length : 0;
      const hex = colors[cat];
      tb += `<tr><td><span style="width:10px;height:10px;border-radius:50%;background:${{hex}};display:inline-block;flex-shrink:0"></span>${{cat}}</td>`;
      rowPcts.forEach(p => {{
        tb += `<td class="num"><div class="pct-cell">
          <div class="pct-bar-wrap"><div class="pct-bar" style="width:${{p.toFixed(1)}}%;background:${{hex}}"></div></div>
          <span class="pct-val">${{p.toFixed(1)}}%</span></div></td>`;
      }});
      tb += `<td class="num"><b>${{avg.toFixed(1)}}%</b></td></tr>`;
    }});
    tb += '</tbody>';
    let tf = '<tfoot><tr><td>TOTAL</td>';
    labels.forEach(()=>{{ tf += '<td>100,0%</td>'; }});
    tf += '<td>100,0%</td></tr></tfoot>';
    wrap.innerHTML = `<table class="pct-table">${{th}}${{tb}}${{tf}}</table>`;
  }}

  // ===== ABA PROJECAO =====
  if (activeTab === 'projecao') {{
    updateProjecao(allLabels, cats, n);
  }}

  // ===== ABA COMPARACAO =====
  if (activeTab === 'comparacao') {{
    const cCats = allCats.filter(c => compCats.has(c) && selCats.has(c));
    if (cCats.length < 1) {{
      ['comp-linha','comp-bar-total','comp-radar','comp-mom-linha','comp-scatter']
        .forEach(id => Plotly.react(id, [], {{...layout_base}}, cfg));
      document.getElementById('comp-kpis').innerHTML = '<div style="color:#94a3b8;padding:8px">Selecione ao menos uma categoria para comparar.</div>';
      return;
    }}
    const cSliced = {{}};
    cCats.forEach(c => {{ cSliced[c] = rawData[c].slice(selStart, selEnd+1); }});
    const cTotals = {{}};
    cCats.forEach(c => {{ cTotals[c] = cSliced[c].reduce((a,b)=>a+b,0); }});
    const cAvgs  = {{}};
    cCats.forEach(c => {{ cAvgs[c]  = n > 0 ? cTotals[c]/n : 0; }});
    const grandC = Object.values(cTotals).reduce((a,b)=>a+b,0);

    // KPIs comparativos
    const kpiHtml = cCats.map(c => {{
      const lv  = cSliced[c][n-1]||0;
      const pv  = n>=2 ? cSliced[c][n-2] : 0;
      const mom = pv ? (lv-pv)/pv*100 : 0;
      const pct = grandC ? cTotals[c]/grandC*100 : 0;
      return `<div class="proj-kpi" style="border-color:${{colors[c]}}">
        <div class="kpi-label" style="display:flex;align-items:center;gap:5px">
          <span style="width:10px;height:10px;border-radius:50%;background:${{colors[c]}};display:inline-block"></span>${{c}}
        </div>
        <div class="kpi-value" style="color:${{colors[c]}};font-size:1.05rem">${{fmt(cTotals[c])}}</div>
        <div class="kpi-sub">${{pct.toFixed(1)}}% do grupo &nbsp;|&nbsp;
          <span class="${{mom>=0?'pos':'neg'}}">${{fmtPct(mom)}} MoM</span>
        </div>
      </div>`;
    }}).join('');
    document.getElementById('comp-kpis').innerHTML = kpiHtml;

    // Preparar serie Y conforme escala
    function getY(cat) {{
      const v = cSliced[cat];
      if (escala === 'abs') return v;
      if (escala === 'idx') {{
        const base = v.find(x=>x>0) || 1;
        return v.map(x => +(x/base*100).toFixed(2));
      }}
      if (escala === 'pct') {{
        const base = v.find(x=>x>0) || 1;
        return v.map(x => +((x-base)/base*100).toFixed(2));
      }}
      return v;
    }}
    const yLabel  = escala==='abs' ? 'R$' : escala==='idx' ? 'Indice (base 100)' : 'Var. Acum. %';
    const yFmt    = escala==='abs' ? ',.0f' : '.1f';
    const yPrefix = escala==='abs' ? 'R$ ' : '';
    const ySuffix = escala!=='abs' ? (escala==='idx'?'':' %') : '';

    // 1. Linha comparativa
    const linhaTraces = cCats.map(c => ({{
      x:labels, y:getY(c), type:'scatter', mode:'lines+markers', name:c,
      line:{{color:colors[c],width:2.5}}, marker:{{size:6}},
      hovertemplate:`<b>${{c}}</b><br>%{{x}}<br>${{yPrefix}}%{{y:${{yFmt}}}}${{ySuffix}}<extra></extra>`,
    }}));
    Plotly.react('comp-linha', linhaTraces, {{...layout_base,
      margin:layoutM(70,20,10,50),
      xaxis:{{...layout_base.xaxis,tickangle:-35}},
      yaxis:{{...layout_base.yaxis,tickformat:yFmt,tickprefix:yPrefix,ticksuffix:ySuffix}},
      legend:{{orientation:'h',y:-0.28,font:{{size:isMobile()?9:11}}}}, showlegend:true }}, cfg);

    // 2. Barras total
    const sortedC = [...cCats].sort((a,b)=>cTotals[b]-cTotals[a]);
    Plotly.react('comp-bar-total', [{{
      type:'bar', x:sortedC, y:sortedC.map(c=>cTotals[c]),
      marker:{{color:sortedC.map(c=>colors[c])}},
      text:sortedC.map(c=>(grandC?cTotals[c]/grandC*100:0).toFixed(1)+'%'),
      textposition:'outside', textfont:{{size:isMobile()?9:11}},
      hovertemplate:'<b>%{{x}}</b><br>R$ %{{y:,.0f}}<extra></extra>',
    }}], {{...layout_base,
      margin:layoutM(70,20,30,70),
      xaxis:{{...layout_base.xaxis,tickangle:-30,tickfont:{{size:isMobile()?8:10}}}},
      yaxis:{{...layout_base.yaxis,tickformat:',.0f',tickprefix:'R$ '}},
      showlegend:false }}, cfg);

    // 3. Radar
    const radarCats = cCats;
    // normalizar 5 indicadores: total, avg, max, crescimento, participacao
    function norm(vals2, v) {{
      const mn=Math.min(...vals2), mx=Math.max(...vals2);
      return mx>mn ? (v-mn)/(mx-mn)*100 : 50;
    }}
    const rTotals = radarCats.map(c=>cTotals[c]);
    const rAvgs   = radarCats.map(c=>cAvgs[c]);
    const rMaxs   = radarCats.map(c=>Math.max(...cSliced[c]));
    const rGrowth = radarCats.map(c=>{{
      const v=cSliced[c]; const b=v.find(x=>x>0)||1;
      return (v[n-1]||0)/b*100;
    }});
    const rPcts   = radarCats.map(c=>grandC?cTotals[c]/grandC*100:0);

    const radarTraces = radarCats.map((c,i) => ({{
      type:'scatterpolar', name:c,
      r:[norm(rTotals,rTotals[i]), norm(rAvgs,rAvgs[i]), norm(rMaxs,rMaxs[i]),
         norm(rGrowth,rGrowth[i]), norm(rPcts,rPcts[i]), norm(rTotals,rTotals[i])],
      theta:['Total','Media Mensal','Pico','Crescimento','Participacao','Total'],
      fill:'toself', fillcolor:colors[c].replace(')',',0.15)').replace('rgb','rgba'),
      line:{{color:colors[c],width:2}},
      hovertemplate:`<b>${{c}}</b><br>%{{theta}}: %{{r:.0f}}<extra></extra>`,
    }}));
    Plotly.react('comp-radar', radarTraces, {{
      ...layout_base,
      polar:{{ bgcolor: isDark?'#162032':'#f8fafc',
        angularaxis:{{linecolor:'#e5e7eb',gridcolor:'#e5e7eb'}},
        radialaxis:{{visible:true,range:[0,100],gridcolor:'#e5e7eb',tickfont:{{size:9}}}}
      }},
      showlegend:true, legend:{{orientation:'h',y:-0.15,font:{{size:10}}}},
      margin:{{l:50,r:50,t:30,b:50}},
    }}, cfg);

    // 4. Variacao MoM por mes (linhas)
    const momTraces = cCats.map(c => {{
      const moms = cSliced[c].map((v,i) => {{
        if (i===0) return 0;
        const prev = cSliced[c][i-1];
        return prev>0 ? +((v-prev)/prev*100).toFixed(2) : 0;
      }});
      return {{
        x:labels, y:moms, type:'scatter', mode:'lines+markers', name:c,
        line:{{color:colors[c],width:2}}, marker:{{size:5}},
        hovertemplate:`<b>${{c}}</b><br>%{{x}}<br>%{{y:+.1f}}%<extra></extra>`,
      }};
    }});
    momTraces.push({{ x:labels, y:labels.map(()=>0), type:'scatter', mode:'lines',
      line:{{color:'#94a3b8',width:1,dash:'dash'}}, hoverinfo:'skip', showlegend:false }});
    Plotly.react('comp-mom-linha', momTraces, {{...layout_base,
      margin:layoutM(55,20,10,50),
      xaxis:{{...layout_base.xaxis,tickangle:-35}},
      yaxis:{{...layout_base.yaxis,tickformat:'.1f',ticksuffix:'%'}},
      legend:{{orientation:'h',y:-0.3,font:{{size:isMobile()?9:10}}}}, showlegend:true }}, cfg);

    // 5. Scatter: media vs crescimento
    const scatterX = cCats.map(c=>cAvgs[c]);
    const scatterY = cCats.map(c=>{{
      const v=cSliced[c]; const b=v.find(x=>x>0)||1;
      return +((( v[n-1]||0)-b)/b*100).toFixed(2);
    }});
    const scatterSize = cCats.map(c=> 20 + (grandC>0?cTotals[c]/grandC*80:10));
    Plotly.react('comp-scatter', [{{
      type:'scatter', mode:'markers+text',
      x:scatterX, y:scatterY,
      text:cCats, textposition:'top center', textfont:{{size:isMobile()?9:10}},
      marker:{{color:cCats.map(c=>colors[c]), size:scatterSize, opacity:0.85,
               line:{{color:'#fff',width:1.5}}}},
      hovertemplate:'<b>%{{text}}</b><br>Media: R$ %{{x:,.0f}}<br>Cresc: %{{y:+.1f}}%<extra></extra>',
    }}], {{...layout_base,
      margin:layoutM(70,30,20,60),
      xaxis:{{...layout_base.xaxis,title:'Media Mensal (R$)',titlefont:{{size:isMobile()?10:12}},tickformat:',.0f',tickprefix:'R$ '}},
      yaxis:{{...layout_base.yaxis,title:'Crescimento Total (%)',titlefont:{{size:isMobile()?10:12}},tickformat:'.1f',ticksuffix:'%'}},
      shapes:[{{type:'line',x0:0,x1:1,xref:'paper',y0:0,y1:0,yref:'y',line:{{dash:'dash',color:'#94a3b8',width:1}}}}],
      showlegend:false }}, cfg);
  }}
}}

// ---- Projecao State ----
let adjPct = 0;
let metodo = 'mm3';

// Ouvintes dos radio buttons
document.querySelectorAll('input[name="metodo"]').forEach(r => {{
  r.addEventListener('change', e => {{
    metodo = e.target.value;
    document.querySelectorAll('.method-btn').forEach(b => b.classList.remove('active'));
    e.target.closest('.method-btn').classList.add('active');
    if (activeTab === 'projecao') update();
  }});
}});

function adjChange(v) {{
  adjPct = +v;
  document.getElementById('adj-val').textContent = (v >= 0 ? '+' : '') + v + '%';
  document.getElementById('proj-kpi-adj').textContent = 'ajuste: ' + (v >= 0 ? '+' : '') + v + '%';
  if (activeTab === 'projecao') update();
}}

function calcProj(vals, n, m) {{
  // vals = array completo (allLabels length), n = meses selecionados (ate selEnd)
  // usa os dados ate selEnd para calcular projecao
  const serie = vals.slice(selStart, selEnd + 1).filter(v => v > 0);
  const len = serie.length;
  if (len === 0) return 0;
  if (m === 'mm3') {{
    const k = Math.min(3, len);
    return serie.slice(-k).reduce((a,b)=>a+b,0) / k;
  }}
  if (m === 'mm6') {{
    const k = Math.min(6, len);
    return serie.slice(-k).reduce((a,b)=>a+b,0) / k;
  }}
  if (m === 'trend') {{
    // regressao linear simples
    const xs = serie.map((_,i) => i);
    const mx = xs.reduce((a,b)=>a+b,0)/xs.length;
    const my = serie.reduce((a,b)=>a+b,0)/serie.length;
    const num = xs.reduce((s,x,i)=>s+(x-mx)*(serie[i]-my),0);
    const den = xs.reduce((s,x)=>s+(x-mx)**2,0);
    const slope = den ? num/den : 0;
    return Math.max(0, my + slope * (len - mx));
  }}
  if (m === 'yoy') {{
    // mesmo mes do ano anterior = indice - 12 no array original
    const origIdx = selEnd - 12;
    if (origIdx >= 0 && origIdx < vals.length && vals[origIdx] > 0) return vals[origIdx];
    // fallback: mm3
    const k = Math.min(3, len);
    return serie.slice(-k).reduce((a,b)=>a+b,0) / k;
  }}
  return 0;
}}

function nextMonthLabel(label) {{
  const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  const ptMonths = ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'];
  const parts = label.split('/');
  let m = months.indexOf(parts[0]); if (m < 0) m = ptMonths.indexOf(parts[0]);
  let y = +parts[1];
  m++; if (m > 11) {{ m = 0; y++; }}
  return ptMonths[m] + '/' + y;
}}

function updateProjecao(allLabels, cats, n) {{
  // Usar dados completos (allLabels) para calculo YoY
  const allN = allLabels.length;
  const nextLbl = nextMonthLabel(allLabels[selEnd]);
  const metodNames = {{ mm3:'Media Movel 3M', mm6:'Media Movel 6M', trend:'Tendencia Linear', yoy:'Mesmo Mes Ano Ant.' }};

  const projVals = {{}};
  cats.forEach(cat => {{
    const raw = calcProj(rawData[cat], n, metodo);
    projVals[cat] = Math.max(0, raw * (1 + adjPct/100));
  }});

  const totalProj = Object.values(projVals).reduce((a,b)=>a+b,0);
  const totalLast = cats.reduce((s,c) => s + (rawData[c][selEnd]||0), 0);
  const momProj   = totalLast ? (totalProj - totalLast) / totalLast * 100 : 0;
  const mm3Total  = cats.reduce((s,c) => {{
    const serie = rawData[c].slice(selStart, selEnd+1).filter(v=>v>0);
    const k = Math.min(3, serie.length);
    return s + (serie.slice(-k).reduce((a,b)=>a+b,0) / (k||1));
  }}, 0);

  // KPIs
  document.getElementById('proj-kpi-total').textContent = fmt(totalProj);
  document.getElementById('proj-kpi-mes').textContent   = 'Projecao para ' + nextLbl;
  const momPEl = document.getElementById('proj-kpi-mom');
  momPEl.textContent = fmtPct(momProj);
  momPEl.className = 'kpi-value ' + (momProj >= 0 ? 'pos' : 'neg');
  document.getElementById('proj-kpi-mm3').textContent    = fmt(mm3Total);
  document.getElementById('proj-kpi-metodo').textContent = metodNames[metodo];

  // ----- Grafico linha historico + projecao -----
  const histLabels = allLabels.slice(selStart, selEnd+1);
  const lineProj = cats.map(cat => {{
    const histVals = rawData[cat].slice(selStart, selEnd+1);
    const pv = projVals[cat];
    return {{
      x:[...histLabels, nextLbl], y:[...histVals, pv],
      type:'scatter', mode:'lines+markers', name:cat,
      line:{{color:colors[cat],width:2}},
      marker:{{size:5}},
      hovertemplate:`<b>${{cat}}</b><br>%{{x}}<br>R$ %{{y:,.0f}}<extra></extra>`,
    }};
  }});
  // linha vertical separando real de projecao
  lineProj.push({{
    x:[allLabels[selEnd], allLabels[selEnd]], y:[0, totalProj*1.1],
    type:'scatter', mode:'lines', name:'Inicio Proj.',
    line:{{color:'#f59e0b',width:2,dash:'dot'}},
    hoverinfo:'skip', showlegend:true,
  }});
  Plotly.react('chart-proj-linha', lineProj, {{ ...layout_base,
    margin:layoutM(70,20,10,60),
    xaxis:{{...layout_base.xaxis,tickangle:-35}},
    yaxis:{{...layout_base.yaxis,tickformat:',.0f',tickprefix:'R$ '}},
    legend:{{orientation:'h',y:-0.35,font:{{size:isMobile()?9:10}}}}, showlegend:true }}, cfg);

  // ----- Grafico barras projecao -----
  const sortedByProj = [...cats].sort((a,b)=>projVals[b]-projVals[a]);
  Plotly.react('chart-proj-bar', [{{
    type:'bar', orientation:'h',
    y:[...sortedByProj].reverse(),
    x:[...sortedByProj].reverse().map(c=>projVals[c]),
    marker:{{color:[...sortedByProj].reverse().map(c=>colors[c]),
             opacity:0.85,
             line:{{color:'#f59e0b',width:1.5}}}},
    hovertemplate:'<b>%{{y}}</b><br>Proj: R$ %{{x:,.0f}}<extra></extra>',
  }}], {{ ...layout_base, margin:layoutM(140,65,10,40), showlegend:false,
    xaxis:{{...layout_base.xaxis,tickformat:',.0f',tickprefix:'R$ '}},
    yaxis:{{...layout_base.yaxis,automargin:true,tickfont:{{size:isMobile()?8:10}}}}, }}, cfg);

  // ----- Grafico comparativo real vs projecao -----
  Plotly.react('chart-proj-comp', [
    {{ type:'bar', name:'Ultimo Real', x:cats,
       y:cats.map(c=>rawData[c][selEnd]||0),
       marker:{{color:cats.map(c=>colors[c]),opacity:0.6}},
       hovertemplate:'<b>%{{x}}</b><br>Real: R$ %{{y:,.0f}}<extra></extra>' }},
    {{ type:'bar', name:'Projecao', x:cats,
       y:cats.map(c=>projVals[c]),
       marker:{{color:cats.map(c=>colors[c]),
                line:{{color:'#f59e0b',width:2}},opacity:1}},
       hovertemplate:'<b>%{{x}}</b><br>Proj: R$ %{{y:,.0f}}<extra></extra>' }},
  ], {{ ...layout_base, barmode:'group',
    margin:layoutM(60,20,10,80),
    xaxis:{{...layout_base.xaxis,tickangle:-40,tickfont:{{size:isMobile()?8:9}}}},
    yaxis:{{...layout_base.yaxis,tickformat:',.0f',tickprefix:'R$ '}},
    legend:{{orientation:'h',y:-0.35,font:{{size:isMobile()?9:10}}}}, showlegend:true }}, cfg);

  // ----- Tabela -----
  const grandProj = totalProj;
  let tb = '';
  [...cats].sort((a,b)=>projVals[b]-projVals[a]).forEach((cat,i) => {{
    const pv  = projVals[cat];
    const lv  = rawData[cat][selEnd] || 0;
    const var_ = lv ? (pv-lv)/lv*100 : 0;
    const pct = grandProj ? pv/grandProj*100 : 0;
    const varCls = var_ >= 0 ? 'pos' : 'neg';
    const sigma = pv * 0.08; // intervalo simples +/-8%
    tb += `<tr>
      <td><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${{colors[cat]}};margin-right:6px;vertical-align:middle"></span>${{cat}}</td>
      <td class="r">${{fmt(lv)}}</td>
      <td class="r" style="font-weight:700;color:#b45309">${{fmt(pv)}}</td>
      <td class="c ${{varCls}}">${{fmtPct(var_)}}</td>
      <td class="c">
        <div style="display:inline-flex;align-items:center;gap:5px">
          <div style="width:${{Math.round(pct*2)}}px;max-width:60px;height:8px;background:${{colors[cat]}};border-radius:4px"></div>
          ${{pct.toFixed(1)}}%
        </div>
      </td>
      <td class="r" style="color:#64748b">${{fmt(pv-sigma)}}</td>
      <td class="r" style="color:#64748b">${{fmt(pv+sigma)}}</td>
    </tr>`;
  }});
  document.getElementById('proj-tbl-body').innerHTML = tb;

  const totalLvSum = cats.reduce((s,c)=>s+(rawData[c][selEnd]||0),0);
  const varTot = totalLvSum ? (grandProj-totalLvSum)/totalLvSum*100 : 0;
  const varTotCls = varTot >= 0 ? 'pos' : 'neg';
  document.getElementById('proj-tbl-foot').innerHTML =
    `<tr><td><b>TOTAL</b></td><td class="r"><b>${{fmt(totalLvSum)}}</b></td>
     <td class="r" style="color:#b45309;font-weight:700">${{fmt(grandProj)}}</td>
     <td class="c ${{varTotCls}}"><b>${{fmtPct(varTot)}}</b></td>
     <td class="c">100,0%</td><td colspan="2"></td></tr>`;
}}

// ---- Theme ----
function toggleTheme() {{
  isDark = !isDark;
  document.body.classList.toggle('dark', isDark);
  document.getElementById('theme-icon').innerHTML   = isDark ? '&#9728;' : '&#9790;';
  document.getElementById('theme-label').textContent = isDark ? 'Modo Dia' : 'Modo Noite';
  // Rerenderiza todos os graficos com o novo layout
  update();
}}

// ---- Exportar Excel ----
function exportExcel() {{
  const wb = XLSX.utils.book_new();
  const labels = allLabels.slice(selStart, selEnd + 1);
  const cats   = allCats.filter(c => selCats.has(c));
  const n      = labels.length;

  const sliced = {{}};
  cats.forEach(cat => {{ sliced[cat] = rawData[cat].slice(selStart, selEnd + 1); }});
  const totals = labels.map((_, i) => cats.reduce((s,c) => s + (sliced[c][i]||0), 0));
  const catTotals = {{}};
  cats.forEach(cat => {{ catTotals[cat] = sliced[cat].reduce((a,b)=>a+b,0); }});
  const grandTotal = totals.reduce((a,b)=>a+b,0);

  // ---- Aba 1: Dados ----
  const dadosRows = [['Categoria', ...labels, 'TOTAL', 'MEDIA MENSAL', 'PARTICIPACAO %']];
  cats.forEach(cat => {{
    const vals  = sliced[cat];
    const total = catTotals[cat];
    const avg   = n > 0 ? total / n : 0;
    const pct   = grandTotal > 0 ? total / grandTotal * 100 : 0;
    dadosRows.push([cat, ...vals, total, +avg.toFixed(2), +(pct.toFixed(2))]);
  }});
  dadosRows.push(['TOTAL', ...totals, grandTotal, +(grandTotal/n).toFixed(2), 100]);
  const wsDados = XLSX.utils.aoa_to_sheet(dadosRows);
  // Largura colunas
  wsDados['!cols'] = [{{wch:22}}, ...labels.map(()=>({{wch:13}})), {{wch:14}}, {{wch:16}}, {{wch:16}}];
  XLSX.utils.book_append_sheet(wb, wsDados, 'Dados');

  // ---- Aba 2: Participacao % ----
  const pctRows = [['Categoria', ...labels, 'MEDIA %']];
  cats.forEach(cat => {{
    const rowPcts = labels.map((_, i) => totals[i]>0 ? +((sliced[cat][i]||0)/totals[i]*100).toFixed(2) : 0);
    const avg = rowPcts.length ? +(rowPcts.reduce((a,b)=>a+b,0)/rowPcts.length).toFixed(2) : 0;
    pctRows.push([cat, ...rowPcts, avg]);
  }});
  pctRows.push(['TOTAL', ...labels.map(()=>100), 100]);
  const wsPct = XLSX.utils.aoa_to_sheet(pctRows);
  wsPct['!cols'] = [{{wch:22}}, ...labels.map(()=>({{wch:10}})), {{wch:10}}];
  XLSX.utils.book_append_sheet(wb, wsPct, 'Participacao %');

  // ---- Aba 3: Ranking ----
  const sortedCats = [...cats].sort((a,b) => catTotals[b] - catTotals[a]);
  const rankRows = [['#','Categoria','Total Periodo','Participacao %','Media Mensal','Melhor Mes','Valor Max','Pior Mes','Valor Min','Var. MoM %']];
  sortedCats.forEach((cat, i) => {{
    const vals   = sliced[cat];
    const total  = catTotals[cat];
    const avg    = n > 0 ? total/n : 0;
    const pct    = grandTotal > 0 ? total/grandTotal*100 : 0;
    const maxV   = Math.max(...vals); const maxI = vals.indexOf(maxV);
    const minV   = Math.min(...vals.filter(v=>v>0)||[0]); const minI = vals.indexOf(minV);
    const lv = vals[n-1]||0; const pv = n>=2 ? vals[n-2] : 0;
    const mom = pv ? +((lv-pv)/pv*100).toFixed(2) : 0;
    rankRows.push([i+1, cat, +total.toFixed(2), +pct.toFixed(2), +avg.toFixed(2),
                   labels[maxI]||'', +maxV.toFixed(2),
                   labels[minI]||'', +minV.toFixed(2), mom]);
  }});
  const wsRank = XLSX.utils.aoa_to_sheet(rankRows);
  wsRank['!cols'] = [{{wch:4}},{{wch:22}},{{wch:16}},{{wch:16}},{{wch:14}},{{wch:12}},{{wch:14}},{{wch:12}},{{wch:14}},{{wch:12}}];
  XLSX.utils.book_append_sheet(wb, wsRank, 'Ranking');

  // ---- Aba 4: Projecao ----
  function calcProjLocal(vals, m) {{
    const serie = vals.slice(selStart, selEnd+1).filter(v=>v>0);
    const len = serie.length; if (!len) return 0;
    if (m==='mm3') {{ const k=Math.min(3,len); return serie.slice(-k).reduce((a,b)=>a+b,0)/k; }}
    if (m==='mm6') {{ const k=Math.min(6,len); return serie.slice(-k).reduce((a,b)=>a+b,0)/k; }}
    if (m==='trend') {{
      const xs=serie.map((_,i)=>i), mx=xs.reduce((a,b)=>a+b,0)/xs.length;
      const my=serie.reduce((a,b)=>a+b,0)/serie.length;
      const num=xs.reduce((s,x,i)=>s+(x-mx)*(serie[i]-my),0);
      const den=xs.reduce((s,x)=>s+(x-mx)**2,0);
      return Math.max(0, my+(den?num/den:0)*(len-mx));
    }}
    if (m==='yoy') {{
      const oi=selEnd-12;
      if (oi>=0&&oi<vals.length&&vals[oi]>0) return vals[oi];
      const k=Math.min(3,len); return serie.slice(-k).reduce((a,b)=>a+b,0)/k;
    }}
    return 0;
  }}
  const methods = ['mm3','mm6','trend','yoy'];
  const mNames  = ['Media Movel 3M','Media Movel 6M','Tendencia Linear','Mesmo Mes Ano Ant.'];
  const nextLblX = (lbl => {{
    const pt=['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'];
    const [m,y] = lbl.split('/'); let mi=pt.indexOf(m),yr=+y; mi++; if(mi>11){{mi=0;yr++;}} return pt[mi]+'/'+yr;
  }})(allLabels[selEnd]);
  const projHeader = ['Categoria','Ultimo Real', ...mNames.map(n=>'Proj '+n), 'Var. MM3 %'];
  const projRows = [projHeader];
  cats.forEach(cat => {{
    const lv = rawData[cat][selEnd]||0;
    const projs = methods.map(m => +calcProjLocal(rawData[cat], m).toFixed(2));
    const var3  = lv ? +((projs[0]-lv)/lv*100).toFixed(2) : 0;
    projRows.push([cat, +lv.toFixed(2), ...projs, var3]);
  }});
  const totalLv = cats.reduce((s,c)=>s+(rawData[c][selEnd]||0),0);
  const totalProjs = methods.map((_,mi) => cats.reduce((s,c)=>s+calcProjLocal(rawData[c],methods[mi]),0));
  projRows.push(['TOTAL', +totalLv.toFixed(2), ...totalProjs.map(v=>+v.toFixed(2)),
                 totalLv ? +((totalProjs[0]-totalLv)/totalLv*100).toFixed(2) : 0]);
  const wsProj = XLSX.utils.aoa_to_sheet(projRows);
  wsProj['!cols'] = [{{wch:22}},{{wch:14}},{{wch:16}},{{wch:16}},{{wch:18}},{{wch:20}},{{wch:12}}];
  // Nota de periodo
  XLSX.utils.sheet_add_aoa(wsProj, [[`Projecao para: ${{nextLblX}} | Periodo base: ${{labels[0]}} a ${{labels[n-1]}}`]], {{origin: projRows.length + 2}});
  XLSX.utils.book_append_sheet(wb, wsProj, 'Projecao');

  // Download
  const date = new Date().toISOString().slice(0,10);
  XLSX.writeFile(wb, `Evolucao_Categorias_${{date}}.xlsx`);
}}

update();
</script>
</body>
</html>"""

out = r'D:\Evolução categorias\dashboard_categorias.html'
with open(out, 'w', encoding='utf-8') as f:
    f.write(html)
print('Dashboard salvo em:', out)
