  :root{
    color-scheme: light;
    /* ---- Paleta de marca ----
       Cuatro tonos de la misma familia. Sirven para el cromo de la interfaz (cabecera,
       pestañas, botones, énfasis de la marca propia), NO como paleta de series: entre
       #9CAC54 y #97CD97 hay ΔE 9,9 a visión normal —por debajo del piso de 15— y los dos
       claros no alcanzan 2:1 de contraste sobre blanco. La identidad de las marcas en las
       gráficas sigue en la paleta categórica del JS, que sí está validada.
       Contraste de texto medido: sobre --brand-900 va texto blanco (7,7:1); sobre los
       otros tres, texto oscuro (7,2 / 13,7 / 9,8:1). */
    --brand-900:#345C32;   /* primario: botones, pestaña activa, marca propia */
    --brand-500:#9CAC54;   /* secundario: acentos, bordes de énfasis */
    --brand-300:#97CD97;   /* tinte positivo: fila de marca propia */
    --brand-100:#A7F0DD;   /* tinte más claro: hover y selección */
    --brand-900-hover:#26471f;
    --brand-tint:#eef7ee;  /* fondo de selección derivado, para superficies grandes */
  }
  *{box-sizing:border-box;}
  body{
    margin:0; padding:24px; background:#f8fafc; color:#0f172a;
    font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  }
  h1{font-size:20px;margin:0 0 4px;}
  h2{font-size:16px;margin:28px 0 12px;color:#1e293b;}
  h3{font-size:13px;margin:0 0 10px;color:#475569;text-transform:uppercase;letter-spacing:.04em;}
  .subtitle{color:#64748b;font-size:13px;margin:0 0 20px;}
  .card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:18px;margin-bottom:18px;}
  .row{display:flex;gap:16px;flex-wrap:wrap;align-items:flex-end;}
  .upload-zone{
    border:2px dashed #cbd5e1;border-radius:12px;padding:36px 18px;text-align:center;
    background:#fff;cursor:pointer;transition:.15s;
  }
  .upload-zone.dragover{border-color:var(--brand-900);background:var(--brand-tint);}
  .upload-zone p{margin:6px 0;color:#475569;font-size:13px;}
  .upload-zone .big{font-size:14px;color:#1e293b;font-weight:600;}
  .btn{
    border:none;border-radius:8px;padding:9px 16px;font-size:13px;font-weight:600;
    cursor:pointer;background:#e2e8f0;color:#1e293b;
  }
  .btn-primary{background:var(--brand-900);color:#fff;}
  .btn-primary:hover{background:var(--brand-900-hover);}
  .btn-ghost{background:transparent;border:1px solid #cbd5e1;}
  .btn:disabled{opacity:.5;cursor:not-allowed;}
  label{font-size:12px;font-weight:600;color:#475569;display:block;margin-bottom:4px;}
  select,input[type=number],input[type=text]{
    border:1px solid #cbd5e1;border-radius:8px;padding:7px 10px;font-size:13px;background:#fff;min-width:160px;
  }
  .validation-box{border-radius:10px;padding:12px 14px;font-size:13px;margin-top:14px;}
  .validation-ok{background:#f0fdf4;border:1px solid #bbf7d0;color:#166534;}
  .validation-error{background:#fef2f2;border:1px solid #fecaca;color:#991b1b;}
  .col-list{font-family:ui-monospace,Menlo,monospace;font-size:11px;background:#fff5f5;padding:6px 8px;border-radius:6px;display:inline-block;margin:2px 4px 2px 0;}
  .hidden{display:none !important;}
  /* Diez tiles idénticos no son una lectura: hay una cifra que resume la categoría y
     nueve que la matizan. El titular la separa; las que la fuente no puede calcular
     bajan a una nota al pie en vez de ocupar un tile en blanco. */
  .kpi-hero{margin-bottom:14px;}
  .kpi-hero .val{font-size:34px;font-weight:700;color:#0f172a;line-height:1.15;}
  .kpi-hero .lbl{font-size:12px;color:#64748b;margin-top:2px;}
  .kpi-na{
    margin-top:12px;padding:9px 12px;background:#f8fafc;border-radius:8px;
    font-size:11.5px;color:#64748b;line-height:1.5;
  }
  .kpi-na strong{color:#475569;font-weight:600;}
  .kpi-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;}
  .kpi-card{background:#fff;border:1px solid #e2e8f0;border-radius:10px;padding:14px;}
  .kpi-card .val{font-size:20px;font-weight:700;color:#0f172a;}
  .kpi-card .lbl{font-size:11px;color:#64748b;margin-top:2px;}
  .kpi-card.inval-yes{border-color:var(--brand-300);background:var(--brand-tint);}
  .kpi-card.inval-no{border-color:#fde68a;background:#fffbeb;}
  table.data-table{width:100%;border-collapse:collapse;font-size:12.5px;}
  table.data-table th{
    text-align:left;border-bottom:2px solid #e2e8f0;padding:8px 10px;color:#475569;
    cursor:pointer;user-select:none;white-space:nowrap;
  }
  table.data-table th:hover{color:var(--brand-900);}
  table.data-table td{padding:7px 10px;border-bottom:1px solid #f1f5f9;white-space:nowrap;}
  table.data-table tr.brand-inval td{background:var(--brand-tint);font-weight:600;box-shadow:inset 3px 0 0 var(--brand-900);}
  table.data-table tr.subtotal-row td{background:#f1f5f9;font-weight:700;border-top:2px solid #cbd5e1;border-bottom:2px solid #cbd5e1;}
  /* Paleta de estado, la misma que usa TIER_COLOR en el JS: un tier debe leerse del
     mismo color en la tabla y en la gráfica. El ámbar de "considerar" lleva texto
     oscuro porque el blanco sobre él no alcanza contraste.

     A pasó de verde (#0ca30c) a teal: contra el rojo de X quedaba en ΔE 4,1 bajo
     deuteranopía —verificado con scripts/validate_palette.js del skill dataviz—, es
     decir "entrar" y "evitar", los dos extremos de la recomendación, del mismo color
     para ~8% de los hombres. Teal↔rojo sube ese par a ΔE 10,1.
     B dejó de ser #2a78d6 porque ese azul está reservado a la marca propia
     (OWN_BRAND_COLOR): el mismo azul no puede significar "INVAL" en unas gráficas y
     "Tier B" en otras. B y D siguen cercanos bajo daltonismo, y por eso el tier va
     escrito en la etiqueta en todos los sitios donde aparece, nunca solo en el color. */
  .tier-badge{display:inline-block;padding:3px 9px;border-radius:99px;font-size:11px;font-weight:700;color:#fff;}
  .tier-A{background:#14707f;} .tier-B{background:#4f96a3;} .tier-C{background:#eda100;color:#3d2c00;}
  .tier-D{background:#9a9a93;} .tier-X{background:#d03b3b;}
  .risk-badge{display:inline-block;padding:3px 9px;border-radius:99px;font-size:11px;font-weight:700;}
  .risk-red{background:#fef2f2;color:#991b1b;} .risk-yellow{background:#fffbeb;color:#92400e;}
  .risk-green{background:#f0fdf4;color:#166534;} .risk-gray{background:#f1f5f9;color:#475569;}
  /* `min-width:0` en los hijos: un item de grid arranca en `min-width:auto`, así que el
     <canvas> que Chart.js dimensiona a su ancho intrínseco mantenía la columna abierta y
     sacaba el panel —y con él la página entera— fuera del viewport. */
  .panel-2x2{display:grid;grid-template-columns:1fr 1fr;gap:14px;}
  .panel-2x2>*{min-width:0;}
  .chart-container{position:relative;height:280px;min-width:0;}
  .chart-container.tall{height:340px;}
  /* Cuando la serie no tiene ningún valor, en vez de un eje en cero se pinta el motivo.
     Ver emptyState() en el JS. */
  .chart-empty{
    display:flex;align-items:center;gap:9px;min-height:78px;
    background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;padding:14px;
    font-size:12px;color:#64748b;line-height:1.5;
  }
  .chart-empty .ico{font-size:16px;line-height:1;}
  td.empty-cell{padding:14px 10px;color:#64748b;font-size:12.5px;white-space:normal;}

  /* ---- Carga ----
     Dos comportamientos distintos, a propósito:
     · PRIMERA carga (no hay nada que enseñar todavía): esqueleto con la forma real de lo
       que va a llegar —KPIs, tabla, gráficas— para que la página no salte al poblarse.
     · RECARGA (ya hay un dashboard pintado): NO se sustituye por esqueleto. Cambiar
       contenido bueno por cajas grises es un parpadeo que hace perder el sitio; se
       atenúa lo que ya está y se bloquean los clics hasta que llegan los datos nuevos. */
  @keyframes skeleton-pulse{0%{opacity:1}50%{opacity:.45}100%{opacity:1}}
  .sk{
    background:#e8edf2;border-radius:6px;
    animation:skeleton-pulse 1.4s ease-in-out infinite;
  }
  @media (prefers-reduced-motion:reduce){ .sk{animation:none;opacity:.7;} }
  .sk-line{height:11px;margin-bottom:8px;}
  .sk-title{height:14px;width:190px;margin-bottom:14px;}
  .sk-hero{height:38px;width:230px;margin-bottom:8px;}
  .sk-chart{height:240px;border-radius:10px;}
  .sk-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;}
  .sk-tile{height:66px;border-radius:10px;}
  #loadingSkeleton .card{border-color:#eef2f6;}
  /* Recarga sobre contenido ya pintado. */
  #dashboard.is-refetching{opacity:.45;pointer-events:none;transition:opacity .15s;}
  .load-banner{
    display:flex;align-items:center;gap:9px;margin-bottom:14px;padding:10px 14px;
    background:var(--brand-tint);border:1px solid var(--brand-300);border-radius:10px;
    font-size:12.5px;color:#1e293b;
  }
  .load-spinner{
    width:14px;height:14px;flex:none;border-radius:50%;
    border:2px solid var(--brand-300);border-top-color:var(--brand-900);
    animation:spin .7s linear infinite;
  }
  @keyframes spin{to{transform:rotate(360deg)}}
  @media (prefers-reduced-motion:reduce){ .load-spinner{animation-duration:2.4s;} }
  .toolbar{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:10px;}
  .pill{font-size:11px;background:#f1f5f9;color:#475569;border-radius:99px;padding:3px 10px;}
  .insight-box{background:#fffbeb;border:1px solid #fde68a;border-radius:10px;padding:14px;font-size:13px;color:#78350f;line-height:1.5;}
  .footer-note{font-size:11px;color:#94a3b8;margin-top:24px;line-height:1.6;}
  .req-grid{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px;}
  .threshold-row{display:flex;gap:18px;flex-wrap:wrap;margin-top:10px;}
  #loginOverlay{
    position:fixed;inset:0;background:#f8fafc;display:flex;align-items:center;justify-content:center;z-index:1000;
  }
  #loginOverlay .card{width:320px;}
  #loginOverlay input[type=text],#loginOverlay input[type=password]{width:100%;margin-bottom:12px;}
  #loginError{color:#991b1b;font-size:12.5px;margin-top:8px;}
  /* En el flujo, no `position:fixed`: fijo tapaba la última fila de cada tabla y la
     leyenda de la última gráfica en todo scroll. */
  #watermark{
    text-align:center;font-size:11px;color:#94a3b8;
    padding:18px 10px 4px;
  }
  #watermark a{color:#94a3b8;text-decoration:none;}
  #watermark a:hover{color:#64748b;text-decoration:underline;}

  /* ---- Cabecera y filtros fijos ----
     Antes había 16 tarjetas de igual peso en 5.500 px de scroll, con los filtros
     repartidos en tres sitios distintos: al llegar a una gráfica ya no se veía sobre qué
     recorte estaba calculada ni había forma de cambiarla sin volver arriba. Cabecera y
     filtros van pegados al viewport y el resto se agrupa en secciones anclables. */
  #topBar{
    position:sticky;top:0;z-index:900;background:#f8fafc;
    padding:14px 0 0;margin:-24px -24px 14px;border-bottom:1px solid #e2e8f0;
  }
  #topBar>.inner{padding:0 24px;}
  .retailer-tabs{display:flex;gap:2px;margin-top:10px;}
  .retailer-tab{
    border:none;background:none;cursor:pointer;padding:8px 15px;
    font-size:13px;font-weight:600;color:#64748b;border-bottom:2px solid transparent;
  }
  .retailer-tab:hover{color:#1e293b;}
  .retailer-tab[aria-selected=true]{color:var(--brand-900);border-bottom-color:var(--brand-900);}
  /* La sección activa se marca con color de marca Y una barra inferior: el color por sí
     solo no basta para decir "estás aquí" —había que adivinarlo— y con la barra el estado
     se lee también sin distinguir el tono. La actualiza el observador de scroll
     (ver marcarSeccionActiva en el JS), así que sigue al contenido, no solo al clic. */
  .section-nav{display:flex;gap:4px;padding:9px 24px 0;overflow-x:auto;}
  .section-nav a{
    font-size:12px;color:#64748b;text-decoration:none;white-space:nowrap;
    padding:5px 10px 8px;border-radius:6px 6px 0 0;border-bottom:2px solid transparent;
    transition:color .12s, background .12s;
  }
  .section-nav a:hover{color:var(--brand-900);background:var(--brand-tint);}
  .section-nav a[aria-current=true]{
    color:var(--brand-900);font-weight:600;
    background:var(--brand-tint);border-bottom-color:var(--brand-900);
  }
  #filterBar{
    position:sticky;z-index:890;background:#fff;border:1px solid #e2e8f0;border-radius:12px;
    padding:12px 16px;margin-bottom:18px;
  }
  #filterBar .row{gap:12px;}
  #filterBar label{margin-bottom:3px;}
  .filter-meta{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:9px;font-size:11px;color:#64748b;}
  /* Los umbrales de tier gobiernan un solo panel pero se configuran desde la barra:
     plegados, porque son ajuste fino y no filtro de uso diario. */
  #filterBar details summary{font-size:11px;color:#64748b;cursor:pointer;}
  .dash-section{scroll-margin-top:150px;}
  .dash-section>h2{font-size:15px;margin:26px 0 10px;color:#0f172a;}

  /* El dashboard se diseñó para escritorio; por debajo de 768px todo baja a una columna
     para que siga siendo legible sin zoom ni scroll horizontal. */
  @media (max-width:768px){
    body{padding:12px;}
    #topBar{margin:-12px -12px 12px;}
    #topBar>.inner,.section-nav{padding-left:12px;padding-right:12px;}
    #topBar,#filterBar{position:static;}
    .panel-2x2{grid-template-columns:1fr;}
    .kpi-grid{grid-template-columns:repeat(auto-fit,minmax(120px,1fr));}
    .row{gap:10px;}
    .row>div{flex:1 1 100%;}
    .row select,.row input[type=text],.row input[type=number],.row input[type=date]{width:100%;min-width:0;}
    .toolbar{align-items:flex-start;}
    h1{font-size:17px;}
  }
