// PLANES — alimentado por /admin/api/plans.php
// Progressive disclosure: solo la familia destacada arranca expandida.
// Cada sección se puede colapsar; barra de filtros para evitar fatiga visual.
const { useState, useEffect, useMemo, useRef, useCallback } = React;

// Internet hogar va primero y destacado — es el plan más buscado del distribuidor.
const FAMILY_META = {
  internet_hogar:  { title: "Internet hogar",  sub: "Fibra óptica + Wi-Fi 6 en cada esquina. Instalación gratis.", unit: "speed", featured: true, badge: "★ DESTACADO", icon: "🌐" },
  triple_play:     { title: "Triple play",     sub: "Internet, TV y voz. Una sola factura.", unit: "speed", icon: "📺" },
  movil_postpago:  { title: "Móvil postpago",  sub: "Datos 5G, llamadas y roaming. Sin sorpresas en la factura.", unit: "datos", icon: "📱" },
  movil_prepago:   { title: "Móvil prepago",   sub: "Recarga lo que necesitas, cuando lo necesitas.", unit: "datos", icon: "💳" },
  movil_control:   { title: "Móvil control",   sub: "Lo mejor de prepago + postpago. Tope mensual.", unit: "datos", icon: "🎛️" },
  internet_movil:  { title: "Internet móvil",  sub: "Conexión donde estés, con módem portátil.", unit: "datos", icon: "📡" },
  duo:             { title: "Duo",             sub: "Internet hogar + línea móvil en un combo.", unit: "speed", icon: "🔗" },
  otros:           { title: "Otros",           sub: "Servicios complementarios.", unit: "", icon: "✨" },
};

// Detect "cobre" plans (deprecated copper tech) — name or features
function isCobrePlan(p) {
  if (!p) return false;
  const blob = `${p.name || ""} ${p.short_label || ""} ${(p.features || []).join(" ")}`.toLowerCase();
  return /\bcobre\b/i.test(blob);
}

function formatBigUnit(p) {
  if (p.family.startsWith("internet") || p.family === "triple_play" || p.family === "duo") {
    if (p.speed_down_mbps) return { n: String(p.speed_down_mbps), u: "Mbps" };
  }
  if (p.family.startsWith("movil")) {
    if (p.data_unlimited) return { n: "∞", u: "datos" };
    if (p.data_gb) return { n: String(p.data_gb).replace(/\.0$/, ""), u: "GB" };
    if (p.minutes_unlimited) return { n: "∞", u: "minutos" };
  }
  if (p.short_label) return { n: p.short_label, u: "" };
  return { n: String(Math.round(p.price)), u: "RD$" };
}

function buildFeatureList(p) {
  if (p.features && p.features.length) return p.features.slice(0, 6);
  const out = [];
  if (p.speed_down_mbps) out.push(`${p.speed_down_mbps} Mbps${p.symmetric ? ' simétrico' : ''}`);
  if (p.data_unlimited) out.push("Datos ilimitados 5G");
  else if (p.data_gb) out.push(`${p.data_gb} GB de datos`);
  if (p.minutes_unlimited) out.push("Minutos ilimitados");
  else if (p.minutes) out.push(`${p.minutes} minutos`);
  if (p.social_unlimited) out.push("Redes sociales ilimitadas");
  if (p.roaming_usa) out.push("Roaming USA / Canadá");
  if (p.tv_channels) out.push(`TV ${p.tv_channels}+ canales${p.tv_premium ? ' (premium)' : ''}`);
  if (p.install_free) out.push("Instalación gratis");
  if (p.modem_included) out.push("Módem Wi-Fi incluido");
  if (p.wifi_standard) out.push(p.wifi_standard);
  return out.slice(0, 6);
}

function PlanHero({ totalCount }) {
  return (
    <section style={{ padding: "96px 0 24px", position: "relative", overflow: "hidden" }}>
      <window.DotGrid />
      <div className="container" style={{ position: "relative" }}>
        <div className="reveal eyebrow" style={{ marginBottom: 18 }}>Planes</div>
        <h1 className="reveal" style={{
          margin: 0, maxWidth: 1000,
          fontSize: "clamp(36px, 5vw, 64px)",
          fontWeight: 700, letterSpacing: "-0.03em", lineHeight: 1.05,
        }}>
          El plan correcto{" "}
          <span className="italic-display" style={{ color: "var(--brand)" }}>existe.</span>{" "}
          Y lo activamos hoy.
        </h1>
        <p className="lead reveal" style={{ maxWidth: 620, marginTop: 20, fontSize: 16 }}>
          Internet, TV o móvil. Filtra por categoría y abre solo lo que te interese.
        </p>
        {totalCount > 0 && (
          <p className="reveal" style={{
            margin: "16px 0 0", fontSize: 13, color: "var(--ink-3)",
            display: "inline-flex", alignItems: "center", gap: 8,
          }}>
            <span style={{ width: 6, height: 6, borderRadius: 999, background: "var(--brand)", boxShadow: "0 0 8px var(--brand)" }} />
            {totalCount} planes activos · Selecciona una categoría para ver detalles
          </p>
        )}
      </div>
    </section>
  );
}

function PlanCard({ p }) {
  const [hover, setHover] = useState(false);
  const big = formatBigUnit(p);
  const feats = buildFeatureList(p);
  const isPop = p.is_popular;
  const family = (FAMILY_META[p.family] || {}).title || p.family;

  const cardBg = isPop
    ? "linear-gradient(160deg, #1a1024 0%, #2a1538 50%, #1a1024 100%)"
    : "#fff";

  return (
    <article
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        background: cardBg,
        color: isPop ? "var(--paper)" : "var(--ink)",
        border: isPop ? "1px solid transparent" : "1px solid var(--line)",
        borderRadius: 22, padding: "36px 32px 28px",
        display: "flex", flexDirection: "column", gap: 22, position: "relative",
        minHeight: 520,
        transition: "transform 320ms cubic-bezier(0.16,1,0.3,1), box-shadow 320ms cubic-bezier(0.16,1,0.3,1), border-color 200ms",
        transform: hover ? "translateY(-6px)" : "translateY(0)",
        boxShadow: isPop
          ? (hover
              ? "0 30px 70px -20px rgba(237,15,68,0.35), 0 6px 20px -8px rgba(20,17,15,0.45), inset 0 1px 0 rgba(255,255,255,0.08)"
              : "0 18px 48px -16px rgba(237,15,68,0.22), 0 4px 12px -6px rgba(20,17,15,0.40), inset 0 1px 0 rgba(255,255,255,0.08)")
          : (hover
              ? "0 22px 50px -16px rgba(20,17,15,0.18), 0 4px 12px -4px rgba(20,17,15,0.08)"
              : "0 1px 1px rgba(20,17,15,0.04), 0 4px 12px -2px rgba(20,17,15,0.06)"),
        overflow: "hidden",
      }}>

      {isPop && (
        <>
          <div aria-hidden="true" style={{
            position: "absolute", inset: -1, borderRadius: 22, padding: 1,
            background: "linear-gradient(160deg, var(--brand) 0%, transparent 60%)",
            WebkitMask: "linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)",
            WebkitMaskComposite: "xor", maskComposite: "exclude", pointerEvents: "none",
          }} />
          <div aria-hidden="true" style={{
            position: "absolute", top: 0, right: 0, width: 240, height: 240,
            background: "radial-gradient(circle at top right, rgba(237,15,68,0.20), transparent 60%)",
            pointerEvents: "none",
          }} />
        </>
      )}

      {isPop && (
        <span style={{
          position: "absolute", top: 18, right: 18,
          background: "var(--brand)", color: "#fff",
          padding: "6px 12px", borderRadius: 999, fontSize: 10.5, fontWeight: 700,
          letterSpacing: "0.14em", textTransform: "uppercase",
          boxShadow: "0 8px 20px -6px rgba(237,15,68,0.55)",
        }}>★ Más popular</span>
      )}

      <div className="mini" style={{
        color: isPop ? "rgba(255,255,255,0.55)" : "var(--ink-3)",
        position: "relative", letterSpacing: "0.14em",
      }}>{family}</div>

      <div style={{ position: "relative" }}>
        <div style={{
          fontSize: 18, fontWeight: 600, letterSpacing: "-0.012em",
          color: isPop ? "#fff" : "var(--ink)",
        }}>{p.name}</div>

        <div style={{
          marginTop: 10, display: "flex", alignItems: "baseline", gap: 10,
          fontSize: "clamp(56px, 5.8vw, 88px)", fontWeight: 700, letterSpacing: "-0.045em",
          lineHeight: 0.92,
          background: isPop
            ? "linear-gradient(180deg, #ffeaef 0%, var(--brand) 100%)"
            : "linear-gradient(180deg, var(--brand) 0%, var(--brand-deep) 100%)",
          WebkitBackgroundClip: "text", backgroundClip: "text",
          WebkitTextFillColor: "transparent",
          fontVariantNumeric: "tabular-nums",
        }}>
          {big.n}
          {big.u && (
            <span style={{
              fontSize: 16, fontWeight: 500, letterSpacing: "0.02em",
              color: isPop ? "rgba(255,255,255,0.6)" : "var(--ink-3)",
              WebkitTextFillColor: "currentColor",
            }}>{big.u}</span>
          )}
        </div>

        {p.promo_label && (
          <div style={{
            marginTop: 14, display: "inline-flex", alignItems: "center", gap: 6,
            padding: "5px 12px", borderRadius: 999, fontSize: 12, fontWeight: 600,
            background: isPop ? "rgba(255,255,255,0.10)" : "var(--paper-2)",
            color: isPop ? "#fff" : "var(--brand-deep)",
            border: isPop ? "1px solid rgba(255,255,255,0.18)" : "1px solid var(--line)",
          }}>
            <span style={{ width: 6, height: 6, background: "var(--brand)", borderRadius: 999 }} />
            {p.promo_label}
          </div>
        )}
      </div>

      <div style={{
        height: 1, background: isPop
          ? "linear-gradient(90deg, transparent, rgba(255,255,255,0.18), transparent)"
          : "linear-gradient(90deg, transparent, var(--line), transparent)",
      }} />

      <ul style={{
        listStyle: "none", padding: 0, margin: 0,
        display: "flex", flexDirection: "column", gap: 10,
        position: "relative",
      }}>
        {feats.map((f, i) => (
          <li key={i} style={{
            display: "flex", gap: 12, alignItems: "flex-start",
            fontSize: 14.5, lineHeight: 1.45,
            color: isPop ? "rgba(255,255,255,0.88)" : "var(--ink-2)",
          }}>
            <span style={{
              flexShrink: 0, width: 18, height: 18, borderRadius: 999,
              background: isPop ? "rgba(237,15,68,0.18)" : "var(--paper-2)",
              color: isPop ? "var(--brand)" : "var(--brand)",
              display: "grid", placeItems: "center", marginTop: 1,
            }}>
              <svg width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2.6">
                <path d="M3 8.5l3.2 3.2L13 4.8" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
            </span>
            <span>{f}</span>
          </li>
        ))}
      </ul>

      <div style={{ marginTop: "auto", position: "relative" }}>
        <div style={{
          display: "flex", alignItems: "baseline", gap: 6, flexWrap: "wrap",
          marginBottom: 16,
        }}>
          <span style={{
            fontSize: 12, opacity: 0.55, textTransform: "uppercase", letterSpacing: "0.1em",
            fontWeight: 600,
          }}>desde</span>
          <span style={{
            fontSize: 36, fontWeight: 700, letterSpacing: "-0.03em",
            fontVariantNumeric: "tabular-nums",
            color: isPop ? "#fff" : "var(--ink)",
          }}>
            RD${Math.round(p.price).toLocaleString()}
          </span>
          <span style={{
            fontSize: 13, opacity: 0.55,
            color: isPop ? "rgba(255,255,255,0.6)" : "var(--ink-3)",
          }}>/{p.cycle}</span>
          {p.price_old && p.price_old > p.price && (
            <span style={{
              fontSize: 13, opacity: 0.5, textDecoration: "line-through",
              fontVariantNumeric: "tabular-nums",
            }}>
              RD${Math.round(p.price_old).toLocaleString()}
            </span>
          )}
        </div>

        {/* Tax disclaimer — móvil prices are shown sin impuestos (Indotel + ITBIS ≈ 30%) */}
        {p.family && p.family.startsWith("movil") && (
          <div role="note" style={{
            marginTop: -10, marginBottom: 14,
            padding: "8px 12px", borderRadius: 10,
            background: isPop ? "rgba(255,255,255,0.06)" : "var(--paper-2)",
            border: isPop ? "1px solid rgba(255,255,255,0.10)" : "1px solid var(--line)",
            display: "flex", alignItems: "baseline", gap: 8, flexWrap: "wrap",
            fontSize: 11.5, lineHeight: 1.4,
            color: isPop ? "rgba(255,255,255,0.72)" : "var(--ink-3)",
          }}>
            <span style={{ fontWeight: 600, letterSpacing: "0.04em", textTransform: "uppercase", fontSize: 10 }}>+ Impuestos</span>
            <span>
              Total con ITBIS y Telecom (~30%):{" "}
              <strong style={{
                color: isPop ? "#fff" : "var(--ink)",
                fontVariantNumeric: "tabular-nums",
              }}>
                RD${Math.round(p.price * 1.30).toLocaleString()}
              </strong>
              /{p.cycle}
            </span>
          </div>
        )}

        <button onClick={() => window.openLeadForm({
          kind: "plan", id: p.id, name: p.name, slug: p.slug, price: p.price,
          extra: {
            "Familia":     family,
            "Velocidad":   p.speed_down_mbps ? `${p.speed_down_mbps} Mbps${p.symmetric ? ' simétrico' : ''}` : null,
            "Datos":       p.data_unlimited ? "Ilimitados" : (p.data_gb ? `${p.data_gb} GB` : null),
            "Minutos":     p.minutes_unlimited ? "Ilimitados" : (p.minutes ? `${p.minutes} min` : null),
            "Roaming USA": p.roaming_usa ? "sí" : null,
            "Promoción":   p.promo_label || null,
          },
          sourceTag: "plan-form",
        })} style={{
          width: "100%", justifyContent: "center", display: "inline-flex", alignItems: "center", gap: 8,
          padding: "14px 24px", borderRadius: 999,
          background: isPop ? "var(--brand)" : "var(--ink)",
          color: "#fff", border: 0, cursor: "pointer", fontFamily: "inherit",
          fontSize: 14.5, fontWeight: 600, letterSpacing: "-0.005em",
          transition: "transform 140ms cubic-bezier(0.32,0.72,0,1), background 140ms, box-shadow 240ms",
          boxShadow: isPop
            ? "0 12px 28px -8px rgba(237,15,68,0.55)"
            : "0 8px 20px -8px rgba(20,17,15,0.30)",
        }}
          onMouseDown={(e) => e.currentTarget.style.transform = "scale(0.97)"}
          onMouseUp={(e) => e.currentTarget.style.transform = ""}
          onMouseOver={(e) => { if (!isPop) e.currentTarget.style.background = "var(--brand)"; }}
          onMouseOut={(e)  => { if (!isPop) e.currentTarget.style.background = "var(--ink)"; }}
        >
          Solicitar plan
          <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2">
            <path d="M3 8h10M9 4l4 4-4 4" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        </button>
      </div>
    </article>
  );
}

// ============================================================
// SECTION — header con toggle de colapso (oculta por sección).
// • Cerrada → fila compacta (un sólo renglón, sin banda oscura).
// • Abierta → muestra 3 planes; "Ver X más" revela el resto.
// ============================================================
const VISIBLE_PLAN_CAP = 3;

function PlanSection({ family, plans, alt, open, onToggle, filterMatchCount }) {
  const [showAll, setShowAll] = useState(false);
  // Reset showAll when section closes — avoids stale state on reopen.
  useEffect(() => { if (!open) setShowAll(false); }, [open]);

  if (!plans.length) return null;
  const meta = FAMILY_META[family] || { title: family, sub: "" };
  const isFeatured = !!meta.featured;
  const headerId = `plan-section-${family}`;
  const panelId  = `plan-panel-${family}`;
  const totalShown = plans.length;
  const totalFiltered = filterMatchCount != null ? filterMatchCount : totalShown;

  const visiblePlans = (showAll || plans.length <= VISIBLE_PLAN_CAP)
    ? plans : plans.slice(0, VISIBLE_PLAN_CAP);
  const hiddenCount = plans.length - visiblePlans.length;

  // ─── Compact closed view (shared by featured + standard) ────────────────
  // Slim row keeps the page scannable: title + count + "Ver planes" button.
  if (!open) {
    return (
      <section id={family} aria-labelledby={headerId} style={{
        background: alt ? "var(--paper-2)" : "var(--paper)",
        borderBottom: "1px solid var(--line)",
      }}>
        <div className="container" style={{ padding: "18px 0" }}>
          <div style={{
            display: "grid", gridTemplateColumns: "auto 1fr auto", gap: 14,
            alignItems: "center",
          }}>
            <span aria-hidden="true" style={{
              width: 36, height: 36, borderRadius: 12,
              background: isFeatured ? "var(--brand)" : "var(--paper-3)",
              color: isFeatured ? "#fff" : "var(--ink-2)",
              display: "grid", placeItems: "center", fontSize: 18,
            }}>{meta.icon || "•"}</span>

            <div style={{ minWidth: 0 }}>
              <div style={{ display: "flex", alignItems: "baseline", gap: 10, flexWrap: "wrap" }}>
                <h2 id={headerId} style={{
                  margin: 0, fontSize: "clamp(16px, 1.6vw, 19px)",
                  fontWeight: 700, letterSpacing: "-0.018em", color: "var(--ink)",
                }}>{meta.title}</h2>
                {isFeatured && (
                  <span style={{
                    fontSize: 10, fontWeight: 700, letterSpacing: "0.14em", textTransform: "uppercase",
                    padding: "3px 8px", borderRadius: 999,
                    background: "var(--brand)", color: "#fff",
                  }}>★ Destacado</span>
                )}
                <span style={{
                  fontSize: 12, color: "var(--ink-3)",
                  fontVariantNumeric: "tabular-nums",
                }}>
                  {totalFiltered === totalShown
                    ? `${totalShown} ${totalShown === 1 ? "plan" : "planes"}`
                    : `${totalFiltered}/${totalShown} planes`}
                </span>
              </div>
              {meta.sub && (
                <p style={{
                  margin: "2px 0 0", fontSize: 13, color: "var(--ink-3)",
                  lineHeight: 1.35, display: "-webkit-box",
                  WebkitLineClamp: 1, WebkitBoxOrient: "vertical", overflow: "hidden",
                }}>{meta.sub}</p>
              )}
            </div>

            <button type="button" onClick={onToggle}
                    aria-expanded={open} aria-controls={panelId}
                    style={{
                      display: "inline-flex", alignItems: "center", gap: 6,
                      padding: "9px 16px", borderRadius: 999,
                      background: "var(--ink)", color: "#fff",
                      border: 0, cursor: "pointer", fontFamily: "inherit",
                      fontSize: 13, fontWeight: 600, letterSpacing: "-0.005em",
                      whiteSpace: "nowrap",
                      transition: "background 140ms, transform 120ms",
                    }}
                    onMouseOver={(e) => e.currentTarget.style.background = "var(--brand)"}
                    onMouseOut={(e)  => e.currentTarget.style.background = "var(--ink)"}
                    onMouseDown={(e) => e.currentTarget.style.transform = "scale(0.97)"}
                    onMouseUp={(e) => e.currentTarget.style.transform = ""}
            >
              Ver planes
              <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2.4" aria-hidden="true">
                <path d="M4 6l4 4 4-4" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
            </button>
          </div>
        </div>
      </section>
    );
  }

  // ─── Open featured view ─────────────────────────────────────────────────
  if (isFeatured) {
    return (
      <section id={family} aria-labelledby={headerId} style={{
        position: "relative",
        background: "linear-gradient(180deg, #1a1024 0%, #2a1538 50%, var(--paper) 50%, var(--paper) 100%)",
        paddingTop: 0, paddingBottom: 0,
      }}>
        <div style={{ position: "relative", overflow: "hidden", paddingTop: 56, paddingBottom: 180 }}>
          <div aria-hidden="true" style={{
            position: "absolute", top: -60, right: -60, width: 480, aspectRatio: 1,
            background: "radial-gradient(circle, rgba(237,15,68,0.35), transparent 60%)",
            pointerEvents: "none",
          }} />
          <div aria-hidden="true" style={{
            position: "absolute", inset: 0,
            backgroundImage: "radial-gradient(rgba(255,255,255,0.06) 1.5px, transparent 1.5px)",
            backgroundSize: "26px 26px", pointerEvents: "none",
          }} />
          <div className="container" style={{ position: "relative" }}>
            <SectionHeaderBar
              id={headerId}
              panelId={panelId}
              open={open}
              onToggle={onToggle}
              eyebrow={<><span aria-hidden="true">{meta.icon} </span>{meta.badge || "★ Destacado"}</>}
              eyebrowStyle={{
                background: "var(--brand)", color: "#fff",
                boxShadow: "0 12px 32px -8px rgba(237,15,68,0.55)",
              }}
              title={
                <>
                  {meta.title}.{" "}
                  <span className="italic-display" style={{
                    background: "linear-gradient(180deg, #ffeaef 0%, var(--brand-coral) 100%)",
                    WebkitBackgroundClip: "text", backgroundClip: "text",
                    WebkitTextFillColor: "transparent",
                  }}>{meta.sub.split('.')[0]}.</span>
                </>
              }
              count={totalFiltered}
              countTotal={totalShown}
              dark
            />

            <div className="reveal" style={{
              marginTop: 28, display: "flex", flexWrap: "wrap", gap: "12px 32px",
              fontSize: 14, color: "rgba(255,255,255,0.78)",
            }}>
              {[
                { i: "✓", t: "Instalación gratis" },
                { i: "✓", t: "Wi-Fi 6 incluido" },
                { i: "✓", t: "Soporte 24/7" },
                { i: "✓", t: "Activación en 24h" },
              ].map((b, i) => (
                <span key={i} style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
                  <span style={{
                    width: 18, height: 18, borderRadius: 999,
                    background: "rgba(237,15,68,0.20)", color: "var(--brand-coral)",
                    display: "grid", placeItems: "center", fontSize: 11, fontWeight: 700,
                  }}>{b.i}</span>
                  {b.t}
                </span>
              ))}
            </div>
          </div>
        </div>

        <div id={panelId} className="container" style={{ position: "relative", marginTop: -160, paddingBottom: 56 }}>
          <div className="grid grid-3 reveal">
            {visiblePlans.map((p) => <PlanCard key={p.id} p={p} />)}
          </div>
          {hiddenCount > 0 && (
            <ShowMoreButton hiddenCount={hiddenCount} onClick={() => setShowAll(true)} dark />
          )}
        </div>
      </section>
    );
  }

  // ─── Open standard view ─────────────────────────────────────────────────
  return (
    <section id={family} aria-labelledby={headerId} style={{ background: alt ? "var(--paper-2)" : "var(--paper)", padding: "48px 0 56px" }}>
      <div className="container">
        <SectionHeaderBar
          id={headerId}
          panelId={panelId}
          open={open}
          onToggle={onToggle}
          eyebrow={<><span aria-hidden="true">{meta.icon} </span>{meta.title}</>}
          title={meta.sub}
          count={totalFiltered}
          countTotal={totalShown}
        />
        <div id={panelId} className="grid grid-3 reveal" style={{ marginTop: 32 }}>
          {visiblePlans.map((p) => <PlanCard key={p.id} p={p} />)}
        </div>
        {hiddenCount > 0 && (
          <ShowMoreButton hiddenCount={hiddenCount} onClick={() => setShowAll(true)} />
        )}
      </div>
    </section>
  );
}

// "Ver X planes más" — reveals the rest of the plans inside an open section.
function ShowMoreButton({ hiddenCount, onClick, dark }) {
  return (
    <div style={{ display: "flex", justifyContent: "center", marginTop: 24 }}>
      <button type="button" onClick={onClick}
              style={{
                display: "inline-flex", alignItems: "center", gap: 8,
                padding: "12px 22px", borderRadius: 999,
                fontFamily: "inherit", fontSize: 13.5, fontWeight: 600,
                cursor: "pointer", letterSpacing: "-0.005em",
                background: dark ? "rgba(255,255,255,0.10)" : "#fff",
                color: dark ? "#fff" : "var(--ink)",
                border: dark ? "1px solid rgba(255,255,255,0.22)" : "1px solid var(--line)",
                boxShadow: "0 6px 16px -8px rgba(20,17,15,0.18)",
                transition: "background 160ms, transform 120ms, box-shadow 160ms",
              }}
              onMouseOver={(e) => e.currentTarget.style.background = dark ? "rgba(255,255,255,0.18)" : "var(--paper-2)"}
              onMouseOut={(e)  => e.currentTarget.style.background = dark ? "rgba(255,255,255,0.10)" : "#fff"}
              onMouseDown={(e) => e.currentTarget.style.transform = "scale(0.97)"}
              onMouseUp={(e) => e.currentTarget.style.transform = ""}>
        Ver {hiddenCount} {hiddenCount === 1 ? "plan más" : "planes más"}
        <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2.4" aria-hidden="true">
          <path d="M4 6l4 4 4-4" strokeLinecap="round" strokeLinejoin="round" />
        </svg>
      </button>
    </div>
  );
}

// Reusable section header with collapse chevron + plan count
function SectionHeaderBar({ id, panelId, open, onToggle, eyebrow, eyebrowStyle, title, count, countTotal, dark }) {
  return (
    <div style={{ display: "grid", gridTemplateColumns: "1fr auto", gap: 24, alignItems: "center" }}>
      <div style={{ minWidth: 0 }}>
        <span className="reveal" style={{
          display: "inline-flex", alignItems: "center", gap: 8,
          padding: "6px 14px", borderRadius: 999,
          fontSize: 11, fontWeight: 700, letterSpacing: "0.16em", textTransform: "uppercase",
          marginBottom: 16,
          background: eyebrowStyle?.background || "var(--paper-2)",
          color: eyebrowStyle?.color || "var(--ink-3)",
          border: eyebrowStyle ? "0" : "1px solid var(--line)",
          ...eyebrowStyle,
        }}>{eyebrow}</span>

        <h2 id={id} className="reveal" style={{
          fontSize: dark ? "clamp(36px, 5.4vw, 72px)" : "clamp(28px, 3.6vw, 44px)",
          fontWeight: 700, letterSpacing: "-0.03em", lineHeight: dark ? 1.05 : 1.1,
          margin: 0, color: dark ? "#fff" : "var(--ink)",
          maxWidth: 880,
        }}>{title}</h2>

        {/* count chip */}
        {countTotal > 0 && (
          <p className="reveal" style={{
            margin: "12px 0 0", fontSize: 13,
            color: dark ? "rgba(255,255,255,0.55)" : "var(--ink-3)",
            display: "inline-flex", alignItems: "center", gap: 8,
          }}>
            {count === countTotal ? (
              <>{countTotal} {countTotal === 1 ? "plan disponible" : "planes disponibles"}</>
            ) : (
              <><strong style={{ color: dark ? "#fff" : "var(--ink)" }}>{count}</strong> de {countTotal} {countTotal === 1 ? "plan" : "planes"} coinciden con tus filtros</>
            )}
          </p>
        )}
      </div>

      <button
        type="button"
        onClick={onToggle}
        aria-expanded={open}
        aria-controls={panelId}
        title={open ? "Ocultar planes" : "Ver planes"}
        style={{
          flexShrink: 0,
          display: "inline-flex", alignItems: "center", gap: 8,
          padding: "10px 18px", borderRadius: 999,
          fontSize: 13.5, fontWeight: 600, letterSpacing: "-0.005em",
          fontFamily: "inherit", cursor: "pointer",
          background: dark ? "rgba(255,255,255,0.08)" : "#fff",
          color: dark ? "#fff" : "var(--ink)",
          border: dark ? "1px solid rgba(255,255,255,0.18)" : "1px solid var(--line)",
          transition: "background 160ms, transform 120ms, box-shadow 160ms",
          boxShadow: open ? "0 6px 16px -6px rgba(20,17,15,0.18)" : "none",
        }}
        onMouseOver={(e) => {
          e.currentTarget.style.background = dark ? "rgba(255,255,255,0.14)" : "var(--paper-2)";
        }}
        onMouseOut={(e) => {
          e.currentTarget.style.background = dark ? "rgba(255,255,255,0.08)" : "#fff";
        }}
        onMouseDown={(e) => e.currentTarget.style.transform = "scale(0.97)"}
        onMouseUp={(e) => e.currentTarget.style.transform = ""}
      >
        {open ? "Ocultar" : "Ver planes"}
        <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2.4"
             style={{ transform: open ? "rotate(180deg)" : "rotate(0)", transition: "transform 200ms" }}>
          <path d="M4 6l4 4 4-4" strokeLinecap="round" strokeLinejoin="round" />
        </svg>
      </button>
    </div>
  );
}

// ===========================================================
// SPEED TEST — Cloudflare endpoints, designed in the Ookla style
// ===========================================================
function SpeedTestSection({ plans }) {
  const [phase, setPhase]       = useState("idle");
  const [ping, setPing]         = useState(null);
  const [download, setDownload] = useState(0);
  const [finalDown, setFinalDown] = useState(null);
  const [upload, setUpload]     = useState(0);
  const [finalUp, setFinalUp]   = useState(null);
  const [error, setError]       = useState(null);
  const abortRef = useRef(null);

  function mbpsToAngle(mbps) {
    if (!mbps || mbps <= 0) return 0;
    const max = 1000;
    const ratio = Math.log10(Math.min(mbps, max) + 1) / Math.log10(max + 1);
    return Math.min(ratio, 1) * 270;
  }
  function speedLabel(mbps) {
    if (mbps === null || mbps === undefined) return "—";
    if (mbps < 10)   return mbps.toFixed(2);
    if (mbps < 100)  return mbps.toFixed(1);
    return mbps.toFixed(0);
  }
  function speedVerdict(mbps) {
    if (mbps == null) return { label: "—", color: "#6b5c5c" };
    if (mbps < 10)   return { label: "Muy lenta", color: "#d44545" };
    if (mbps < 25)   return { label: "Baja",      color: "#e08a2a" };
    if (mbps < 100)  return { label: "Aceptable", color: "#c9a82a" };
    if (mbps < 300)  return { label: "Buena",     color: "#7fbf3a" };
    if (mbps < 700)  return { label: "Muy buena", color: "#34c759" };
    return            { label: "Excelente",       color: "#00d68f" };
  }

  function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }

  async function runTest() {
    setError(null);
    setPing(null);
    setDownload(0);
    setUpload(0);
    setFinalDown(null);
    setFinalUp(null);

    setPhase("warmup");
    const warmStart = performance.now();
    try {
      await Promise.all([
        fetch("https://speed.cloudflare.com/__down?bytes=10000", { cache: "no-store" }).then(r => r.text()),
        fetch("https://speed.cloudflare.com/__down?bytes=10000", { cache: "no-store" }).then(r => r.text()),
      ]);
    } catch (e) {
      setError("Sin conexión. Verifica tu red.");
      setPhase("error");
      return;
    }
    const warmElapsed = performance.now() - warmStart;
    if (warmElapsed < 1500) await sleep(1500 - warmElapsed);

    setPhase("ping");
    try {
      const samples = [];
      for (let i = 0; i < 10; i++) {
        const t0 = performance.now();
        await fetch("https://speed.cloudflare.com/__down?bytes=0", { cache: "no-store" });
        samples.push(performance.now() - t0);
        setPing(Math.round(samples[Math.floor(samples.length / 2)]));
      }
      samples.sort((a, b) => a - b);
      const trimmed = samples.slice(2, -2);
      const avg = trimmed.reduce((a, b) => a + b, 0) / trimmed.length;
      setPing(Math.round(avg));
    } catch (e) {
      setError("No pudimos medir la latencia.");
      setPhase("error");
      return;
    }

    // ──────── DOWNLOAD ────────
    setPhase("download");
    try {
      const NUM_STREAMS  = 4;
      const BYTES_STREAM = 100_000_000;
      const DURATION_MS  = 10_000;
      const ctrl = new AbortController();
      abortRef.current = ctrl;
      const tStart = performance.now();
      let totalBytes = 0;
      let speedSamples = [];

      const updateInterval = setInterval(() => {
        const elapsed = (performance.now() - tStart) / 1000;
        if (elapsed < 0.5) return;
        const instMbps = (totalBytes * 8) / elapsed / 1_000_000;
        speedSamples.push(instMbps);
        if (speedSamples.length > 8) speedSamples.shift();
        const sorted = [...speedSamples].sort((a, b) => a - b);
        const med = sorted[Math.floor(sorted.length / 2)];
        setDownload(med);
      }, 150);

      const abortTimer = setTimeout(() => ctrl.abort(), DURATION_MS);

      const streams = Array.from({ length: NUM_STREAMS }, async () => {
        try {
          const res = await fetch(`https://speed.cloudflare.com/__down?bytes=${BYTES_STREAM}`, {
            signal: ctrl.signal, cache: "no-store",
          });
          if (!res.body) return;
          const reader = res.body.getReader();
          while (true) {
            const { done, value } = await reader.read();
            if (done) break;
            totalBytes += value.length;
          }
        } catch (e) { /* aborted */ }
      });

      await Promise.all(streams);
      clearInterval(updateInterval);
      clearTimeout(abortTimer);

      const elapsedTotal = (performance.now() - tStart) / 1000;
      const overall = (totalBytes * 8) / elapsedTotal / 1_000_000;
      const sorted = [...speedSamples].sort((a, b) => a - b);
      const med = speedSamples.length > 2 ? sorted[Math.floor(sorted.length / 2)] : overall;
      const finalMbps = med * 0.7 + overall * 0.3;
      setDownload(finalMbps);
      setFinalDown(finalMbps);
    } catch (e) {
      if (e.name !== "AbortError") {
        setError("Error al medir la descarga.");
        setPhase("error");
        return;
      }
    }

    await sleep(500);

    // ──────── UPLOAD — Cloudflare __up con fallback al backend propio ────────
    setPhase("upload");
    setUpload(0);
    try {
      const CHUNK_BYTES = 2_000_000;   // 2 MB por chunk
      const NUM_STREAMS = 3;
      const DURATION_MS = 8_000;

      // Endpoints: Cloudflare primero (más confiable), backend propio como fallback local
      // Cloudflare expone /__up que acepta arbitrary POST y devuelve 200/204
      const endpoints = [
        "https://speed.cloudflare.com/__up",
      ];
      // Si estamos en dev local con PHP, agregamos el endpoint propio como respaldo
      const localPhp = location.hostname === "localhost" || location.hostname === "127.0.0.1";
      if (localPhp) endpoints.push(window.balUrl("admin/api/speed_up.php"));

      // Generar payload una vez (sin compresión: bytes aleatorios reales)
      const data = new Uint8Array(CHUNK_BYTES);
      crypto.getRandomValues(data.subarray(0, Math.min(65536, CHUNK_BYTES))); // rellena al menos 64KB de aleatoriedad real
      // El resto puede quedar en 0; total entropy es suficiente para no comprimir significativamente

      // Probe: ¿qué endpoint responde 2xx? Usamos el primero que funcione
      let uploadUrl = null;
      for (const url of endpoints) {
        try {
          const probe = await fetch(url, {
            method: "POST",
            body: data.subarray(0, 1024),
            cache: "no-store",
            headers: { "Content-Type": "application/octet-stream" },
          });
          if (probe.ok || probe.status === 204) {
            uploadUrl = url;
            break;
          }
        } catch (e) { /* try next */ }
      }

      if (!uploadUrl) {
        // No hay endpoint disponible — mostramos "—" en lugar de 0 para no engañar
        setFinalUp(null);
        setPhase("done");
        return;
      }

      const tStart = performance.now();
      let totalUploaded = 0;
      let running = true;
      let speedSamples = [];

      const updateInterval = setInterval(() => {
        const elapsed = (performance.now() - tStart) / 1000;
        if (elapsed < 0.3) return;
        const instMbps = (totalUploaded * 8) / elapsed / 1_000_000;
        speedSamples.push(instMbps);
        if (speedSamples.length > 6) speedSamples.shift();
        const sorted = [...speedSamples].sort((a, b) => a - b);
        const med = sorted[Math.floor(sorted.length / 2)];
        setUpload(med);
      }, 200);

      const stopTimer = setTimeout(() => { running = false; }, DURATION_MS);

      const streams = Array.from({ length: NUM_STREAMS }, async () => {
        while (running) {
          try {
            const res = await fetch(uploadUrl, {
              method: "POST",
              body: data,
              cache: "no-store",
              headers: { "Content-Type": "application/octet-stream" },
            });
            if (!res.ok && res.status !== 204) break;
            totalUploaded += CHUNK_BYTES;
          } catch (e) { break; }
        }
      });

      await Promise.all(streams);
      clearInterval(updateInterval);
      clearTimeout(stopTimer);

      const elapsedTotal = (performance.now() - tStart) / 1000;
      if (totalUploaded === 0 || elapsedTotal === 0) {
        setFinalUp(null);
      } else {
        const overall = (totalUploaded * 8) / elapsedTotal / 1_000_000;
        const sorted = [...speedSamples].sort((a, b) => a - b);
        const med = speedSamples.length > 2 ? sorted[Math.floor(sorted.length / 2)] : overall;
        const finalMbps = med * 0.7 + overall * 0.3;
        setUpload(finalMbps);
        setFinalUp(finalMbps);
      }
    } catch (e) {
      setFinalUp(null);
    }

    setPhase("done");
  }

  const topPlan = useMemo(() => {
    if (!plans || plans.length === 0) return null;
    const internet = plans.filter(p => p.family === "internet_hogar" && p.speed_down_mbps);
    if (internet.length === 0) return null;
    return internet.reduce((a, b) => (a.speed_down_mbps > b.speed_down_mbps ? a : b));
  }, [plans]);

  const isRunning = ["warmup", "ping", "download", "upload"].includes(phase);
  const verdictDown = speedVerdict(finalDown);
  const currentMbps = phase === "upload" ? upload : download;
  const angle = mbpsToAngle(currentMbps);

  return (
    <section className="speedtest-section" style={{
      padding: "56px 0 64px",
      background: "linear-gradient(180deg, #0e1421 0%, #141a2e 100%)",
      color: "#fff",
      position: "relative",
      overflow: "hidden",
    }}>
      <div aria-hidden="true" style={{
        position: "absolute", inset: 0, pointerEvents: "none",
        backgroundImage: "linear-gradient(rgba(237,15,68,0.06) 1px, transparent 1px), linear-gradient(90deg, rgba(237,15,68,0.06) 1px, transparent 1px)",
        backgroundSize: "44px 44px",
        maskImage: "radial-gradient(ellipse at center, #000 30%, transparent 75%)",
        WebkitMaskImage: "radial-gradient(ellipse at center, #000 30%, transparent 75%)",
      }} />
      <div aria-hidden="true" style={{
        position: "absolute", top: "-20%", left: "50%", transform: "translateX(-50%)",
        width: 500, height: 500, borderRadius: "50%",
        background: "radial-gradient(circle, rgba(237,15,68,0.20), transparent 65%)",
        filter: "blur(80px)", pointerEvents: "none",
      }} />

      <div className="container speedtest-grid" style={{ position: "relative", maxWidth: 1120 }}>
        <div className="speedtest-head" style={{
          display: "flex", alignItems: "center", justifyContent: "space-between",
          gap: 20, flexWrap: "wrap", marginBottom: 28,
        }}>
          <div style={{ flex: "1 1 320px", minWidth: 0 }}>
            <div style={{
              display: "inline-flex", alignItems: "center", gap: 8,
              padding: "4px 12px", borderRadius: 999,
              background: "rgba(237,15,68,0.12)",
              border: "1px solid rgba(237,15,68,0.32)",
              fontSize: 10.5, fontWeight: 700, letterSpacing: "0.16em", textTransform: "uppercase",
              color: "var(--brand-coral)",
              marginBottom: 8,
            }}>
              <span style={{ width: 6, height: 6, borderRadius: 999, background: "var(--brand)", boxShadow: "0 0 10px var(--brand)" }} />
              Speed test
            </div>
            <h2 style={{
              fontSize: "clamp(22px, 2.6vw, 30px)",
              fontWeight: 700, letterSpacing: "-0.022em", lineHeight: 1.15,
              margin: 0, color: "#fff",
            }}>
              ¿Tu internet va lento?{" "}
              <span style={{ color: "var(--brand-coral)", fontStyle: "italic" }}>Compruébalo en 20 s.</span>
            </h2>
          </div>
          <p style={{
            flex: "1 1 280px", maxWidth: 360,
            fontSize: 13.5, lineHeight: 1.5,
            color: "rgba(255,255,255,0.55)",
            margin: 0,
          }}>
            Test gratuito vía Cloudflare. Sin descargar nada. Sin compartir tus datos.
          </p>
        </div>

        <div className="speedtest-body" style={{
          display: "grid",
          gridTemplateColumns: "minmax(220px, 280px) 1fr",
          gap: 36,
          alignItems: "center",
        }}>
          <div style={{ position: "relative", width: "100%", maxWidth: 280, justifySelf: "center" }}>
            <svg viewBox="0 0 320 320" width="100%" style={{ display: "block" }}>
            <defs>
              <linearGradient id="gaugeGrad" x1="0" y1="0" x2="1" y2="1">
                <stop offset="0%"  stopColor="var(--brand)" />
                <stop offset="100%" stopColor="var(--brand-coral)" />
              </linearGradient>
              <filter id="gaugeGlow">
                <feGaussianBlur stdDeviation="4" result="b" />
                <feMerge><feMergeNode in="b" /><feMergeNode in="SourceGraphic" /></feMerge>
              </filter>
            </defs>
            <circle cx="160" cy="160" r="130" fill="none" stroke="rgba(255,255,255,0.08)" strokeWidth="14"
                    strokeDasharray={`${270/360 * 2 * Math.PI * 130} ${2 * Math.PI * 130}`}
                    transform="rotate(135 160 160)" strokeLinecap="round" />
            <circle cx="160" cy="160" r="130" fill="none" stroke="url(#gaugeGrad)" strokeWidth="14"
                    strokeDasharray={`${angle/360 * 2 * Math.PI * 130} ${2 * Math.PI * 130}`}
                    transform="rotate(135 160 160)" strokeLinecap="round"
                    filter="url(#gaugeGlow)"
                    style={{ transition: "stroke-dasharray 220ms ease-out" }} />
            {[0, 10, 50, 100, 250, 500, 1000].map((v) => {
              const a = mbpsToAngle(v);
              const rad = (a - 135) * Math.PI / 180;
              const x1 = 160 + 142 * Math.cos(rad);
              const y1 = 160 + 142 * Math.sin(rad);
              const x2 = 160 + 152 * Math.cos(rad);
              const y2 = 160 + 152 * Math.sin(rad);
              const tx = 160 + 166 * Math.cos(rad);
              const ty = 160 + 166 * Math.sin(rad);
              return (
                <g key={v}>
                  <line x1={x1} y1={y1} x2={x2} y2={y2} stroke="rgba(255,255,255,0.32)" strokeWidth="1.5" />
                  <text x={tx} y={ty} textAnchor="middle" dominantBaseline="central"
                        fill="rgba(255,255,255,0.45)" fontSize="11" fontWeight="600" fontFamily="ui-monospace, monospace">
                    {v}
                  </text>
                </g>
              );
            })}
          </svg>
          <div style={{
            position: "absolute", inset: 0, display: "flex", flexDirection: "column",
            alignItems: "center", justifyContent: "center", pointerEvents: "none",
          }}>
            {phase === "idle" && (
              <button onClick={runTest} aria-label="Iniciar test de velocidad"
                style={{
                  pointerEvents: "auto",
                  width: "44%", aspectRatio: "1 / 1", borderRadius: "50%",
                  background: "linear-gradient(135deg, var(--brand) 0%, var(--brand-deep) 100%)",
                  border: 0, cursor: "pointer",
                  color: "#fff", fontSize: "clamp(20px, 4vw, 26px)", fontWeight: 800, letterSpacing: "0.06em",
                  boxShadow: "0 16px 48px -12px rgba(237,15,68,0.7), inset 0 1px 0 rgba(255,255,255,0.25)",
                  transition: "transform 200ms",
                  fontFamily: "inherit",
                }}
                onMouseDown={(e) => { e.currentTarget.style.transform = "scale(0.95)"; }}
                onMouseUp={(e) => { e.currentTarget.style.transform = "scale(1)"; }}
                onMouseLeave={(e) => { e.currentTarget.style.transform = "scale(1)"; }}>
                GO
              </button>
            )}
            {(isRunning || phase === "done") && (
              <>
                <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: "0.16em", color: "rgba(255,255,255,0.55)", textTransform: "uppercase" }}>
                  {phase === "warmup"   && "Preparando"}
                  {phase === "ping"     && "Latencia"}
                  {phase === "download" && "Descarga"}
                  {phase === "upload"   && "Subida"}
                  {phase === "done"     && "Descarga"}
                </div>
                <div style={{
                  fontSize: "clamp(36px, 7vw, 56px)", fontWeight: 800, letterSpacing: "-0.03em",
                  color: "#fff", lineHeight: 1, fontVariantNumeric: "tabular-nums",
                  marginTop: 4,
                }}>
                  {phase === "warmup" && <span style={{ display: "inline-block", animation: "balPulse 1s ease-in-out infinite", fontSize: "0.8em" }}>•••</span>}
                  {phase === "ping"   && (ping != null ? ping : "—")}
                  {(phase === "download" || phase === "upload") && speedLabel(currentMbps)}
                  {phase === "done"   && speedLabel(finalDown)}
                </div>
                <div style={{ fontSize: 11, fontWeight: 500, color: "rgba(255,255,255,0.7)", marginTop: 2 }}>
                  {phase === "ping" ? "ms" : phase === "warmup" ? "iniciando…" : "Mbps"}
                </div>
              </>
            )}
            {phase === "error" && (
              <div style={{ pointerEvents: "auto", textAlign: "center" }}>
                <div style={{ fontSize: 12, color: "#ff6b6b", marginBottom: 10 }}>⚠️ {error}</div>
                <button onClick={runTest} style={{
                  padding: "8px 18px", background: "var(--brand)", color: "#fff", border: 0, borderRadius: 999,
                  fontSize: 12, fontWeight: 600, cursor: "pointer",
                }}>Reintentar</button>
              </div>
            )}
          </div>
          </div>

          <div style={{ display: "flex", flexDirection: "column", gap: 14, minWidth: 0 }}>
            <div className="speedtest-results" style={{
              display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 8,
            }}>
              <ResultCard icon="↓" label="Descarga" value={finalDown != null ? speedLabel(finalDown) : (phase === "download" ? speedLabel(download) : "—")} unit="Mbps" tone="primary" />
              <ResultCard icon="↑" label="Subida"   value={finalUp != null ? speedLabel(finalUp) : (phase === "upload" ? speedLabel(upload) : "—")} unit="Mbps" tone="default" />
              <ResultCard icon="◎" label="Ping"     value={ping != null ? String(ping) : "—"} unit="ms" tone="default" />
            </div>

            {isRunning && (
              <div style={{ display: "flex", gap: 14, fontSize: 11, color: "rgba(255,255,255,0.55)", flexWrap: "wrap" }}>
                <ProgressDot label="Prep"     active={phase === "warmup"}   done={["ping","download","upload"].includes(phase)} />
                <ProgressDot label="Ping"     active={phase === "ping"}     done={["download","upload"].includes(phase)} />
                <ProgressDot label="Descarga" active={phase === "download"} done={phase === "upload"} />
                <ProgressDot label="Subida"   active={phase === "upload"}   done={false} />
              </div>
            )}

            {phase === "done" && finalDown != null && (
              <div style={{
                padding: "14px 16px",
                background: "rgba(255,255,255,0.04)",
                border: "1px solid rgba(255,255,255,0.12)",
                borderRadius: 14,
                display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap",
              }}>
                <span style={{
                  padding: "3px 10px", borderRadius: 999,
                  background: verdictDown.color + "20", color: verdictDown.color,
                  fontSize: 10, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase",
                  flexShrink: 0,
                }}>{verdictDown.label}</span>
                <span style={{ flex: "1 1 240px", fontSize: 13, color: "rgba(255,255,255,0.85)", lineHeight: 1.45 }}>
                  {topPlan && finalDown < (topPlan.speed_down_mbps * 0.9) ? (
                    <>
                      Con <strong style={{ color: "var(--brand-coral)" }}>{topPlan.name}</strong> podrías tener{" "}
                      <strong style={{ color: "#fff" }}>{topPlan.speed_down_mbps} Mbps</strong>
                      {" "}({Math.round(topPlan.speed_down_mbps / Math.max(finalDown, 1))}× más rápido)
                    </>
                  ) : (
                    <>Tu velocidad: <strong style={{ color: "#fff" }}>{speedLabel(finalDown)} Mbps</strong></>
                  )}
                </span>
                <div style={{ display: "flex", gap: 8, flexShrink: 0 }}>
                  <a href="#internet_hogar"
                     onClick={(e) => { e.preventDefault(); document.getElementById("internet_hogar")?.scrollIntoView({ behavior: "smooth", block: "start" }); }}
                     className="btn btn-brand" style={{ padding: "8px 16px", fontSize: 12.5, fontWeight: 600 }}>
                    Ver planes →
                  </a>
                  <button onClick={runTest} aria-label="Reiniciar test" style={{
                    padding: "8px 14px", fontSize: 12, fontWeight: 500,
                    background: "transparent", color: "#fff",
                    borderRadius: 999, border: "1px solid rgba(255,255,255,0.22)",
                    cursor: "pointer",
                  }}>↻</button>
                </div>
              </div>
            )}

            {phase === "idle" && (
              <p style={{ margin: 0, fontSize: 12, color: "rgba(255,255,255,0.45)", lineHeight: 1.5 }}>
                Click <strong style={{ color: "#fff" }}>GO</strong> para medir tu conexión actual.
                Tarda ~20 segundos. Endpoints de Cloudflare, no almacenamos resultados.
              </p>
            )}
          </div>
        </div>
      </div>

      <style>{`
        @keyframes balPulse {
          0%, 100% { opacity: 0.4; }
          50%      { opacity: 1; }
        }
        @keyframes balProgressDot {
          0%, 100% { transform: scale(1); }
          50%      { transform: scale(1.6); }
        }
        @media (max-width: 780px) {
          .speedtest-body { grid-template-columns: 1fr !important; gap: 20px !important; }
        }
        @media (max-width: 460px) {
          .speedtest-results { grid-template-columns: 1fr 1fr !important; }
          .speedtest-results > div:last-child { grid-column: 1 / -1 !important; }
        }
        @media (prefers-reduced-motion: reduce) {
          * { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
        }
      `}</style>
    </section>
  );
}

function ProgressDot({ label, active, done }) {
  const color = done ? "var(--brand-coral)" : active ? "var(--brand)" : "rgba(255,255,255,0.25)";
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 11.5, color: active ? "#fff" : done ? "rgba(255,255,255,0.6)" : "rgba(255,255,255,0.35)", fontWeight: active ? 600 : 500 }}>
      <span style={{
        width: 8, height: 8, borderRadius: "50%",
        background: color,
        boxShadow: active ? `0 0 12px ${color}` : "none",
        animation: active ? "balProgressDot 1s ease-in-out infinite" : "none",
      }} />
      {label}
    </span>
  );
}

function ResultCard({ icon, label, value, unit, tone }) {
  const isPrimary = tone === "primary";
  return (
    <div style={{
      padding: "12px 14px",
      background: isPrimary ? "linear-gradient(135deg, rgba(237,15,68,0.18), rgba(237,15,68,0.06))" : "rgba(255,255,255,0.04)",
      border: isPrimary ? "1px solid rgba(237,15,68,0.45)" : "1px solid rgba(255,255,255,0.10)",
      borderRadius: 12,
      display: "flex", flexDirection: "column", alignItems: "flex-start", gap: 1,
      minWidth: 0,
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 2 }}>
        <span style={{ fontSize: 13, color: isPrimary ? "var(--brand-coral)" : "rgba(255,255,255,0.55)", lineHeight: 1 }}>{icon}</span>
        <span style={{ fontSize: 9.5, fontWeight: 700, letterSpacing: "0.14em", color: "rgba(255,255,255,0.55)", textTransform: "uppercase" }}>{label}</span>
      </div>
      <div style={{
        fontSize: "clamp(20px, 3vw, 26px)", fontWeight: 800, color: "#fff",
        letterSpacing: "-0.02em", fontVariantNumeric: "tabular-nums", lineHeight: 1,
      }}>{value}<span style={{ fontSize: 11, fontWeight: 500, color: "rgba(255,255,255,0.55)", marginLeft: 4 }}>{unit}</span></div>
    </div>
  );
}

// ============================================================
// FILTER BAR — chips de familias + búsqueda + orden
// ============================================================
function PlanFilterBar({ families, active, onChange, counts, filters, setFilters, priceMax, openCount = 0, totalSections = 0, onExpandAll, onCollapseAll }) {
  const trackRef = useRef(null);

  const onKeyDown = (e, idx, allKeys) => {
    if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
    e.preventDefault();
    const dir = e.key === "ArrowLeft" ? -1 : 1;
    const next = (idx + dir + allKeys.length) % allKeys.length;
    const nextKey = allKeys[next];
    onChange(nextKey);
    trackRef.current?.querySelector(`[data-tab-key="${nextKey}"]`)?.focus();
  };

  const allKeys = families;
  const hasActiveFilter = !!filters.q || filters.priceMax < priceMax || filters.sort !== "popular";

  return (
    <div className="bal-plan-filter" role="region" aria-label="Filtros de planes" ref={trackRef}>
      <div className="bal-plan-filter__inner container">
        {/* Family chips */}
        <div className="bal-plan-chips" role="tablist" aria-label="Familias de planes">
          {families.map((f, i) => {
            const meta = FAMILY_META[f] || { title: f };
            return (
              <Chip
                key={f}
                isActive={active === f}
                onClick={() => onChange(f)}
                onKeyDown={(e) => onKeyDown(e, i, allKeys)}
                dataKey={f}
                label={meta.title}
                icon={meta.icon}
                count={counts[f] || 0}
                featured={meta.featured}
              />
            );
          })}
        </div>

        {/* Filters row — search + sort + price + reset */}
        <div className="bal-plan-tools">
          {/* Search */}
          <div className="bal-plan-search" role="search">
            <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
              <circle cx="7" cy="7" r="5" strokeLinecap="round" />
              <path d="M11 11l3 3" strokeLinecap="round" />
            </svg>
            <input
              type="search"
              placeholder="Buscar plan…"
              aria-label="Buscar por nombre o característica"
              value={filters.q}
              onChange={(e) => setFilters((s) => ({ ...s, q: e.target.value }))}
            />
            {filters.q && (
              <button type="button" onClick={() => setFilters((s) => ({ ...s, q: "" }))} aria-label="Limpiar búsqueda"
                      className="bal-plan-search-clear">×</button>
            )}
          </div>

          {/* Sort */}
          <label className="bal-plan-select">
            <span className="sr-only">Ordenar planes</span>
            <select
              value={filters.sort}
              onChange={(e) => setFilters((s) => ({ ...s, sort: e.target.value }))}
              aria-label="Ordenar planes"
            >
              <option value="popular">Más populares</option>
              <option value="price-asc">Precio: menor a mayor</option>
              <option value="price-desc">Precio: mayor a menor</option>
              <option value="speed-desc">Velocidad: mayor a menor</option>
            </select>
            <svg width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2.4" aria-hidden="true">
              <path d="M4 6l4 4 4-4" strokeLinecap="round" strokeLinejoin="round" />
            </svg>
          </label>

          {/* Expand / collapse all */}
          {totalSections > 1 && (
            openCount === totalSections ? (
              <button type="button" className="bal-plan-toggle-all" onClick={onCollapseAll}
                      title="Cerrar todas las secciones" aria-label="Cerrar todas las secciones">
                <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2.2" aria-hidden="true">
                  <path d="M4 10l4-4 4 4" strokeLinecap="round" strokeLinejoin="round" />
                </svg>
                Cerrar todo
              </button>
            ) : (
              <button type="button" className="bal-plan-toggle-all" onClick={onExpandAll}
                      title="Abrir todas las secciones" aria-label="Abrir todas las secciones">
                <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2.2" aria-hidden="true">
                  <path d="M4 6l4 4 4-4" strokeLinecap="round" strokeLinejoin="round" />
                </svg>
                Abrir todo
              </button>
            )
          )}

          {/* Reset */}
          {hasActiveFilter && (
            <button type="button" className="bal-plan-reset" onClick={() => setFilters({ q: "", priceMax, sort: "popular" })}>
              Limpiar filtros
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

function Chip({ isActive, onClick, onKeyDown, dataKey, label, icon, count, featured }) {
  return (
    <button
      type="button"
      role="tab"
      tabIndex={isActive ? 0 : -1}
      aria-selected={isActive}
      data-tab-key={dataKey}
      onClick={onClick}
      onKeyDown={onKeyDown}
      className={`bal-plan-chip ${isActive ? "is-active" : ""}`}
    >
      {icon && <span aria-hidden="true" style={{ fontSize: 14, opacity: isActive ? 1 : 0.85 }}>{icon}</span>}
      {featured && !isActive && <span aria-hidden="true" style={{ color: "var(--brand)", fontSize: 12 }}>★</span>}
      <span>{label}</span>
      {count > 0 && <span className="bal-plan-chip-count" aria-hidden="true">{count}</span>}
    </button>
  );
}

function FilterStyles() {
  return (
    <style>{`
      .bal-plan-filter {
        position: sticky; top: 80px; z-index: 30;
        background: rgba(247,241,247,0.92);
        backdrop-filter: saturate(160%) blur(14px);
        -webkit-backdrop-filter: saturate(160%) blur(14px);
        border-bottom: 1px solid var(--line);
      }
      .bal-plan-filter__inner {
        display: flex; gap: 14px;
        padding: 12px 0;
        flex-wrap: wrap;
        align-items: center;
      }
      .bal-plan-chips {
        display: inline-flex; gap: 8px; flex-wrap: wrap;
        flex: 1 1 auto; min-width: 0;
      }
      .bal-plan-tools {
        display: inline-flex; gap: 8px; align-items: center;
        flex-shrink: 0;
      }
      .bal-plan-chip {
        display: inline-flex; align-items: center; gap: 8px;
        padding: 9px 14px; border-radius: 999px;
        border: 1px solid var(--line);
        background: #fff;
        color: var(--ink);
        font-size: 13.5px; font-weight: 600;
        font-family: inherit; cursor: pointer;
        white-space: nowrap;
        transition: background 160ms, color 160ms, border-color 160ms, box-shadow 160ms, transform 120ms;
      }
      .bal-plan-chip:hover { transform: translateY(-1px); box-shadow: 0 8px 18px -10px rgba(20,17,15,0.18); }
      .bal-plan-chip.is-active {
        background: var(--brand); color: #fff; border-color: var(--brand);
        box-shadow: 0 10px 22px -10px rgba(237,15,68,0.55);
      }
      .bal-plan-chip:focus-visible {
        outline: 2px solid var(--brand);
        outline-offset: 2px;
      }
      .bal-plan-chip-count {
        font-size: 11px; font-weight: 700;
        padding: 2px 7px; border-radius: 999px;
        background: var(--paper-2); color: var(--ink-3);
        font-variant-numeric: tabular-nums;
      }
      .bal-plan-chip.is-active .bal-plan-chip-count {
        background: rgba(255,255,255,0.22); color: #fff;
      }

      .bal-plan-search {
        position: relative;
        display: inline-flex; align-items: center; gap: 6px;
        padding: 0 10px 0 12px;
        border-radius: 999px;
        background: #fff;
        border: 1px solid var(--line);
        color: var(--ink-3);
        min-width: 200px;
        transition: border-color 160ms, box-shadow 160ms;
      }
      .bal-plan-search:focus-within {
        border-color: var(--brand);
        box-shadow: 0 0 0 3px rgba(237,15,68,0.12);
      }
      .bal-plan-search input {
        flex: 1; min-width: 0;
        background: transparent;
        border: 0; outline: 0;
        padding: 9px 0;
        font-family: inherit; font-size: 13.5px;
        color: var(--ink);
      }
      .bal-plan-search input::placeholder { color: var(--ink-4); }
      .bal-plan-search-clear {
        flex-shrink: 0;
        background: var(--paper-2); color: var(--ink-3);
        border: 0; cursor: pointer;
        width: 20px; height: 20px; border-radius: 999px;
        font-size: 16px; line-height: 1;
        display: grid; place-items: center;
        font-family: inherit;
      }
      .bal-plan-search-clear:hover { background: var(--line); color: var(--ink); }

      .bal-plan-select {
        position: relative;
        display: inline-flex; align-items: center;
        background: #fff;
        border: 1px solid var(--line);
        border-radius: 999px;
        padding: 0 32px 0 14px;
        cursor: pointer;
        transition: border-color 160ms, box-shadow 160ms;
      }
      .bal-plan-select:hover { border-color: var(--ink-4); }
      .bal-plan-select:focus-within {
        border-color: var(--brand);
        box-shadow: 0 0 0 3px rgba(237,15,68,0.12);
      }
      .bal-plan-select select {
        appearance: none; -webkit-appearance: none; -moz-appearance: none;
        background: transparent; border: 0; outline: 0;
        padding: 9px 0;
        font-family: inherit; font-size: 13.5px; font-weight: 600;
        color: var(--ink);
        cursor: pointer;
      }
      .bal-plan-select svg {
        position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
        color: var(--ink-3); pointer-events: none;
      }

      .bal-plan-reset {
        display: inline-flex; align-items: center;
        padding: 9px 14px;
        background: transparent; color: var(--brand);
        border: 1px solid transparent; border-radius: 999px;
        font-family: inherit; font-size: 13px; font-weight: 600;
        cursor: pointer;
        transition: background 160ms;
      }
      .bal-plan-reset:hover { background: rgba(237,15,68,0.08); }
      .bal-plan-reset:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

      .bal-plan-toggle-all {
        display: inline-flex; align-items: center; gap: 6px;
        padding: 9px 14px;
        background: var(--paper-2); color: var(--ink-2);
        border: 1px solid var(--line); border-radius: 999px;
        font-family: inherit; font-size: 12.5px; font-weight: 600;
        cursor: pointer;
        transition: background 160ms, color 160ms, border-color 160ms;
      }
      .bal-plan-toggle-all:hover { background: var(--ink); color: #fff; border-color: var(--ink); }
      .bal-plan-toggle-all:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

      .sr-only {
        position: absolute; width: 1px; height: 1px;
        padding: 0; margin: -1px; overflow: hidden;
        clip: rect(0,0,0,0); white-space: nowrap; border: 0;
      }

      @media (max-width: 900px) {
        .bal-plan-filter__inner { gap: 10px; }
        .bal-plan-chips {
          flex-wrap: nowrap;
          overflow-x: auto;
          padding-left: 20px; padding-right: 20px;
          margin-left: calc(-1 * var(--container-px, 20px));
          margin-right: calc(-1 * var(--container-px, 20px));
          scroll-snap-type: x proximity;
          scrollbar-width: none;
          width: 100%;
        }
        .bal-plan-chips::-webkit-scrollbar { display: none; }
        .bal-plan-chip { scroll-snap-align: start; flex-shrink: 0; }
        .bal-plan-tools { width: 100%; flex-wrap: wrap; }
        .bal-plan-search { flex: 1 1 200px; min-width: 0; }
      }
      @media (max-width: 720px) {
        .bal-plan-filter { top: 70px; }
      }
      .bal-plan-results { animation: balFadeIn 380ms cubic-bezier(0.16,1,0.3,1) both; }
      @keyframes balFadeIn {
        from { opacity: 0; transform: translateY(8px); }
        to   { opacity: 1; transform: translateY(0); }
      }
      @media (prefers-reduced-motion: reduce) {
        .bal-plan-chip { transition: none; }
        .bal-plan-results { animation: none; }
      }
    `}</style>
  );
}

// ============================================================
// APP
// ============================================================
function App() {
  const [items, setItems]   = useState([]);
  const [status, setStatus] = useState("loading");
  const [filters, setFilters] = useState({ q: "", priceMax: 99999, sort: "popular" });
  const [expanded, setExpanded] = useState({}); // family -> bool (override defaults)
  const [activeFamily, setActiveFamily] = useState(null); // tab nav: which is the focus

  // Filtro del catálogo (estilo equipos): familia + orden + precio
  const [selFamilies, setSelFamilies] = useState(() => new Set());
  const [catSort, setCatSort] = useState("recomendado");
  const [catMin, setCatMin] = useState("");
  const [catMax, setCatMax] = useState("");
  const [showCatFilters, setShowCatFilters] = useState(false);

  useEffect(() => {
    let cancelled = false;
    fetch(window.balUrl("admin/api/plans.php?limit=200"))
      .then((r) => r.ok ? r.json() : Promise.reject("HTTP " + r.status))
      .then((d) => {
        if (cancelled) return;
        setItems(d.items || []);
        setStatus(d.items && d.items.length ? "ok" : "empty");
      })
      .catch(() => { if (!cancelled) setStatus("error"); });
    return () => { cancelled = true; };
  }, []);

  // Filtra cobre + aplica búsqueda y rango de precio
  const cleanItems = useMemo(() => {
    return (items || []).filter((p) => !isCobrePlan(p));
  }, [items]);

  // 3 planes principales = los 3 mejores FIJOS del hogar (internet_hogar).
  // No mezcla móvil. Prioriza los marcados is_popular en el admin; completa
  // por precio y los muestra ordenados de entrada a premium.
  const topPlans = useMemo(() => {
    const fixed = cleanItems.filter((p) => p.family === "internet_hogar");
    const pinned = fixed.filter((p) => p.is_popular);
    const rest = fixed.filter((p) => !p.is_popular).sort((a, b) => (a.price || 0) - (b.price || 0));
    return [...pinned, ...rest].slice(0, 3).sort((a, b) => (a.price || 0) - (b.price || 0));
  }, [cleanItems]);

  // Catálogo plano filtrado (sidebar familia + orden + precio)
  const catalogFiltered = useMemo(() => {
    let out = cleanItems.filter((p) => !selFamilies.size || selFamilies.has(p.family));
    const mn = parseFloat(catMin), mx = parseFloat(catMax);
    if (!isNaN(mn)) out = out.filter((p) => (p.price || 0) >= mn);
    if (!isNaN(mx)) out = out.filter((p) => (p.price || 0) <= mx);
    switch (catSort) {
      case "precio-asc":  out = out.slice().sort((a, b) => (a.price || 0) - (b.price || 0)); break;
      case "precio-desc": out = out.slice().sort((a, b) => (b.price || 0) - (a.price || 0)); break;
      case "velocidad":   out = out.slice().sort((a, b) => (b.speed_down_mbps || 0) - (a.speed_down_mbps || 0)); break;
      default:            out = out.slice().sort((a, b) => (!!b.is_popular - !!a.is_popular) || (a.price || 0) - (b.price || 0));
    }
    return out;
  }, [cleanItems, selFamilies, catMin, catMax, catSort]);
  const catHasActive = selFamilies.size || catMin || catMax || catSort !== "recomendado";
  const toggleFamily = (f) => setSelFamilies((prev) => { const n = new Set(prev); n.has(f) ? n.delete(f) : n.add(f); return n; });
  const resetCat = () => { setSelFamilies(new Set()); setCatMin(""); setCatMax(""); setCatSort("recomendado"); };

  const grouped = useMemo(() => {
    const g = {};
    for (const p of cleanItems) (g[p.family] = g[p.family] || []).push(p);
    return g;
  }, [cleanItems]);

  const orderedFamilies = useMemo(
    () => Object.keys(FAMILY_META).filter((f) => grouped[f] && grouped[f].length),
    [grouped]
  );

  // Precio máximo global para el slider
  const globalPriceMax = useMemo(() => {
    if (!cleanItems.length) return 99999;
    return Math.max(...cleanItems.map((p) => Math.round(p.price || 0))) + 100;
  }, [cleanItems]);

  // Init priceMax once data loads
  useEffect(() => {
    if (cleanItems.length && filters.priceMax === 99999) {
      setFilters((s) => ({ ...s, priceMax: globalPriceMax }));
    }
  }, [cleanItems.length, globalPriceMax]);

  // Apply filters per family
  const filteredByFamily = useMemo(() => {
    const out = {};
    const q = filters.q.trim().toLowerCase();
    const max = filters.priceMax;

    for (const f of orderedFamilies) {
      let list = grouped[f].slice();

      if (q) {
        list = list.filter((p) => {
          const blob = `${p.name || ""} ${p.short_label || ""} ${(p.features || []).join(" ")} ${p.promo_label || ""}`.toLowerCase();
          return blob.includes(q);
        });
      }
      if (max < globalPriceMax) {
        list = list.filter((p) => Math.round(p.price || 0) <= max);
      }
      // Sort
      switch (filters.sort) {
        case "price-asc":
          list.sort((a, b) => (a.price || 0) - (b.price || 0));
          break;
        case "price-desc":
          list.sort((a, b) => (b.price || 0) - (a.price || 0));
          break;
        case "speed-desc":
          list.sort((a, b) => (b.speed_down_mbps || 0) - (a.speed_down_mbps || 0));
          break;
        default:
          // popular first, then price asc
          list.sort((a, b) => {
            if (!!b.is_popular - !!a.is_popular !== 0) return !!b.is_popular - !!a.is_popular;
            return (a.price || 0) - (b.price || 0);
          });
      }
      out[f] = list;
    }
    return out;
  }, [orderedFamilies, grouped, filters, globalPriceMax]);

  // Counts (after filtering) per family
  const counts = useMemo(() => {
    const c = {};
    for (const f of orderedFamilies) c[f] = filteredByFamily[f].length;
    return c;
  }, [orderedFamilies, filteredByFamily]);

  // Total count (all families, after filtering)
  const totalFiltered = useMemo(
    () => Object.values(counts).reduce((s, n) => s + n, 0),
    [counts]
  );

  // Total count before any filtering (for hero)
  const totalRaw = cleanItems.length;

  // Active family — focus marker for chip nav. ALL sections start CLOSED.
  // User must click chip o "Ver planes" para abrir. Hash deeplink abre solo esa.
  useEffect(() => {
    if (orderedFamilies.length === 0 || activeFamily) return;
    const hash = (location.hash || "").replace(/^#/, "");
    const fromHash = orderedFamilies.includes(hash) ? hash : null;
    // activeFamily marca el chip seleccionado en teclado, no expande nada solo.
    setActiveFamily(fromHash || orderedFamilies[0]);
    if (fromHash) {
      setExpanded((e) => ({ ...e, [fromHash]: true }));
      // Scroll to deeplinked section after expand
      requestAnimationFrame(() => {
        const target = document.getElementById(fromHash);
        if (target) {
          const offset = 80 + 60;
          const top = target.getBoundingClientRect().top + window.scrollY - offset;
          window.scrollTo({ top, behavior: "smooth" });
        }
      });
    }
  }, [orderedFamilies, activeFamily]);

  const filterBarRef = useRef(null);
  const handleFamilyChange = (next) => {
    setActiveFamily(next);
    // Expand the chosen, collapse the others (focused view)
    const newExpanded = {};
    newExpanded[next] = true;
    setExpanded(newExpanded);
    history.replaceState(null, "", `#${next}`);
    requestAnimationFrame(() => {
      // Scroll to that family's section after expansion
      const target = document.getElementById(next);
      if (target) {
        const offset = 80 + 60; // header + filter bar
        const top = target.getBoundingClientRect().top + window.scrollY - offset;
        window.scrollTo({ top, behavior: "smooth" });
      }
    });
  };

  const toggleSection = (family) => {
    setExpanded((e) => ({ ...e, [family]: !e[family] }));
  };

  const expandAll = () => {
    const next = {};
    for (const f of orderedFamilies) next[f] = true;
    setExpanded(next);
  };
  const collapseAll = () => {
    setExpanded({});
  };
  const openCount = orderedFamilies.filter((f) => expanded[f]).length;

  // Empty after filter? show a friendly message
  const emptyAfterFilter = status === "ok" && totalFiltered === 0 && totalRaw > 0;

  return (
    <window.PageScaffold active="planes">
      <FilterStyles />
      <PlanHero totalCount={totalRaw} />

      {status === "ok" && topPlans.length > 0 && (
        <section style={{ paddingTop: 4 }}>
          <div className="container">
            <div style={{ textAlign: "center", maxWidth: 640, margin: "0 auto 32px" }}>
              <div className="eyebrow" style={{ marginBottom: 10 }}>Los más elegidos</div>
              <h2 className="h2" style={{ margin: 0 }}>
                Internet fijo <span className="italic-display" style={{ color: "var(--brand)" }}>principal</span>
              </h2>
              <p className="lead" style={{ marginTop: 12, color: "var(--ink-3)" }}>
                Nuestros 3 planes de fibra al hogar más elegidos. ¿Buscas móvil u otra velocidad? Mira el catálogo completo abajo.
              </p>
            </div>
            <div className="grid grid-3" style={{ gap: 20, alignItems: "stretch" }}>
              {topPlans.map((p) => <PlanCard key={p.id} p={p} />)}
            </div>
            <div style={{ textAlign: "center", marginTop: 28 }}>
              <a href="#catalogo" className="btn btn-ghost" style={{ border: "1px solid var(--line)", background: "#fff", padding: "11px 22px" }}>
                Ver todos los planes ↓
              </a>
            </div>
          </div>
        </section>
      )}

      {status === "loading" && (
        <section><div className="container">
          <div className="grid grid-3" style={{ gap: 16 }}>
            {[...Array(3)].map((_, i) => (
              <div key={i} className="card" style={{ padding: 36, minHeight: 480 }}>
                <div style={{ height: 14, width: 100, background: "var(--paper-2)", borderRadius: 4 }} />
                <div style={{ height: 60, marginTop: 16, background: "var(--paper-2)", borderRadius: 8 }} />
                <div style={{ height: 1, background: "var(--paper-2)", margin: "24px 0" }} />
                <div style={{ height: 80, background: "var(--paper-2)", borderRadius: 4 }} />
              </div>
            ))}
          </div>
        </div></section>
      )}

      {status === "empty" && (
        <section><div className="container">
          <div className="card" style={{ padding: 48, textAlign: "center" }}>
            <h3 className="h3" style={{ margin: 0 }}>Catálogo de planes vacío</h3>
            <p className="body" style={{ marginTop: 16 }}>
              Crea planes desde el admin · <code>/admin/plans.php</code>
            </p>
          </div>
        </div></section>
      )}

      {status === "error" && (
        <section><div className="container">
          <div className="card" style={{ padding: 48, textAlign: "center" }}>
            <h3 className="h3" style={{ margin: 0 }}>Error de carga</h3>
            <p className="body" style={{ marginTop: 16, marginBottom: 24 }}>
              No pudimos cargar los planes. Verifica tu conexión.
            </p>
            <button onClick={() => window.location.reload()} className="btn btn-brand"
                    style={{ padding: "10px 24px" }}>
              Reintentar
            </button>
          </div>
        </div></section>
      )}

      {status === "ok" && (
        <section style={{ background: "var(--paper-2)", paddingTop: 8, paddingBottom: 80 }}>
          <div className="container">
            <div id="catalogo" style={{ scrollMarginTop: 90 }} />
            <div style={{ textAlign: "center", maxWidth: 560, margin: "0 auto 28px" }}>
              <div className="eyebrow" style={{ marginBottom: 8 }}>Catálogo completo</div>
              <h2 className="h2" style={{ margin: 0 }}>Todos los planes</h2>
            </div>

            <button className="eq-filter-toggle" onClick={() => setShowCatFilters(!showCatFilters)}>
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><line x1="4" y1="6" x2="20" y2="6"/><line x1="7" y1="12" x2="17" y2="12"/><line x1="10" y1="18" x2="14" y2="18"/></svg>
              Filtros {catHasActive ? "· activos" : ""}
            </button>

            <div className="eq-layout">
              <aside className={"eq-filters" + (showCatFilters ? " is-open" : "")}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
                  <h2 style={{ fontSize: 17, fontWeight: 700, margin: 0 }}>Filtrar</h2>
                  <span className="caption">{catalogFiltered.length} planes</span>
                </div>

                <div style={{ paddingBottom: 18, marginBottom: 18, borderBottom: "1px solid var(--line)" }}>
                  <div style={{ fontSize: 12, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--ink-3)", marginBottom: 12 }}>Ordenar por</div>
                  {[["recomendado","Recomendado"],["precio-asc","Precio: menor"],["precio-desc","Precio: mayor"],["velocidad","Más velocidad"]].map(([v,l]) => (
                    <label key={v} style={{ display: "flex", alignItems: "center", gap: 10, padding: "5px 0", cursor: "pointer", fontSize: 14, color: "var(--ink-2)" }}>
                      <input type="radio" name="cat-sort" checked={catSort === v} onChange={() => setCatSort(v)} style={{ width: 16, height: 16, accentColor: "var(--brand)", cursor: "pointer" }} />
                      {l}
                    </label>
                  ))}
                </div>

                {orderedFamilies.length > 1 && (
                  <div style={{ paddingBottom: 18, marginBottom: 18, borderBottom: "1px solid var(--line)" }}>
                    <div style={{ fontSize: 12, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--ink-3)", marginBottom: 12 }}>Categoría</div>
                    {orderedFamilies.map((f) => (
                      <label key={f} style={{ display: "flex", alignItems: "center", gap: 10, padding: "5px 0", cursor: "pointer", fontSize: 14, color: "var(--ink-2)" }}>
                        <input type="checkbox" checked={selFamilies.has(f)} onChange={() => toggleFamily(f)} style={{ width: 17, height: 17, accentColor: "var(--brand)", cursor: "pointer", flexShrink: 0 }} />
                        {(FAMILY_META[f] || {}).title || f}
                      </label>
                    ))}
                  </div>
                )}

                <div style={{ paddingBottom: 18, marginBottom: 18, borderBottom: "1px solid var(--line)" }}>
                  <div style={{ fontSize: 12, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--ink-3)", marginBottom: 12 }}>Precio (RD$)</div>
                  <div style={{ display: "flex", gap: 8 }}>
                    <input type="number" inputMode="numeric" placeholder="Desde" value={catMin} onChange={(e) => setCatMin(e.target.value)}
                      style={{ width: "50%", padding: "9px 12px", borderRadius: 10, border: "1px solid var(--line)", fontFamily: "inherit", fontSize: 13, background: "#fff", color: "var(--ink)" }} />
                    <input type="number" inputMode="numeric" placeholder="Hasta" value={catMax} onChange={(e) => setCatMax(e.target.value)}
                      style={{ width: "50%", padding: "9px 12px", borderRadius: 10, border: "1px solid var(--line)", fontFamily: "inherit", fontSize: 13, background: "#fff", color: "var(--ink)" }} />
                  </div>
                </div>

                {catHasActive && (
                  <button onClick={resetCat} style={{ background: "transparent", border: 0, color: "var(--brand)", fontSize: 14, fontWeight: 600, cursor: "pointer", padding: "4px 0", fontFamily: "inherit" }}>
                    Restablecer filtros
                  </button>
                )}
              </aside>

              <div className="eq-results">
                {catalogFiltered.length === 0 ? (
                  <div className="card" style={{ padding: 48, textAlign: "center", background: "#fff" }}>
                    <div className="h3" style={{ margin: 0 }}>Sin resultados</div>
                    <p className="body" style={{ marginTop: 12 }}>Ningún plan coincide con tus filtros.</p>
                    <button onClick={resetCat} className="btn btn-brand" style={{ marginTop: 16 }}>Restablecer filtros</button>
                  </div>
                ) : (
                  <div className="grid grid-3" style={{ gap: 16, alignItems: "stretch" }}>
                    {catalogFiltered.map((p) => <PlanCard key={p.id} p={p} />)}
                  </div>
                )}
              </div>
            </div>
          </div>

          <style>{`
            .eq-layout { display: grid; grid-template-columns: 248px 1fr; gap: 32px; align-items: start; }
            .eq-filters { background: #fff; border: 1px solid var(--line); border-radius: 18px; padding: 22px; position: sticky; top: 90px; }
            .eq-filter-toggle { display: none; }
            @media (max-width: 900px) {
              .eq-layout { grid-template-columns: 1fr; }
              .eq-filter-toggle {
                display: inline-flex; align-items: center; gap: 8px;
                padding: 10px 18px; border-radius: 999px; margin-bottom: 20px;
                background: var(--ink); color: #fff; border: 0; cursor: pointer;
                font-family: inherit; font-size: 14px; font-weight: 600;
              }
              .eq-filters { display: none; position: static; margin-bottom: 24px; }
              .eq-filters.is-open { display: block; }
            }
          `}</style>
        </section>
      )}

      {/* Speed test moved after plans — opt-in, less noise above the fold. */}
      {status === "ok" && <SpeedTestSection plans={cleanItems} />}
    </window.PageScaffold>
  );
}

window.PAGES = Object.assign({}, window.PAGES, { planes: App });
if (!window.__BAL_ROOT) window.__BAL_ROOT = ReactDOM.createRoot(document.getElementById("root"));
window.__BAL_ROOT.render(React.createElement((window.PAGES && window.PAGES[window.PAGE]) || App));
