// VisitorPulse — Hatch edition
// Clusters: fashion-dress · nursing-practical · lounge-comfort · swimwear-resort · basics-neutral

function _hatchClusterProducts(filterFn, limit) {
  return (window.PRODUCTS || []).filter(filterFn).slice(0, limit || 4).map(p => p.id);
}

const CLUSTERS = [
  {
    id: 'fashion-dress',
    label: 'Dresses · Patterns · Statement',
    short: 'Dresses · Style',
    color: '#C47A9A',
    angle: -30,
    products: _hatchClusterProducts(p => p.cats.includes('dress') && (p.cats.includes('floral') || p.cats.includes('pattern') || p.cats.includes('crochet') || p.cats.includes('color-pop'))),
  },
  {
    id: 'nursing-practical',
    label: 'Nursing · Post-partum · Functional',
    short: 'Nursing · Practical',
    color: '#5B8FA8',
    angle: 50,
    products: _hatchClusterProducts(p => p.cats.includes('nursing') && !p.cats.includes('sleepwear')),
  },
  {
    id: 'lounge-comfort',
    label: 'Lounge · Sleepwear · Basics',
    short: 'Lounge · Sleep',
    color: '#A7A0EB',
    angle: -150,
    products: _hatchClusterProducts(p => p.cats.includes('sleepwear') || p.cats.includes('lounge')),
  },
  {
    id: 'swimwear-resort',
    label: 'Swimwear · Resort · Vacation',
    short: 'Swim · Resort',
    color: '#D89251',
    angle: -90,
    products: _hatchClusterProducts(p => p.cats.includes('swimwear')),
  },
  {
    id: 'basics-neutral',
    label: 'Basics · Neutrals · Wardrobe Builders',
    short: 'Basics · Neutral',
    color: '#7AAB7C',
    angle: 130,
    products: _hatchClusterProducts(p => p.cats.includes('neutral') && (p.cats.includes('top') || p.cats.includes('bottom')) && !p.cats.includes('nursing')),
  },
];

const REST_R = 42;
const NEAR_R = 15;

const PERSONA_AFFINITY = {
  'style-forward':  { 'fashion-dress': 1.00, 'swimwear-resort': 0.55, 'basics-neutral': 0.20, 'nursing-practical': 0.15, 'lounge-comfort': 0.08 },
  'comfort-first':  { 'lounge-comfort': 1.00, 'basics-neutral': 0.75, 'nursing-practical': 0.55, 'fashion-dress': 0.18, 'swimwear-resort': 0.05 },
  'new-mom':        { 'nursing-practical': 1.00, 'lounge-comfort': 0.72, 'basics-neutral': 0.58, 'fashion-dress': 0.35, 'swimwear-resort': 0.04 },
};

const PRODUCT_TO_CLUSTER = (() => {
  const map = {};
  for (const c of CLUSTERS) for (const pid of c.products) map[pid] = c.id;
  return map;
})();

const PERSONA_NARRATIVE = {
  'style-forward': {
    primaryCluster: 'fashion-dress',
    bars: ['Fashion Dresses', 'Color & Pattern', 'Crochet & Texture', 'Swimwear', 'Premium Tier', 'Statement Tops'],
    barKey: ['dress', 'color-pop', 'crochet', 'swimwear', 'premium', 'top'],
    perStep: {
      welcome: { bars: [0.38, 0.30, 0.24, 0.20, 0.22, 0.18], events: [{ kind: 'PAGE VIEW', label: 'New Arrivals PLP', val: '96 items' }] },
      plp:     { bars: [0.62, 0.56, 0.44, 0.36, 0.44, 0.34], events: [
        { kind: 'DWELL', label: 'Floral Crochet Dress · Sand', val: '14s' },
        { kind: 'HOVER', label: 'Bella Dress · Marguerite Fleur', val: 'hover 3×' },
      ]},
      search:  { bars: [0.76, 0.70, 0.56, 0.48, 0.60, 0.44], events: [
        { kind: 'SEARCH', label: '"materity dress beach vacation" (typo)', val: 'mal-rank' },
      ]},
      pdp:     { bars: [0.88, 0.82, 0.70, 0.58, 0.74, 0.56], events: [
        { kind: 'DWELL', label: 'Maisie Dress White · PDP', val: '22s' },
        { kind: 'VIEW RECS', label: 'Maci Crochet Short pairing', val: 'open' },
      ]},
      merch:   { bars: [0.96, 0.90, 0.82, 0.70, 0.88, 0.70], events: [
        { kind: 'BUNDLE', label: '+ Audrey Dress · Palm Shadows', val: 'open' },
      ]},
    },
  },
  'comfort-first': {
    primaryCluster: 'lounge-comfort',
    bars: ['Lounge & Sleep', 'Soft Basics', 'Nursing-ready', 'Neutral Palette', 'Value Pieces', 'Comfort Tops'],
    barKey: ['lounge', 'neutral', 'nursing', 'mid-range', 'budget', 'top'],
    perStep: {
      welcome: { bars: [0.40, 0.34, 0.26, 0.30, 0.22, 0.30], events: [{ kind: 'PAGE VIEW', label: 'New Arrivals PLP', val: '96 items' }] },
      plp:     { bars: [0.64, 0.58, 0.46, 0.50, 0.38, 0.50], events: [
        { kind: 'DWELL', label: 'Softluxe Delivery Nightgown', val: '16s' },
        { kind: 'HOVER', label: 'Softluxe Pant · Light Grey', val: 'hover 2×' },
      ]},
      search:  { bars: [0.78, 0.70, 0.60, 0.64, 0.50, 0.64], events: [
        { kind: 'SEARCH', label: '"what to wear to hospital to give birth"', val: 'mal-rank' },
      ]},
      pdp:     { bars: [0.88, 0.80, 0.72, 0.74, 0.62, 0.76], events: [
        { kind: 'DWELL', label: 'Maisie Dress White · PDP', val: '8s' },
        { kind: 'ADD INTENT', label: 'Softluxe Robe', val: 'cart 1×' },
      ]},
      merch:   { bars: [0.96, 0.90, 0.84, 0.86, 0.78, 0.86], events: [
        { kind: 'BUNDLE', label: '+ Cotton Nursing PJ Set', val: 'open' },
      ]},
    },
  },
  'new-mom': {
    primaryCluster: 'nursing-practical',
    bars: ['Nursing', 'Lounge & Recovery', 'Nursing Dresses', 'Soft Basics', 'Mid-range', 'Post-partum Tops'],
    barKey: ['nursing', 'sleepwear', 'dress', 'neutral', 'mid-range', 'top'],
    perStep: {
      welcome: { bars: [0.44, 0.36, 0.28, 0.32, 0.28, 0.34], events: [{ kind: 'PAGE VIEW', label: 'New Arrivals PLP', val: '96 items' }] },
      plp:     { bars: [0.66, 0.56, 0.46, 0.50, 0.44, 0.54], events: [
        { kind: 'DWELL', label: 'Eloise Pointelle Nursing Dress', val: '12s' },
        { kind: 'HOVER', label: 'Isla Nursing Top · Natural Stripe', val: 'hover 2×' },
      ]},
      search:  { bars: [0.80, 0.68, 0.60, 0.62, 0.56, 0.68], events: [
        { kind: 'SEARCH', label: '"nursing top that doesnt look frumpy"', val: 'mal-rank' },
      ]},
      pdp:     { bars: [0.90, 0.78, 0.60, 0.74, 0.68, 0.80], events: [
        { kind: 'DWELL', label: 'Maisie Dress White · PDP', val: '6s' },
        { kind: 'VIEW RECS', label: 'Ava Nursing Top pairing', val: 'open' },
      ]},
      merch:   { bars: [0.96, 0.88, 0.72, 0.86, 0.80, 0.90], events: [
        { kind: 'BUNDLE', label: '+ Eloise Nursing Dress · Rose', val: 'open' },
      ]},
    },
  },
};

const PERSONA_CONTEXT = {
  'style-forward': { visitorId: 'usr_4821', returning: '1st visit · no account', device: 'Desktop · Mac · 1680w', referrer: 'instagram.com · organic browse', location: 'New York, NY', time: 'Sat 2:14p · afternoon' },
  'comfort-first': { visitorId: 'usr_7639', returning: '2nd visit · no account', device: 'iOS 17 · 390w · mobile', referrer: 'google.com · "maternity clothes third trimester"', location: 'Seattle, WA', time: 'Tue 8:42a · morning' },
  'new-mom':       { visitorId: 'usr_2947', returning: '1st visit · no account', device: 'Android · 412w · mobile', referrer: 'pinterest.com · "nursing clothes postpartum"', location: 'Austin, TX', time: 'Thu 9:30p · evening' },
};

const STEP_ORDER   = ['welcome', 'plp', 'search', 'pdp', 'merch'];
const STEP_LABELS  = { welcome: 'Cold start', plp: 'PLP', search: 'Search', pdp: 'PDP', merch: 'Merch' };
const STEP_IMG_FLOOR    = { welcome: 0.00, plp: 0.20, search: 0.55, pdp: 0.80, merch: 0.92 };
const STEP_BLUR_CEILING = { welcome: 18,   plp: 5,    search: 2.5,  pdp: 1.0,  merch: 0.5 };
const STEP_BASE_DEF     = { welcome: 0.12, plp: 0.42, search: 0.70, pdp: 0.92, merch: 0.99 };

function VisitorPulse({ stepId, persona, favorites, expanded, setExpanded }) {
  React.useEffect(() => {
    if (!window.PRODUCTS) return;
    const seen = new Set();
    CLUSTERS.flatMap(c => c.products).forEach(pid => {
      if (seen.has(pid)) return; seen.add(pid);
      const p = window.PRODUCTS.find(x => x.id === pid);
      if (!p?.img) return;
      const img = new Image(); img.src = p.img;
    });
  }, []);

  const currentStepOrd = Math.max(0, STEP_ORDER.indexOf(stepId));
  const [sliderOrd, setSliderOrd] = React.useState(currentStepOrd);
  React.useEffect(() => { setSliderOrd(currentStepOrd); }, [currentStepOrd]);
  const effectiveStepId = STEP_ORDER[Math.max(0, Math.min(STEP_ORDER.length - 1, sliderOrd))];
  const isScrubbing = sliderOrd !== currentStepOrd;

  const narrative = PERSONA_NARRATIVE[persona.id] || PERSONA_NARRATIVE['style-forward'];
  const context   = PERSONA_CONTEXT[persona.id]   || PERSONA_CONTEXT['style-forward'];
  const stepData  = narrative.perStep[effectiveStepId] || narrative.perStep.welcome;
  const affinityMap = PERSONA_AFFINITY[persona.id] || PERSONA_AFFINITY['style-forward'];

  const heartClusterCounts = React.useMemo(() => {
    const counts = {};
    for (const fid of (favorites || [])) { const cid = PRODUCT_TO_CLUSTER[fid]; if (cid) counts[cid] = (counts[cid] || 0) + 1; }
    return counts;
  }, [favorites]);
  const heartTotal = Object.values(heartClusterCounts).reduce((s,v) => s+v, 0);

  const baseDef = STEP_BASE_DEF[effectiveStepId] ?? 0.12;
  const definition = Math.min(0.99, baseDef + Math.min(0.32, heartTotal * 0.10));

  const computedClusters = React.useMemo(() => {
    return CLUSTERS.map(c => {
      const aff = affinityMap[c.id] ?? 0.1;
      const hearts = heartClusterCounts[c.id] || 0;
      let pull = Math.pow(aff, 1.4) * (0.40 + definition * 1.00) + hearts * 0.40;
      pull = Math.max(0, Math.min(0.97, pull));
      const ang = c.angle * Math.PI / 180;
      const dist = REST_R - (REST_R - NEAR_R) * pull;
      return { ...c, pos: { x: 50 + Math.cos(ang)*dist, y: 50 + Math.sin(ang)*dist }, pull, affinity: aff, hearts, normDist: dist/REST_R };
    });
  }, [persona.id, definition, heartClusterCounts]);

  const visitorPos = { x: 50, y: 50 };

  const eventLog = React.useMemo(() => {
    const upto = STEP_ORDER.indexOf(effectiveStepId);
    const arr = [];
    for (let i = 0; i <= upto; i++) { const s = narrative.perStep[STEP_ORDER[i]]; if (s) for (const e of s.events) arr.push({ ...e, step: STEP_ORDER[i] }); }
    return arr.slice(-6);
  }, [effectiveStepId, persona.id]);

  return (
    <>
      <div aria-hidden="true" style={{ position:'absolute', width:1, height:1, overflow:'hidden', opacity:0, pointerEvents:'none', left:-9999, top:-9999 }}>
        {CLUSTERS.flatMap(c => c.products).map(pid => {
          const p = (window.PRODUCTS||[]).find(x => x.id===pid);
          if (!p?.img) return null;
          return <img key={pid} src={p.img} alt="" loading="eager" />;
        })}
      </div>

      {!expanded && (
        <button className="vp-rail" onClick={() => setExpanded(true)} title="Visitor Pulse · click to expand">
          <div className="vp-rail__mini">
            <VectorSpaceSVG clusters={computedClusters} visitorPos={visitorPos} definition={definition} stepId={effectiveStepId} compact />
          </div>
          <div className="vp-rail__label">VECTOR<span>·</span>384d</div>
          <div className="vp-rail__def">
            <div className="vp-rail__def-bar"><div className="vp-rail__def-fill" style={{ height:`${Math.round(definition*100)}%` }} /></div>
            <div className="vp-rail__def-num">{Math.round(definition*100)}%</div>
          </div>
          <div className="vp-rail__chev">‹</div>
        </button>
      )}

      {expanded && (
        <div className="vp-panel" role="dialog" aria-label="Visitor Pulse">
          <div className="vp-panel__head">
            <div className="vp-panel__head-l">
              <span className="vp-panel__eye">VISITOR PULSE</span>
              <span className="vp-panel__id">{context.visitorId} · {Math.round(definition*100)}% defined</span>
            </div>
            <button className="vp-panel__close" onClick={() => setExpanded(false)} aria-label="Collapse">›</button>
          </div>
          <div className="vp-panel__body">
            <div className="vp-panel__col vp-panel__col--left">
              <div className="vp-card vp-card--dark vp-card--context">
                <div className="vp-card__eye">USER CONTEXT</div>
                <div className="vp-rows">
                  {[['RETURNING', context.returning],['DEVICE', context.device],['REFERRER', context.referrer],['LOCATION', context.location],['TIME', context.time]].map(([k,v]) => (
                    <div key={k} className="vp-row"><span className="vp-row__k">{k}</span><span className="vp-row__v">{v}</span></div>
                  ))}
                </div>
              </div>
              <div className="vp-card vp-card--tracker">
                <div className="vp-card__eye vp-card__eye--tracker">IN-SESSION BEHAVIOURS <span className="vp-card__eye-meta">· <span className="vp-dot" />{eventLog.length} events</span></div>
                <div className="vp-events">
                  {eventLog.map((e,i) => (
                    <div key={i} className="vp-event">
                      <span className="vp-event__kind">{e.kind}</span>
                      <span className="vp-event__label">{e.label}</span>
                      <span className="vp-event__val">{e.val}</span>
                    </div>
                  ))}
                </div>
              </div>
            </div>

            <div className="vp-panel__col vp-panel__col--center">
              <div className="vp-card vp-card--cream">
                <div className="vp-card__eye vp-card__eye--cream">
                  VECTOR SPACE · 2D PROJECTION
                  <span style={{ marginLeft:10, color:'#9C9277', fontWeight:500, letterSpacing:'0.10em' }}>384 DIMS · UPDATED PER EVENT</span>
                </div>
                <div className="vp-space">
                  {currentStepOrd > 0 && (
                    <div className="vp-scrubber">
                      <div className="vp-scrubber__head">
                        <span className="vp-scrubber__eye">REPLAY PROGRESSION</span>
                        <span className="vp-scrubber__pos">{STEP_LABELS[effectiveStepId]}{isScrubbing && <span className="vp-scrubber__scrub">· scrubbing</span>}</span>
                      </div>
                      <input type="range" className="vp-scrubber__range" min={0} max={currentStepOrd} step={1} value={sliderOrd} onChange={e => setSliderOrd(Number(e.target.value))} />
                      <div className="vp-scrubber__ticks">
                        {STEP_ORDER.slice(0, currentStepOrd+1).map((s,i) => (
                          <button key={s} type="button" className={`vp-scrubber__tick ${i===sliderOrd?'is-active':''}`} onClick={() => setSliderOrd(i)}>{STEP_LABELS[s]}</button>
                        ))}
                      </div>
                    </div>
                  )}
                  <VectorSpaceSVG clusters={computedClusters} visitorPos={visitorPos} definition={definition} stepId={effectiveStepId} hearted={favorites||[]} visitorTag={context.visitorId} />
                </div>
              </div>
            </div>

            <div className="vp-panel__col vp-panel__col--right">
              <div className="vp-card vp-card--dark">
                <div className="vp-card__eye vp-card__eye--top">ADAPTIVE PROFILE</div>
                <div className="vp-bars">
                  {narrative.bars.map((label, i) => {
                    const val = stepData.bars[i] || 0;
                    const tone = ['lime','violet','rose','lime','violet','amber'][i%6];
                    return (
                      <div key={i} className="vp-bar">
                        <div className="vp-bar__head"><span className="vp-bar__label">{label}</span><span className={`vp-bar__val vp-bar__val--${tone}`}>{val.toFixed(2)}</span></div>
                        <div className="vp-bar__track"><div className={`vp-bar__fill vp-bar__fill--${tone}`} style={{ width:`${val*100}%` }} /></div>
                      </div>
                    );
                  })}
                </div>
              </div>
            </div>
          </div>
        </div>
      )}
    </>
  );
}

function VectorSpaceSVG({ clusters, visitorPos, definition, compact=false, hearted=[], visitorTag='usr_4821', stepId='plp' }) {
  const products = window.PRODUCTS || [];
  const byId = (id) => products.find(p => p.id === id);
  const stepFloor = STEP_IMG_FLOOR[stepId] ?? 0.16;
  const stepBlurMax = STEP_BLUR_CEILING[stepId] ?? 6;
  const lineOpacity = Math.max(0, (definition - 0.25) * 1.4);
  const visitorBlur = Math.max(0, (1 - definition) * 4);

  return (
    <svg viewBox="-8 -8 116 116" className={`vp-svg ${compact?'vp-svg--compact':''}`} preserveAspectRatio="xMidYMid meet">
      <circle cx={visitorPos.x} cy={visitorPos.y} r="36" className="vp-ring" />
      <circle cx={visitorPos.x} cy={visitorPos.y} r="24" className="vp-ring" />
      <circle cx={visitorPos.x} cy={visitorPos.y} r="14" className="vp-ring" />

      {clusters.map(c => (
        <line key={`l-${c.id}`} x1={visitorPos.x} y1={visitorPos.y} x2={c.pos.x} y2={c.pos.y}
          className="vp-line" stroke={c.color}
          strokeOpacity={Math.max(0.10, (c.pull??0)*0.75 + lineOpacity*0.2)}
          strokeDasharray="1.4 1.6" style={{ transition:'stroke-opacity 600ms ease' }} />
      ))}

      {clusters.map(c => {
        const pull = c.pull ?? 0;
        const haloR = compact ? 8.5 : 8 + pull*5 + definition*1.5;
        const naturalOpacity = 0.40 + pull*0.55;
        const imgOpacity = stepId==='welcome' ? 0 : Math.max(stepFloor, naturalOpacity);
        const blurAmt = Math.min(stepBlurMax, (1-pull)*7);
        const labelOpacity = 0.35 + pull*0.65;
        const heartedHere = c.products.filter(pid => hearted.includes(pid));
        const otherHere = c.products.filter(pid => !hearted.includes(pid));
        const pickIds = [...heartedHere, ...otherHere].slice(0, compact?0:4);
        const productThumbs = pickIds.map(byId).filter(Boolean);
        const isTop = c.pos.y < 50;
        const labelY = isTop ? -haloR-4.5 : haloR+4.6;
        const dY = isTop ? -haloR-1.6 : haloR+7.8;
        return (
          <g key={c.id} className="vp-cluster" transform={`translate(${c.pos.x} ${c.pos.y})`} style={{ transition:'transform 700ms cubic-bezier(.22,.61,.36,1)' }}>
            <ellipse rx={haloR*1.18} ry={haloR*0.92} fill={c.color} fillOpacity={0.08+pull*0.16} />
            <ellipse rx={haloR*0.80} ry={haloR*0.66} fill={c.color} fillOpacity={0.18+pull*0.22} />
            {productThumbs.map((p, i) => {
              const cols=2, col=i%cols, row=Math.floor(i/cols);
              const gap=haloR*0.42, tx=(col-0.5)*gap, ty=(row-0.5)*gap;
              const tr=3.4+pull*1.0;
              const isHearted=hearted.includes(p.id);
              const variance=0.5+((i*37)%100)/100*0.5;
              const localBlur=isHearted?0:Math.min(6,blurAmt*variance);
              return (
                <g key={p.id} transform={`translate(${tx} ${ty})`}>
                  <circle r={tr+0.5} fill={c.color} fillOpacity={0.22+(1-pull)*0.30} />
                  <foreignObject x={-tr} y={-tr} width={tr*2} height={tr*2} style={{ overflow:'visible' }}>
                    <img xmlns="http://www.w3.org/1999/xhtml" src={p.img} alt="" loading="eager" decoding="sync"
                      style={{ width:'100%', height:'100%', objectFit:'cover', borderRadius:'50%', display:'block',
                        filter:`blur(${localBlur}px)`, opacity:isHearted?1:imgOpacity,
                        transition:'filter 600ms ease, opacity 600ms ease' }} />
                  </foreignObject>
                  <circle r={tr+0.3} fill="none" stroke={isHearted?'#C47A9A':c.color}
                    strokeWidth={isHearted?'0.7':'0.4'} strokeOpacity={isHearted?1:(0.5+pull*0.5)} />
                  {isHearted && <text x={tr*0.95} y={-tr*0.65} fontSize="3" textAnchor="middle" fill="#C47A9A">♥</text>}
                </g>
              );
            })}
            {!compact && <text y={labelY} textAnchor="middle" className="vp-cluster__label" fill={c.color} fontSize="4.2" fontWeight="700" style={{ opacity:labelOpacity }}>{c.short}</text>}
            {!compact && pull>0.2 && <text y={dY} textAnchor="middle" className="vp-cluster__d" fill={c.color} fontSize="2.6" style={{ opacity:0.45+pull*0.45 }}>d={(Math.sqrt(Math.pow(c.pos.x-visitorPos.x,2)+Math.pow(c.pos.y-visitorPos.y,2))/REST_R).toFixed(2)} · n={c.products.length}</text>}
          </g>
        );
      })}

      <defs>
        <radialGradient id="vp-visitor-glow" cx="50%" cy="50%" r="50%">
          <stop offset="0%"   stopColor="#ADEF9B" stopOpacity="0.55" />
          <stop offset="60%"  stopColor="#3DBE85" stopOpacity="0.22" />
          <stop offset="100%" stopColor="#038362" stopOpacity="0" />
        </radialGradient>
        <radialGradient id="vp-visitor-body" cx="35%" cy="30%" r="75%">
          <stop offset="0%"   stopColor="#9AE9C8" />
          <stop offset="35%"  stopColor="#3DBE85" />
          <stop offset="80%"  stopColor="#0C5A45" />
          <stop offset="100%" stopColor="#062F26" />
        </radialGradient>
        <radialGradient id="vp-visitor-spec" cx="30%" cy="22%" r="22%">
          <stop offset="0%"   stopColor="#FFFFFF" stopOpacity="0.95" />
          <stop offset="100%" stopColor="#FFFFFF" stopOpacity="0" />
        </radialGradient>
      </defs>
      <g transform={`translate(${visitorPos.x} ${visitorPos.y})`} style={{ transition:'transform 700ms cubic-bezier(.22,.61,.36,1)' }}>
        <ellipse cx="0" cy={compact?3.6:5.6} rx={compact?3.6:5.6} ry={compact?0.8:1.2} fill="#06171F" fillOpacity="0.18" style={{ filter:'blur(1px)' }} />
        <circle r={compact?5:10} fill="url(#vp-visitor-glow)" />
        <circle r={compact?3:4.6} fill="url(#vp-visitor-body)" style={{ filter:`blur(${visitorBlur*0.6}px)` }} />
        <circle r={compact?3:4.6} fill="none" stroke="#ADEF9B" strokeWidth="0.35" strokeOpacity="0.45" />
        <ellipse cx={compact?-0.9:-1.4} cy={compact?-1.0:-1.6} rx={compact?1.2:1.8} ry={compact?0.7:1.1} fill="url(#vp-visitor-spec)" />
        {!compact && <>
          <text y={9.6} textAnchor="middle" fontSize="3.6" fontWeight="700" fill="#06171F" style={{ paintOrder:'stroke', stroke:'#FBF6E9', strokeWidth:0.8 }}>visitor</text>
          <text y={13}  textAnchor="middle" fontSize="2.4" fontWeight="500" fill="#6E6657" style={{ paintOrder:'stroke', stroke:'#FBF6E9', strokeWidth:0.5, letterSpacing:'0.06em' }}>{visitorTag}</text>
        </>}
      </g>
    </svg>
  );
}

Object.assign(window, { VisitorPulse });
