/* lib.jsx — Shared primitives & data-poetic graphics */
/* global React */
const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ── Hooks ──────────────────────────────────────────────────────────
function useInView(opts = { threshold: 0.15 }) {
  const ref = useRef(null);
  const [inView, setInView] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    const obs = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setInView(true); obs.disconnect(); }
    }, opts);
    obs.observe(ref.current);
    return () => obs.disconnect();
  }, []);
  return [ref, inView];
}

// Counter that runs once on mount or when triggered
function useCounter(target, { duration = 1400, delay = 0, run = true } = {}) {
  const [val, setVal] = useState(0);
  useEffect(() => {
    if (!run) return;
    let raf, start;
    const t = setTimeout(() => {
      const tick = (now) => {
        if (!start) start = now;
        const p = Math.min(1, (now - start) / duration);
        // easeOutCubic
        const eased = 1 - Math.pow(1 - p, 3);
        setVal(target * eased);
        if (p < 1) raf = requestAnimationFrame(tick);
        else setVal(target);
      };
      raf = requestAnimationFrame(tick);
    }, delay);
    return () => { clearTimeout(t); cancelAnimationFrame(raf); };
  }, [target, duration, delay, run]);
  return val;
}

function formatCompact(n) {
  if (n >= 1_000_000) return (n / 1_000_000).toFixed(1).replace(/\.0$/, '') + 'M';
  if (n >= 10_000) return Math.round(n / 1000) + 'K';
  if (n >= 1_000) return (n / 1000).toFixed(1).replace(/\.0$/, '') + 'K';
  return Math.round(n).toLocaleString();
}

function formatMoney(n, currency = '$') {
  if (n >= 1_000_000) return currency + (n / 1_000_000).toFixed(2).replace(/\.?0+$/, '') + 'M';
  if (n >= 1_000) return currency + (n / 1000).toFixed(1).replace(/\.0$/, '') + 'K';
  return currency + Math.round(n).toLocaleString();
}

// ── Counter (component) ───────────────────────────────────────────
function Counter({ to, prefix = '', suffix = '', decimals = 0, duration = 1500, delay = 0, format, run = true }) {
  const v = useCounter(to, { duration, delay, run });
  const display = format ? format(v) : (
    decimals > 0
      ? v.toFixed(decimals)
      : Math.round(v).toLocaleString()
  );
  return <span className="tabular">{prefix}{display}{suffix}</span>;
}

// ── Sparkline ─────────────────────────────────────────────────────
function Sparkline({ data, color = 'var(--cyan)', fillId = 'sparkFill', height = 44, animate = true, delay = 0 }) {
  const w = 160;
  const h = height;
  const min = Math.min(...data), max = Math.max(...data);
  const range = max - min || 1;
  const pts = data.map((v, i) => {
    const x = (i / (data.length - 1)) * w;
    const y = h - ((v - min) / range) * (h - 8) - 4;
    return [x, y];
  });
  const line = pts.map((p, i) => `${i === 0 ? 'M' : 'L'}${p[0]},${p[1]}`).join(' ');
  const area = line + ` L${w},${h} L0,${h} Z`;
  const lineRef = useRef(null);
  const [len, setLen] = useState(300);
  useEffect(() => {
    if (lineRef.current) setLen(lineRef.current.getTotalLength());
  }, [data]);
  return (
    <svg className="spark" viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none">
      <defs>
        <linearGradient id={fillId} x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor={color} stopOpacity="0.28" />
          <stop offset="100%" stopColor={color} stopOpacity="0" />
        </linearGradient>
      </defs>
      <path d={area} fill={`url(#${fillId})`} />
      <path
        ref={lineRef}
        d={line}
        className={animate ? 'line draw-on' : 'line'}
        style={{ '--len': len, '--d': delay + 's', stroke: color }}
      />
    </svg>
  );
}

// ── Animated bar chart (vertical) ────────────────────────────────
function BarChart({ data, height = 160, color = 'var(--cyan)', labelFmt }) {
  // data: [{label, value, color?}]
  const max = Math.max(...data.map(d => d.value), 1);
  return (
    <div className="row items-end gap-12" style={{ height: height + 36 }}>
      {data.map((d, i) => {
        const h = Math.max(8, (d.value / max) * height);
        return (
          <div key={i} className="flex-1 col items-center" style={{ height: '100%' }}>
            <div className="col" style={{ flex: 1, justifyContent: 'flex-end', alignItems: 'center', width: '100%' }}>
              <span className="bold tabular" style={{ fontSize: 12, marginBottom: 6 }}>
                {labelFmt ? labelFmt(d.value) : d.value}
              </span>
              <div
                className="grow-h"
                style={{
                  width: '70%', height: h,
                  background: d.color || color,
                  borderRadius: '8px 8px 4px 4px',
                  '--d': (i * 0.08) + 's'
                }}
              />
            </div>
            <span className="cap-label" style={{ marginTop: 8 }}>{d.label}</span>
          </div>
        );
      })}
    </div>
  );
}

// ── Line chart (multi-series) ────────────────────────────────────
function LineChart({ series, height = 220, animate = true, ySteps = 4, xLabels = [] }) {
  // series: [{name, color, data:[]}]
  const w = 640;
  const h = height;
  const pad = { l: 36, r: 12, t: 12, b: 30 };
  const all = series.flatMap(s => s.data);
  const min = 0;
  const max = Math.max(...all) * 1.1;
  const range = max - min || 1;
  const n = series[0].data.length;
  const xstep = (w - pad.l - pad.r) / (n - 1);
  const yScale = (v) => pad.t + (h - pad.t - pad.b) * (1 - (v - min) / range);

  return (
    <svg viewBox={`0 0 ${w} ${h}`} style={{ width: '100%', height }}>
      {/* y grid */}
      {Array.from({ length: ySteps + 1 }).map((_, i) => {
        const v = (max / ySteps) * i;
        const y = yScale(v);
        return (
          <g key={i}>
            <line x1={pad.l} x2={w - pad.r} y1={y} y2={y} stroke="rgba(0,43,80,0.06)" strokeDasharray="2 4" />
            <text x={pad.l - 8} y={y + 4} fontSize="10" fill="var(--ink-soft)" textAnchor="end" fontFamily="Inter">
              {formatCompact(v)}
            </text>
          </g>
        );
      })}
      {/* x labels */}
      {xLabels.map((lab, i) => (
        <text key={i} x={pad.l + i * xstep} y={h - 10} fontSize="10" fill="var(--ink-soft)" textAnchor="middle" fontFamily="Inter">
          {lab}
        </text>
      ))}
      {/* series */}
      {series.map((s, si) => {
        const pts = s.data.map((v, i) => [pad.l + i * xstep, yScale(v)]);
        const line = pts.map((p, i) => `${i === 0 ? 'M' : 'L'}${p[0]},${p[1]}`).join(' ');
        const area = line + ` L${pad.l + (n - 1) * xstep},${h - pad.b} L${pad.l},${h - pad.b} Z`;
        const len = 1400;
        return (
          <g key={si}>
            <defs>
              <linearGradient id={`lc-${si}`} x1="0" y1="0" x2="0" y2="1">
                <stop offset="0%" stopColor={s.color} stopOpacity="0.22" />
                <stop offset="100%" stopColor={s.color} stopOpacity="0" />
              </linearGradient>
            </defs>
            <path d={area} fill={`url(#lc-${si})`} opacity={animate ? 0 : 1}
              style={{ animation: animate ? `fadeIn .8s ${si * 0.2 + 0.6}s forwards` : 'none' }} />
            <path
              d={line}
              fill="none" stroke={s.color} strokeWidth="2.6"
              strokeLinecap="round" strokeLinejoin="round"
              className={animate ? 'draw-on' : ''}
              style={{ '--len': len, '--d': (si * 0.2) + 's' }}
            />
            {pts.map((p, i) => (
              <circle key={i} cx={p[0]} cy={p[1]} r="3" fill="white" stroke={s.color} strokeWidth="2"
                opacity={animate ? 0 : 1}
                style={{ animation: animate ? `fadeIn .3s ${1.4 + si * 0.2 + i * 0.04}s forwards` : 'none' }} />
            ))}
          </g>
        );
      })}
      <style>{`@keyframes fadeIn { to { opacity: 1; } }`}</style>
    </svg>
  );
}

// ── Donut chart ──────────────────────────────────────────────────
function Donut({ data, size = 180, thickness = 22, centerLabel, centerValue, animate = true }) {
  // data: [{value, color, label}]
  const total = data.reduce((s, d) => s + d.value, 0);
  const r = size / 2 - thickness / 2;
  const c = 2 * Math.PI * r;
  let acc = 0;
  return (
    <div style={{ position: 'relative', width: size, height: size }}>
      <svg width={size} height={size} style={{ transform: 'rotate(-90deg)' }}>
        <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke="var(--bg-soft)" strokeWidth={thickness} />
        {data.map((d, i) => {
          const frac = d.value / total;
          const dash = c * frac;
          const offset = c * acc;
          acc += frac;
          return (
            <circle
              key={i}
              cx={size / 2} cy={size / 2} r={r}
              fill="none"
              stroke={d.color}
              strokeWidth={thickness}
              strokeDasharray={`${dash} ${c - dash}`}
              strokeDashoffset={-offset}
              strokeLinecap="butt"
              style={{
                transformOrigin: 'center',
                transition: 'stroke-dasharray 1.2s cubic-bezier(.4,.05,.2,1)',
                animation: animate ? `donut-grow${i} 1.4s ${0.2 + i * 0.1}s cubic-bezier(.4,.05,.2,1) backwards` : 'none'
              }}
            />
          );
        })}
        <style>{data.map((d, i) => {
          const dash = c * (d.value / total);
          return `@keyframes donut-grow${i} { from { stroke-dasharray: 0 ${c}; } to { stroke-dasharray: ${dash} ${c - dash}; } }`;
        }).join(' ')}</style>
      </svg>
      <div style={{
        position: 'absolute', inset: 0,
        display: 'grid', placeItems: 'center', textAlign: 'center'
      }}>
        <div>
          {centerLabel && <div className="cap-label">{centerLabel}</div>}
          {centerValue && <div className="title-xl tabular" style={{ marginTop: 2 }}>{centerValue}</div>}
        </div>
      </div>
    </div>
  );
}

// ── Match circle (progress around avatar) ────────────────────────
function MatchCircle({ score, size = 56, color, label }) {
  const r = size / 2 - 4;
  const c = 2 * Math.PI * r;
  const fill = c * (score / 100);
  const stroke = color || (score >= 80 ? 'var(--mint-deep)' : score >= 60 ? 'var(--cyan)' : score >= 40 ? 'var(--yellow-deep)' : 'var(--coral)');
  return (
    <div style={{ position: 'relative', width: size, height: size, display: 'inline-block' }}>
      <svg width={size} height={size} className="match-circle">
        <circle cx={size / 2} cy={size / 2} r={r} className="track" />
        <circle
          cx={size / 2} cy={size / 2} r={r}
          className="fill"
          stroke={stroke}
          strokeDasharray={`${fill} ${c - fill}`}
          style={{ animation: 'mc-grow 1.2s cubic-bezier(.4,.05,.2,1) forwards' }}
        />
      </svg>
      <div style={{
        position: 'absolute', inset: 0,
        display: 'grid', placeItems: 'center'
      }}>
        <div className="text-center">
          <div className="bold tabular" style={{ fontSize: size > 50 ? 14 : 11, lineHeight: 1 }}>{score}<span style={{ fontSize: 9 }}>%</span></div>
          {label && <div className="cap-label" style={{ fontSize: 8, marginTop: 1 }}>{label}</div>}
        </div>
      </div>
      <style>{`@keyframes mc-grow { from { stroke-dasharray: 0 ${c}; } }`}</style>
    </div>
  );
}

// ── Avatar (deterministic gradient by seed) ──────────────────────
function Avatar({ name, size = 40, src, gradient }) {
  const initials = (name || 'A').split(' ').slice(0, 2).map(s => s[0]).join('').toUpperCase();
  const seed = name ? name.charCodeAt(0) + (name.charCodeAt(1) || 0) : 0;
  const palettes = [
    ['#009CFF', '#1C1CC9'],
    ['#FF6FA3', '#1C1CC9'],
    ['#1FD6B4', '#009CFF'],
    ['#FEE960', '#FF7A59'],
    ['#C9A3E8', '#FF6FA3'],
    ['#79FEE7', '#009CFF'],
  ];
  const grad = gradient || palettes[seed % palettes.length];
  if (src) return <img src={src} alt={name} style={{ width: size, height: size, borderRadius: '50%', objectFit: 'cover' }} />;
  return (
    <div style={{
      width: size, height: size, borderRadius: '50%',
      background: `linear-gradient(135deg, ${grad[0]}, ${grad[1]})`,
      color: 'white', fontWeight: 800,
      display: 'grid', placeItems: 'center',
      fontSize: size * 0.36, letterSpacing: '-0.02em',
      flexShrink: 0,
      fontFamily: 'Space Grotesk, sans-serif'
    }}>
      {initials}
    </div>
  );
}

// ── Tag/Badge ────────────────────────────────────────────────────
function Pill({ children, variant = 'cyan', icon }) {
  return <span className={`pill pill-${variant}`}>{icon}{children}</span>;
}

// ── Reveal (scroll-triggered) ────────────────────────────────────
function Reveal({ children, delay = 0, as: As = 'div', className = '', style = {} }) {
  const [ref, inView] = useInView();
  return (
    <As
      ref={ref}
      className={`reveal ${inView ? 'is-visible' : ''} ${className}`}
      style={{ transitionDelay: delay + 'ms', ...style }}
    >
      {children}
    </As>
  );
}

// ── Decorative SVG illustrations ─────────────────────────────────
function MascotConstellation({ size = 360 }) {
  // A poetic "constellation" of nodes — feels like influencer network
  const nodes = [
    [80, 60], [180, 30], [280, 80], [340, 170],
    [220, 220], [110, 200], [40, 140], [180, 130], [280, 260]
  ];
  return (
    <svg viewBox="0 0 360 300" width={size} style={{ overflow: 'visible' }}>
      <defs>
        <radialGradient id="halo" cx="50%" cy="50%" r="50%">
          <stop offset="0%" stopColor="#EAC2FB" stopOpacity="0.55" />
          <stop offset="100%" stopColor="#EAC2FB" stopOpacity="0" />
        </radialGradient>
      </defs>
      <circle cx="180" cy="150" r="160" fill="url(#halo)" />
      {/* lines */}
      {nodes.map((a, i) => nodes.slice(i + 1).map((b, j) => {
        if (Math.random() > 0.55) return null;
        const len = Math.hypot(a[0] - b[0], a[1] - b[1]);
        return (
          <line key={`${i}-${j}`} x1={a[0]} y1={a[1]} x2={b[0]} y2={b[1]}
            stroke="#1C1CC9" strokeOpacity="0.18" strokeWidth="1.2"
            strokeDasharray={len} strokeDashoffset={len}
            style={{ animation: `draw 1.6s ${0.2 + i * 0.05}s ease-out forwards` }} />
        );
      }))}
      {/* nodes */}
      {nodes.map(([x, y], i) => (
        <g key={i} style={{ transformOrigin: `${x}px ${y}px`, animation: `nodeIn .6s ${0.4 + i * 0.08}s backwards` }}>
          <circle cx={x} cy={y} r="14" fill="white" stroke="#1C1CC9" strokeWidth="2" />
          <circle cx={x} cy={y} r="6" fill={['#009CFF', '#FF6FA3', '#1FD6B4', '#FEE960'][i % 4]} />
        </g>
      ))}
      <style>{`@keyframes nodeIn { from { transform: scale(0); opacity: 0; } to { transform: scale(1); opacity: 1; } }`}</style>
    </svg>
  );
}

// Decorative blob with face (mascot)
function Mascot({ size = 220 }) {
  return (
    <svg viewBox="0 0 240 240" width={size} style={{ overflow: 'visible' }}>
      <defs>
        <linearGradient id="mGrad" x1="0" x2="1" y1="0" y2="1">
          <stop offset="0%" stopColor="#79FEE7" />
          <stop offset="100%" stopColor="#009CFF" />
        </linearGradient>
      </defs>
      <g style={{ animation: 'float 4.5s ease-in-out infinite', transformOrigin: 'center' }}>
        <path d="M120 12 C 180 12, 228 60, 228 122 C 228 184, 180 228, 122 228 C 50 228, 12 178, 12 116 C 12 56, 60 12, 120 12 Z"
          fill="url(#mGrad)" />
        {/* eyes */}
        <circle cx="92" cy="108" r="11" fill="#002B50" />
        <circle cx="156" cy="108" r="11" fill="#002B50" />
        <circle cx="95" cy="104" r="3" fill="white" />
        <circle cx="159" cy="104" r="3" fill="white" />
        {/* mouth */}
        <path d="M96 150 Q 124 174 152 150" stroke="#002B50" strokeWidth="5" fill="none" strokeLinecap="round" />
        {/* sparkle */}
        <g style={{ transformOrigin: '200px 50px', animation: 'spin 6s linear infinite' }}>
          <path d="M200 36 L204 50 L218 54 L204 58 L200 72 L196 58 L182 54 L196 50 Z" fill="#FEE960" />
        </g>
      </g>
    </svg>
  );
}

// ── Tiny icons (Lucide-style SVG) ────────────────────────────────
const Icon = {
  search: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="7" /><path d="m21 21-4.3-4.3" /></svg>,
  bell: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" /><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0" /></svg>,
  spark: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m12 3 1.9 5.8a2 2 0 0 0 1.3 1.3L21 12l-5.8 1.9a2 2 0 0 0-1.3 1.3L12 21l-1.9-5.8a2 2 0 0 0-1.3-1.3L3 12l5.8-1.9a2 2 0 0 0 1.3-1.3Z" /></svg>,
  home: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22V12h6v10"/></svg>,
  file: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>,
  users: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>,
  megaphone: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m3 11 18-5v12L3 14v-3z"/><path d="M11.6 16.8a3 3 0 1 1-5.8-1.6"/></svg>,
  chart: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 3v18h18"/><path d="m7 14 4-4 4 4 5-5"/></svg>,
  settings: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>,
  plus: <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M12 5v14"/></svg>,
  arrow: <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M13 5l7 7-7 7"/></svg>,
  arrowDown: <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M12 5v14M5 12l7 7 7-7"/></svg>,
  check: <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><path d="m5 12 5 5L20 7"/></svg>,
  trend: <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="m23 6-9.5 9.5-5-5L1 18"/><path d="M17 6h6v6"/></svg>,
  trendDown: <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="m23 18-9.5-9.5-5 5L1 6"/><path d="M17 18h6v-6"/></svg>,
  tiktok: <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M19.59 6.69a4.83 4.83 0 0 1-3.77-4.25V2h-3.45v13.67a2.89 2.89 0 0 1-5.2 1.74 2.89 2.89 0 0 1 2.31-4.64 2.93 2.93 0 0 1 .88.13V9.4a6.84 6.84 0 0 0-1-.05A6.33 6.33 0 0 0 5.8 20.1a6.34 6.34 0 0 0 10.86-4.43V8.97a8.16 8.16 0 0 0 4.77 1.52V7.04a4.85 4.85 0 0 1-1.84-.35z"/></svg>,
  instagram: <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2"><rect x="2" y="2" width="20" height="20" rx="5"/><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"/><circle cx="17.5" cy="6.5" r=".5" fill="currentColor"/></svg>,
  youtube: <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M23.5 6.2a3 3 0 0 0-2.1-2.1C19.5 3.6 12 3.6 12 3.6s-7.5 0-9.4.5A3 3 0 0 0 .5 6.2 31 31 0 0 0 0 12a31 31 0 0 0 .5 5.8 3 3 0 0 0 2.1 2.1c1.9.5 9.4.5 9.4.5s7.5 0 9.4-.5a3 3 0 0 0 2.1-2.1 31 31 0 0 0 .5-5.8 31 31 0 0 0-.5-5.8zM9.6 15.6V8.4l6.2 3.6z"/></svg>,
  filter: <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>,
  sliders: <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="4" x2="4" y1="21" y2="14"/><line x1="4" x2="4" y1="10" y2="3"/><line x1="12" x2="12" y1="21" y2="12"/><line x1="12" x2="12" y1="8" y2="3"/><line x1="20" x2="20" y1="21" y2="16"/><line x1="20" x2="20" y1="12" y2="3"/><line x1="2" x2="6" y1="14" y2="14"/><line x1="10" x2="14" y1="8" y2="8"/><line x1="18" x2="22" y1="16" y2="16"/></svg>,
  dollar: <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="12" x2="12" y1="2" y2="22"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>,
  external: <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" x2="21" y1="14" y2="3"/></svg>,
  verified: <svg viewBox="0 0 24 24" width="14" height="14" fill="#009CFF" stroke="white" strokeWidth="2.5" strokeLinejoin="round"><path d="m12 2 2.4 2.4L18 5l.5 3.5L21 11l-2.5 2.5L18 17l-3.6.6L12 20l-2.4-2.4L6 17l-.5-3.5L3 11l2.5-2.5L6 5l3.6-.6z"/><path d="m9 12 2 2 4-4" stroke="white" fill="none"/></svg>,
  flame: <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 17c1.4 0 2.5-1.1 2.5-2.5 0-1.3-.7-2-1.5-3-1.4-1.7-1.4-3.5 0-5.5 1.5-2 4-3 4-3s.5 2.5 0 4.5c0 2.5 1.5 3 2 3 2 0 4-1 4-5.5 0 0-2.5 4.5-5.5 4.5"/></svg>,
  globe: <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" x2="22" y1="12" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>,
  trophy: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"/><path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"/><path d="M4 22h16"/><path d="M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22"/><path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22"/><path d="M18 2H6v7a6 6 0 0 0 12 0V2Z"/></svg>,
  bag: <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z"/><path d="M3 6h18M16 10a4 4 0 0 1-8 0"/></svg>,
};

// expose
Object.assign(window, {
  useInView, useCounter, formatCompact, formatMoney,
  Counter, Sparkline, BarChart, LineChart, Donut, MatchCircle,
  Avatar, Pill, Reveal, MascotConstellation, Mascot, Icon
});
