// LongtailIQ app, shared helper components. Reads DS from window namespace.
(function init(){
if(!window.LongtailIQDesignSystem_ae8f12){return setTimeout(init,30);}
const React = window.React;
const DS = window.LongtailIQDesignSystem_ae8f12;
const { Badge, Icon, Card, Button, OpportunityScore } = DS;
const t = (k, v) => (window.LTQ.t ? window.LTQ.t(k, v) : k);

// ---- Responsive: canonical breakpoints (JS mirror of tokens/responsive.css —
// keep the two in sync) + a subscription-safe media-query hook so the
// inline-styled screens can branch layout by viewport.
const BP = { phone: 640, tablet: 980, desktop: 1280 };
const _mq = new Map();
function _mqEntry(query) {
  let e = _mq.get(query);
  if (!e) {
    const mql = window.matchMedia(query);
    e = {
      subscribe: (cb) => { mql.addEventListener("change", cb); return () => mql.removeEventListener("change", cb); },
      get: () => mql.matches,
    };
    _mq.set(query, e);
  }
  return e;
}
function useMedia(query) {
  const e = _mqEntry(query);
  return React.useSyncExternalStore(e.subscribe, e.get);
}
// Layout tiers: usePhone = single-column layouts; useCompact = below the
// sidebar→drawer cutoff (phones + iPad portrait); useCoarse = touch device.
const usePhone = () => useMedia(`(max-width: ${BP.phone}px)`);
const useCompact = () => useMedia(`(max-width: ${BP.tablet}px)`);
const useCoarse = () => useMedia("(hover: none), (pointer: coarse)");

// Categorical hue per tone — used as a small dot / icon tint only (flowing labels, no boxes).
const TONE_COLOR = {
  blue: "var(--viz-blue)", accent: "var(--accent-500)", green: "var(--viz-green)",
  violet: "var(--viz-cyan)", amber: "var(--viz-amber)", red: "var(--viz-red)", neutral: "var(--ink-400)",
};

const INTENT = {
  "Informational": "blue", "Commercial": "accent", "Transactional": "green",
  "Comparison": "violet", "Local": "amber", "Problem-aware": "neutral",
  "Solution-aware": "blue", "AI prompt": "violet", "Reddit pain point": "neutral",
  "Commercial research": "accent", "Awareness": "blue", "Consideration": "violet", "Decision": "green",
};
// Intent as a flowing label with a small categorical dot (de-boxed).
const intentKey = (v) => "intent." + String(v).toLowerCase().replace(/[^a-z]+/g, "_");
// Canonical English data values -> display keys (raw fallback keeps unknown
// values readable; the stored value stays English so server logic is stable).
const DATA_LABEL = {
  funnel: { Decision: "funnel.decision", Consideration: "funnel.consideration", Awareness: "funnel.awareness" },
  intent: {
    Informational: "intent.informational", Commercial: "intent.commercial", Transactional: "intent.transactional",
    Comparison: "intent.comparison", Local: "intent.local", "Problem-aware": "intent.problem_aware",
    "Solution-aware": "intent.solution_aware", "AI prompt": "intent.ai_prompt", "Reddit pain point": "intent.reddit_pain_point",
    "Commercial research": "intent.commercial_research", Awareness: "intent.awareness",
    Consideration: "intent.consideration", Decision: "intent.decision",
  },
  serp: { "Featured snippet": "serp.featured_snippet", "Standard result": "serp.standard_result", Shopping: "serp.shopping", "Local pack": "serp.local_pack" },
  type: { "Comparison page": "type.comparison_page", "Blog post": "type.blog_post", Listicle: "type.listicle", "AI-answer page": "type.ai_answer_page", "Landing page": "type.landing_page" },
  format: { "Ranked list": "format.ranked_list", "Explainer + sources": "format.explainer_sources", "Step-by-step": "format.step_by_step", "Pros/cons + sources": "format.pros_cons" },
};
const dataLabel = (group, v) => (DATA_LABEL[group] && DATA_LABEL[group][v] ? t(DATA_LABEL[group][v]) : v);
// DS OpportunityScore with the band label localized (the DS component's own
// label is hardcoded English; bands + label style mirror the DS source).
function Score({ score, width }) {
  const band =
    score >= 80 ? { key: "level.very_high", color: "var(--green-500)" } :
    score >= 55 ? { key: "level.high", color: "var(--accent-500)" } :
    score >= 35 ? { key: "level.medium", color: "var(--amber-500)" } :
                  { key: "level.low", color: "var(--ink-400)" };
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 10 }}>
      <OpportunityScore score={score} width={width} showLabel={false} />
      <span style={{ fontSize: 11.5, fontWeight: 700, color: band.color, minWidth: 56 }}>{t(band.key)}</span>
    </span>
  );
}
function IntentBadge({ value }) {
  const c = TONE_COLOR[INTENT[value] || "neutral"];
  const label = INTENT[value] ? t(intentKey(value)) : value;
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 7, fontSize: 12.5, fontWeight: 600, color: "var(--text-body)", whiteSpace: "nowrap" }}>
      <span style={{ width: 6, height: 6, borderRadius: 999, background: c, flex: "none" }} />{label}
    </span>
  );
}

const PLAT = {
  "Google": ["neutral","globe"], "AI Overviews": ["violet","sparkles"], "AI Overview": ["violet","sparkles"], "Google AI Overview": ["violet","sparkles"],
  "ChatGPT": ["green","sparkle"], "Perplexity": ["blue","compass"], "Reddit": ["accent","message-circle"],
  "YouTube": ["red","monitor-play"], "TikTok": ["neutral","music-2"], "Amazon": ["amber","shopping-cart"],
  "Shopify": ["green","shopping-bag"],
};
// Platform as an icon + flowing label (de-boxed).
function PlatformBadge({ value }) {
  const [tone, icon] = PLAT[value] || ["neutral", "globe"];
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 7, fontSize: 12.5, fontWeight: 600, color: "var(--text-body)", whiteSpace: "nowrap" }}>
      <Icon name={icon} size={14} style={{ color: TONE_COLOR[tone] }} />{value}
    </span>
  );
}

// Difficulty mini-meter
function Difficulty({ value }) {
  const band = value < 20 ? "var(--green-500)" : value < 40 ? "var(--amber-500)" : "var(--red-500)";
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
      <span style={{ display: "flex", gap: 2 }}>
        {[0,1,2,3,4].map(i => (
          <span key={i} style={{ width: 4, height: 13, borderRadius: 2,
            background: i < Math.round(value/20) ? band : "var(--ink-150)" }} />
        ))}
      </span>
      <span className="lt-num" style={{ fontFamily: "var(--font-mono)", fontSize: 12.5, color: "var(--text-muted)" }}>{value}</span>
    </span>
  );
}

// Demand with a tiny bar
function Demand({ value }) {
  const max = 5000;
  return (
    <span style={{ display: "inline-flex", flexDirection: "column", gap: 4, minWidth: 70 }}>
      <span className="lt-num" style={{ fontFamily: "var(--font-mono)", fontSize: 13, fontWeight: 600, color: "var(--text-strong)" }}>{window.LTQ.fmtNumber ? window.LTQ.fmtNumber(value) : value.toLocaleString()}</span>
      <span style={{ height: 3, width: "100%", background: "var(--ink-100)", borderRadius: 999, overflow: "hidden" }}>
        <span style={{ display: "block", height: "100%", width: (Math.min(100, value/max*100))+"%", background: "var(--blue-400)", borderRadius: 999 }} />
      </span>
    </span>
  );
}

// SQ — the mascot, dropped into any React screen. Thin wrapper over the
// self-contained /shared/sq-robot.js rig: mounts on an effect, tears down on
// unmount, updates state/label without remounting. No-op (renders an empty box)
// if the rig script hasn't loaded, so it can never break a screen.
function SQ({ state = "idle", width = 120, label, track = false, interactive = true, hoverWave = false, bubble = true, say, saySide = "top", sayOnce, style }) {
  const ref = React.useRef(null);
  const inst = React.useRef(null);
  React.useEffect(() => {
    if (!ref.current || !window.SQRobot) return;
    inst.current = window.SQRobot.mount(ref.current, { state, width, label, track, interactive, bubble });
    // First-visit coach line: SQ speaks a hint once (per browser, keyed by
    // sayOnce). No-op if already seen or reduced-motion (the bubble is calm
    // there anyway); the surrounding copy carries the message regardless.
    if (say && !(window.SQRobot.explainMuted && window.SQRobot.explainMuted())) {
      let seen = false;
      if (sayOnce) { try { seen = localStorage.getItem("sqsay_" + sayOnce) === "1"; } catch (e) {} }
      if (!seen) {
        if (sayOnce) { try { localStorage.setItem("sqsay_" + sayOnce, "1"); } catch (e) {} }
        setTimeout(() => { if (inst.current) inst.current.say(say, { side: saySide, autohideMs: 9000 }); }, 900);
      }
    }
    return () => { if (inst.current) { inst.current.destroy(); inst.current = null; } };
  }, []); // mount once; state/label handled below
  React.useEffect(() => { if (inst.current) inst.current.setState(state, label); }, [state, label]);
  const h = Math.round((650 / 540) * width);
  return (
    <div ref={ref} aria-hidden="true"
      style={{ width, minHeight: h, ...style }}
      onMouseEnter={hoverWave ? () => inst.current && inst.current.setState("wave") : undefined}
      onMouseLeave={hoverWave ? () => inst.current && inst.current.setState(state, label) : undefined} />
  );
}

// SQ pops into the corner and cheers for a beat — called next to the existing
// LTQEngine.celebrate() confetti on a win (first run, saved keywords, saved
// brief). Imperative + self-cleaning so any screen can fire it. No-op under
// reduced-motion (the confetti engine already goes static there) or if the rig
// isn't loaded. Debounced so rapid wins don't stack robots.
let _sqCheerBusy = false;
function sqCheer(opts) {
  opts = opts || {};
  if (_sqCheerBusy || !window.SQRobot) return;
  try { if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; } catch (e) {}
  _sqCheerBusy = true;
  const host = document.createElement("div");
  host.setAttribute("aria-hidden", "true");
  host.style.cssText = "position:fixed;right:26px;bottom:20px;z-index:200;pointer-events:none;transform:translateY(24px);opacity:0;transition:transform .45s cubic-bezier(.34,1.56,.64,1),opacity .35s ease;";
  document.body.appendChild(host);
  const inst = window.SQRobot.mount(host, { width: opts.width || 118, state: "cheer", interactive: false, track: false });
  requestAnimationFrame(() => { host.style.transform = "translateY(0)"; host.style.opacity = "1"; });
  const hold = opts.holdMs || 1900;
  setTimeout(() => {
    host.style.transform = "translateY(20px)"; host.style.opacity = "0";
    setTimeout(() => { if (inst) inst.destroy(); host.remove(); _sqCheerBusy = false; }, 420);
  }, hold);
}

// Game-feel (phase 9): the failure twin of sqCheer — SQ slides up sad, gives a
// single damped shake, and leaves. Same busy-flag so reactions never stack;
// strictly decorative (aria-hidden), reduced-motion silent.
function sqOops(opts) {
  opts = opts || {};
  if (_sqCheerBusy || !window.SQRobot) return;
  try { if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; } catch (e) {}
  _sqCheerBusy = true;
  const host = document.createElement("div");
  host.setAttribute("aria-hidden", "true");
  host.style.cssText = "position:fixed;right:26px;bottom:20px;z-index:200;pointer-events:none;transform:translateY(24px);opacity:0;transition:transform .4s var(--ease-out),opacity .3s ease;";
  document.body.appendChild(host);
  const inst = window.SQRobot.mount(host, { width: opts.width || 108, state: "sad", interactive: false, track: false });
  requestAnimationFrame(() => {
    host.style.transform = "translateY(0)";
    host.style.opacity = "1";
    if (window.GameFeel) window.GameFeel.shake(host);
  });
  setTimeout(() => {
    host.style.transform = "translateY(20px)"; host.style.opacity = "0";
    setTimeout(() => { try { inst && inst.destroy && inst.destroy(); } catch (e) {} host.remove(); _sqCheerBusy = false; }, 380);
  }, opts.holdMs || 1500);
}
window.LTQ.sqOops = sqOops;

// Empty state. Pass `sq="idle|search|sad|sleep|think"` to greet with the mascot
// instead of the generic icon tile; `icon` stays the default so existing callers
// are unchanged.
function EmptyState({ icon, sq, sqLabel, sqSay, sqSayKey, title, body, action }) {
  // Game-feel (phase 11): ONE gentle attention nudge on the CTA after 6s of
  // idle, once per mount — the "tap here" beat, never repeated, reduced-safe.
  const nudgeRef = React.useRef(null);
  React.useEffect(() => {
    if (!action) return;
    const id = setTimeout(() => {
      const GF = window.GameFeel;
      const host = nudgeRef.current;
      if (!GF || GF.reduced() || !host) return;
      const btn = host.querySelector("button:not([disabled])");
      if (btn) { btn.style.animation = "gf-nudge 700ms var(--ease-out) 1"; btn.addEventListener("animationend", () => { btn.style.animation = ""; }, { once: true }); }
    }, 6000);
    return () => clearTimeout(id);
  }, []);
  return (
    <div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center",
      textAlign: "center", padding: sq ? (sqSay ? "72px 24px 56px" : "44px 24px 56px") : "64px 24px", gap: 6 }}>
      {sq && window.SQRobot
        ? <div style={{ marginBottom: 2 }}><SQ state={sq} width={132} label={sqLabel} interactive bubble={false}
            say={sqSay} saySide="top" sayOnce={sqSayKey} /></div>
        : <div className="fx-float" style={{ width: 64, height: 64, borderRadius: 20, display: "grid", placeItems: "center",
            background: "var(--grad-accent-soft)", color: "var(--accent-600)", marginBottom: 8, boxShadow: "var(--ring-inset)" }}>
            <Icon name={icon} size={28} strokeWidth={1.8} />
          </div>}
      <h3 style={{ fontSize: 18, fontWeight: 700, color: "var(--text-strong)" }}>{title}</h3>
      <p style={{ fontSize: 14, color: "var(--text-muted)", maxWidth: 380, lineHeight: 1.55 }}>{body}</p>
      {action && <div ref={nudgeRef} style={{ marginTop: 12 }}>{action}</div>}
    </div>
  );
}

// Pro lock chip
function ProTag({ small }) {
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 4, height: small?18:20, padding: "0 7px",
      borderRadius: 999, background: "var(--ink-900)", color: "#fff", fontSize: small?10:11, fontWeight: 700, letterSpacing: "0.02em" }}>
      <Icon name="lock" size={small?10:11} strokeWidth={2.4} /> PRO
    </span>
  );
}

// Upgrade card (sidebar / inline)
function UpgradeCard({ compact }) {
  if (compact) {
    return (
      <div style={{ borderRadius: "var(--r-lg)", padding: 14, background: "var(--grad-ink)", color: "#fff" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 7, marginBottom: 6 }}>
          <Icon name="zap" size={15} style={{ color: "var(--accent-400)" }} />
          <span style={{ fontSize: 13, fontWeight: 700, whiteSpace: "nowrap" }}>{t("plan.free")}</span>
        </div>
        <p style={{ fontSize: 11.5, color: "rgba(245,242,236,0.65)", lineHeight: 1.5, marginBottom: 11 }}>{t("upgrade.compact_sub")}</p>
        <Button variant="primary" size="sm" block trailingIcon="arrow-right" onClick={() => window.open("/pricing", "_blank", "noopener")}>{t("upgrade.title")}</Button>
      </div>
    );
  }
  return (
    <div style={{ borderRadius: "var(--r-xl)", padding: 22, background: "var(--grad-ink)", color: "#fff",
      display: "flex", alignItems: "center", gap: 20, boxShadow: "var(--shadow-lg)" }}>
      <div style={{ flex: 1 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }}>
          <Icon name="radar" size={18} style={{ color: "var(--accent-400)" }} />
          <span className="lt-eyebrow" style={{ color: "var(--accent-400)" }}>{t("upgrade.eyebrow")}</span>
        </div>
        <h3 style={{ fontSize: 21, fontWeight: 700, color: "#fff", marginBottom: 6 }}>{t("upgrade.big_title")}</h3>
        <p style={{ fontSize: 14, color: "rgba(245,242,236,0.7)", maxWidth: 520, lineHeight: 1.55 }}>{t("upgrade.big_sub")}</p>
      </div>
      <Button variant="primary" size="lg" trailingIcon="arrow-right" onClick={() => window.open("/pricing", "_blank", "noopener")}>{t("upgrade.cta")}</Button>
    </div>
  );
}

// Lightweight sparkline / trend line (SVG)
function TrendChart({ points, height = 96, color = "var(--accent-500)", fill = true, id = "g" }) {
  const w = 600;
  if (!Array.isArray(points) || points.length < 2) return null; // one point can't make a line
  const max = Math.max(...points), min = Math.min(...points);
  const range = max - min || 1;
  const step = w / (points.length - 1);
  const coords = points.map((p, i) => [i * step, height - 10 - ((p - min) / range) * (height - 24)]);
  const line = coords.map((c, i) => (i === 0 ? "M" : "L") + c[0].toFixed(1) + " " + c[1].toFixed(1)).join(" ");
  const area = line + ` L ${w} ${height} L 0 ${height} Z`;
  return (
    <svg viewBox={`0 0 ${w} ${height}`} preserveAspectRatio="none" style={{ width: "100%", height, display: "block" }}>
      <defs>
        <linearGradient id={"tc"+id} x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stopColor={color} stopOpacity="0.22" />
          <stop offset="1" stopColor={color} stopOpacity="0" />
        </linearGradient>
      </defs>
      {fill && <path d={area} fill={`url(#tc${id})`} />}
      <path d={line} fill="none" stroke={color} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
      {coords.filter((_,i)=>i===coords.length-1).map((c,i)=>(
        <circle key={i} cx={c[0]} cy={c[1]} r="4" fill={color} stroke="#fff" strokeWidth="2" />
      ))}
    </svg>
  );
}

// Opportunity map, horizontal cluster bars
function OpportunityMap({ items }) {
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
      {items.map((it, i) => (
        <div key={i} style={{ display: "grid", gridTemplateColumns: "160px 1fr 44px", alignItems: "center", gap: 14 }}>
          <span style={{ display: "inline-flex", alignItems: "center", gap: 8, fontSize: 13, fontWeight: 600, color: "var(--text-body)" }}>
            <span style={{ width: 8, height: 8, borderRadius: 999, background: it.color }} />{it.label}
          </span>
          <span style={{ height: 8, background: "var(--ink-100)", borderRadius: 999, overflow: "hidden" }}>
            <span style={{ display: "block", height: "100%", width: it.pct+"%", background: it.color, borderRadius: 999, transition: "width var(--dur-slow) var(--ease-out)" }} />
          </span>
          <span className="lt-num" style={{ fontFamily: "var(--font-mono)", fontSize: 13, fontWeight: 600, color: "var(--text-strong)", textAlign: "right" }}>{it.value}</span>
        </div>
      ))}
    </div>
  );
}

// Bottom sheet (mobile-first overlay): scrim + spring slide-up panel with a
// drag handle; swipe down (or tap the scrim / press Escape) to close. On
// desktop it still works as a centered small modal replacement, but its home
// is phone filters/pickers. Content scrolls inside; safe-area padded.
function BottomSheet({ open, onClose, title, children, maxHeight = "82dvh" }) {
  const panelRef = React.useRef(null);
  const dragRef = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const prev = document.documentElement.style.overflow;
    document.documentElement.style.overflow = "hidden";
    // Depth cue: the app plane recedes behind the sheet (the sheet itself is
    // portaled to <body>, so it can never be trapped by this transform).
    const app = document.getElementById("app");
    if (app) app.classList.add("lt-sheet-open");
    const onKey = (e) => { if (e.key === "Escape") onClose && onClose(); };
    window.addEventListener("keydown", onKey);
    return () => {
      document.documentElement.style.overflow = prev;
      if (app) app.classList.remove("lt-sheet-open");
      window.removeEventListener("keydown", onKey);
    };
  }, [open, onClose]);
  if (!open) return null;
  const ReactDOM = window.ReactDOM;
  const onTouchStart = (e) => {
    if (!e.touches || e.touches.length !== 1) return;
    dragRef.current = { y0: e.touches[0].clientY, dy: 0, t0: performance.now(), moved: false };
  };
  const onTouchMove = (e) => {
    const d = dragRef.current; if (!d) return;
    const dy = e.touches[0].clientY - d.y0;
    if (!d.moved && Math.abs(dy) < 6) return;
    d.moved = true;
    d.dy = Math.max(0, dy); d.yt = e.touches[0].clientY; d.tt = performance.now();
    if (panelRef.current) { panelRef.current.style.transition = "none"; panelRef.current.style.transform = "translateY(" + d.dy + "px)"; }
  };
  const onTouchEnd = () => {
    const d = dragRef.current; dragRef.current = null;
    const el = panelRef.current;
    if (!d || !d.moved || !el) return;
    const vel = ((d.yt - d.y0) / Math.max(1, d.tt - d.t0)) * 1000;
    const h = el.offsetHeight;
    if (d.dy > h * 0.35 || vel > 500) { onClose && onClose(); }
    else { el.style.transition = "transform .3s var(--ease-spring)"; el.style.transform = "translateY(0)"; }
  };
  // Portal to <body>: screen-entrance animations leave a transform on the
  // route wrapper, which would hijack position:fixed as a containing block.
  const sheet = (
    <div style={{ position: "fixed", inset: 0, zIndex: 130, display: "flex", alignItems: "flex-end", justifyContent: "center" }}>
      <div onClick={onClose} style={{ position: "absolute", inset: 0, background: "rgba(8,16,12,0.5)" }} />
      <div ref={panelRef} role="dialog" aria-modal="true" aria-label={title || undefined} className="lt-sheet-in"
        onTouchStart={onTouchStart} onTouchMove={onTouchMove} onTouchEnd={onTouchEnd} onTouchCancel={onTouchEnd}
        style={{ position: "relative", width: "min(100%, 560px)", maxHeight, display: "flex", flexDirection: "column",
          background: "var(--paper)", borderRadius: "16px 16px 0 0", boxShadow: "var(--shadow-xl)",
          paddingBottom: "var(--safe-bottom, 0px)" }}>
        <div style={{ display: "grid", placeItems: "center", padding: "10px 0 2px", touchAction: "none" }} aria-hidden>
          <span style={{ width: 40, height: 4, borderRadius: 999, background: "var(--ink-200)" }} />
        </div>
        {(title || onClose) && (
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10, padding: "6px 16px 10px" }}>
            <span style={{ fontSize: 15, fontWeight: 700, color: "var(--text-strong)" }}>{title || ""}</span>
            <button onClick={onClose} aria-label={t("chrome.close")} style={{ width: 40, height: 40, display: "grid", placeItems: "center", border: "none", background: "transparent", borderRadius: 10, cursor: "pointer", color: "var(--text-faint)" }}>
              <Icon name="x" size={18} />
            </button>
          </div>
        )}
        <div style={{ overflowY: "auto", WebkitOverflowScrolling: "touch", padding: "0 16px 16px" }}>{children}</div>
      </div>
    </div>
  );
  return ReactDOM && ReactDOM.createPortal ? ReactDOM.createPortal(sheet, document.body) : sheet;
}

// Section header with title + action slot (wraps on narrow viewports so the
// action row drops below the title instead of clipping off-screen)
function SectionTitle({ eyebrow, title, sub, right }) {
  return (
    <div style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: 16, marginBottom: 18, flexWrap: "wrap" }}>
      <div>
        {eyebrow && <div className="lt-eyebrow" style={{ marginBottom: 6 }}>{eyebrow}</div>}
        <h2 style={{ fontSize: 24, fontWeight: 700, letterSpacing: "-0.02em", color: "var(--text-strong)" }}>{title}</h2>
        {sub && <p style={{ fontSize: 14, color: "var(--text-muted)", marginTop: 5 }}>{sub}</p>}
      </div>
      {right}
    </div>
  );
}

Object.assign(window.LTQ, { IntentBadge, PlatformBadge, Difficulty, Demand, Score, EmptyState, SQ, sqCheer, ProTag, UpgradeCard, TrendChart, OpportunityMap, SectionTitle, dataLabel, BP, useMedia, usePhone, useCompact, useCoarse, BottomSheet });
})();
