// UI Components for Tooth Clicker
const { useState: useStateC } = React;

const primaryBtnStyle = { all: 'unset', boxSizing: 'border-box', padding: '10px 18px', background: 'var(--primary-i100)', color: '#fff', borderRadius: 'var(--radius-s)', fontWeight: 600, fontSize: 14, cursor: 'pointer', flex: 1, textAlign: 'center', fontFamily: 'var(--font-sans)' };
const secondaryBtnStyle = { all: 'unset', boxSizing: 'border-box', padding: '10px 18px', background: 'var(--bg-3)', color: 'var(--fg-1)', borderRadius: 'var(--radius-s)', fontWeight: 500, fontSize: 14, cursor: 'pointer', flex: 1, textAlign: 'center', fontFamily: 'var(--font-sans)' };

function ToothIcon({ size = 220, golden = false }) {
  const fill1 = golden ? '#FFD463' : '#FFFFFF';
  const fill2 = golden ? '#FFC220' : '#EBF4FC';
  const stroke = golden ? '#7F6A33' : '#0076DB';
  const shadow = golden ? '#FFC220' : '#0076DB';
  return (
    <svg width={size} height={size} viewBox="0 0 200 200" style={{ filter: `drop-shadow(0 10px 24px ${shadow}40)` }}>
      <defs>
        <linearGradient id={`tooth-grad-${golden ? 'g' : 'w'}`} x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor={fill1} />
          <stop offset="100%" stopColor={fill2} />
        </linearGradient>
        <radialGradient id={`tooth-shine-${golden ? 'g' : 'w'}`} cx="30%" cy="25%" r="35%">
          <stop offset="0%" stopColor="#FFFFFF" stopOpacity="0.9" />
          <stop offset="100%" stopColor="#FFFFFF" stopOpacity="0" />
        </radialGradient>
      </defs>
      <path
        d="M100 22 C 60 22, 34 42, 34 80 C 34 110, 48 128, 54 150 C 60 170, 72 182, 82 178 C 92 174, 92 158, 96 144 C 100 130, 104 130, 108 144 C 112 158, 114 174, 124 178 C 134 182, 146 170, 150 150 C 154 130, 166 110, 166 80 C 166 42, 140 22, 100 22 Z"
        fill={`url(#tooth-grad-${golden ? 'g' : 'w'})`} stroke={stroke} strokeWidth="3" />
      
      <ellipse cx="75" cy="60" rx="22" ry="30" fill={`url(#tooth-shine-${golden ? 'g' : 'w'})`} />
      <path d="M70 110 Q 100 125 130 110" stroke={stroke} strokeWidth="2.5" fill="none" strokeLinecap="round" opacity="0.35" />
    </svg>);

}

/**
 * Renders a ring of toothbrushes around the main tooth.
 * Each purchase of "brush" adds one toothbrush (max 100).
 */
function ToothbrushRing({ count, radius = 180 }) {
  const max = 100;
  const displayCount = Math.min(count || 0, max);
  
  if (displayCount <= 0) return null;

  const delayStep = 0.1; // 100ms between brushes
  const pulseTime = 1;   // 1s pulse duration
  const cycleTime = Math.max(pulseTime, displayCount * delayStep);
  const peakPercent = (pulseTime / 2 / cycleTime) * 100;
  const endPercent = (pulseTime / cycleTime) * 100;

  return (
    <div style={{
      position: 'absolute',
      width: 0,
      height: 0,
      zIndex: 0,
      animation: 'rotateSun 60s linear infinite',
      pointerEvents: 'none'
    }}>
      <style>{`
        @keyframes brushWaveDynamic {
          0% { opacity: 1; transform: translateY(0); }
          ${peakPercent}% { opacity: 0.3; transform: translateY(-3px); }
          ${endPercent}%, 100% { opacity: 1; transform: translateY(0); }
        }
        @keyframes brushEntry {
          0% { opacity: 0; transform: translateY(-${radius + 10}px) rotate(180deg) scale(0); }
          60% { opacity: 1; transform: translateY(-${radius - 5}px) rotate(180deg) scale(1.1); }
          100% { opacity: 1; transform: translateY(-${radius}px) rotate(180deg) scale(1); }
        }
      `}</style>
      {Array.from({ length: displayCount }).map((_, i) => {
        const angle = (i / displayCount) * 360;
        return (
          <div key={i} style={{
            position: 'absolute',
            width: 0,
            height: 0,
            transform: `rotate(${angle}deg)`,
            transition: 'transform 0.5s ease-out'
          }}>
            <div style={{
              position: 'absolute',
              width: 0,
              height: 0,
              transform: `translateY(-${radius}px) rotate(180deg)`,
              animation: 'brushEntry 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) backwards'
            }}>
              <img
                src="assets/tooth_wash/tooth_wash_1.png"
                alt=""
                style={{
                  position: 'absolute',
                  width: 50,
                  height: 50,
                  objectFit: 'contain',
                  left: -25,
                  top: -25,
                  filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.1))',
                  animation: `brushWaveDynamic ${cycleTime}s infinite ease-in-out`,
                  animationDelay: `${i * delayStep}s`
                }}
              />
            </div>
          </div>
        );
      })}
    </div>
  );
}

function StatTile({ label, value, sub, icon, accent, onHelpEnter, onHelpLeave }) {
  return (
    <div style={{ background: 'var(--bg-1)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-m)', display: 'flex', flexDirection: 'column', gap: 'var(--spacing-1)', padding: "12px 14px" }}>
      <div className="t-mini-caps" style={{ color: 'var(--fg-3)', display: 'flex', alignItems: 'center', gap: 6 }}>
        {icon && <i className={icon} style={{ fontSize: 11, color: accent || 'var(--fg-3)' }}></i>}
        {label}
        {onHelpEnter && <i 
          className="fa-solid fa-circle-question" 
          onMouseEnter={(e) => { e.currentTarget.style.color = 'var(--primary-i100)'; onHelpEnter(e); }} 
          onMouseLeave={(e) => { e.currentTarget.style.color = 'var(--fg-4)'; if (onHelpLeave) onHelpLeave(); }} 
          style={{ fontSize: 11, color: 'var(--fg-4)', cursor: 'help', transition: 'color 150ms' }}></i>}
      </div>
      <div className="t-heading-l" style={{ color: accent || 'var(--fg-1)', fontVariantNumeric: 'tabular-nums' }}>{value}</div>
      {sub && <div className="t-body-s" style={{ color: 'var(--fg-3)' }}>{sub}</div>}
    </div>);

}

function StatsGroup({ title, icon, accent, rows }) {
  return (
    <div style={{ background: 'var(--bg-1)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-m)', overflow: 'hidden' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: 'var(--spacing-3) var(--spacing-5)', borderBottom: '1px solid var(--border-subtle)', background: 'var(--bg-2)' }}>
        <div style={{ width: 26, height: 26, borderRadius: 4, background: accent || 'var(--fg-2)', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 12 }}>
          <i className={icon}></i>
        </div>
        <div className="t-heading-xs" style={{ color: 'var(--fg-1)' }}>{title}</div>
      </div>
      <div>
        {rows.map((r, i) =>
        <div key={i} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '12px var(--spacing-5)', borderTop: i === 0 ? 'none' : '1px solid var(--border-subtle)' }}>
            <div className="t-body-s" style={{ color: 'var(--fg-2)' }}>{r.label}</div>
            <div style={{ fontFamily: 'var(--font-sans)', fontWeight: r.strong ? 600 : 500, fontSize: r.strong ? 18 : 14, color: r.color || 'var(--fg-1)', fontVariantNumeric: 'tabular-nums' }}>{r.value}</div>
          </div>
        )}
      </div>
    </div>);

}

function TabBar({ tabs, active, onChange, id, setTooltip, style = {} }) {
  return (
    <div id={id} style={{ 
      display: 'flex', 
      gap: 6, 
      background: 'var(--bg-2)', 
      padding: '8px 8px 8px 8px', 
      borderBottom: '1px solid var(--border-subtle)',
      overflowX: 'auto',
      WebkitOverflowScrolling: 'touch',
      scrollbarWidth: 'none',
      msOverflowStyle: 'none',
      ...style
    }}>
      {tabs.filter(Boolean).map((t) => {
        const isActive = active === t.id;
        return (
          <button 
            key={t.id} 
            id={`tab-${t.id}`} 
            onClick={() => { window.playClickSound && window.playClickSound(); onChange(t.id); }} 
            onMouseEnter={(e) => {
              if (setTooltip) {
                const rect = e.currentTarget.getBoundingClientRect();
                setTooltip({ type: 'text', text: t.label, pos: { x: rect.left + rect.width / 2, y: rect.bottom }, direction: 'down' });
              }
            }}
            onMouseLeave={() => {
              if (setTooltip) setTooltip(null);
            }}
            style={{
              all: 'unset',
              position: 'relative',
              boxSizing: 'border-box',
              flex: 1,
              background: isActive ? 'var(--bg-1)' : 'transparent', 
              padding: '10px 0', 
              borderRadius: 10,
              cursor: 'pointer',
              display: 'flex', 
              alignItems: 'center', 
              justifyContent: 'center',
              transition: 'all 200ms ease', 
              boxShadow: isActive ? '0 4px 12px rgba(0,0,0,0.05)' : 'none'
            }}
          >
            {t.img ? 
              <img src={t.img} style={{ width: 32, height: 32, objectFit: 'contain', filter: isActive ? 'none' : 'grayscale(1) opacity(0.5)', transform: isActive ? 'scale(1.2)' : 'scale(0.95)', transition: 'all 200ms ease' }} /> 
              : <i className={t.icon} style={{ fontSize: 24, color: isActive ? 'var(--primary-i100)' : 'var(--fg-4)', transform: isActive ? 'scale(1.2)' : 'scale(0.95)', transition: 'all 200ms ease' }}></i>
            }
            {t.badge != null &&
              <span style={{ 
                position: 'absolute',
                top: 4,
                right: 'calc(50% - 24px)',
                fontSize: 9, 
                fontWeight: 700, 
                background: 'var(--primary-i100)', 
                color: '#fff', 
                padding: '2px 6px', 
                borderRadius: 999, 
                minWidth: 16, 
                textAlign: 'center',
                boxShadow: '0 2px 4px rgba(0,0,0,0.2)'
              }}>{t.badge}</span>
            }
            {t.dot && (
              <span style={{ 
                position: 'absolute', 
                top: 6, 
                right: 'calc(50% - 20px)', 
                width: 10, 
                height: 10, 
                borderRadius: '50%', 
                background: '#e11d24', 
                border: '2px solid var(--bg-1)',
                boxShadow: '0 2px 4px rgba(0,0,0,0.2)'
              }}></span>
            )}
          </button>
        );
      })}
    </div>
  );
}


function GeneratorRow({ gen, owned, cost, canAfford, unlocked, revealed, onBuy, lang, totalTeeth, production, nextProduction, buyQty, actualQty }) {
  const t = window.STRINGS[lang];
  const name = gen.name?.[lang] || gen[lang] || gen.es;
  const desc = gen.description?.[lang] || gen[`desc_${lang}`] || gen.desc_es;
  const [isPressed, setIsPressed] = React.useState(false);
  const [isHovered, setIsHovered] = React.useState(false);
  const [tooltipPos, setTooltipPos] = React.useState({ top: 0, left: 0 });
  const rowRef = React.useRef(null);

  if (!revealed) return null;

  const sharedGrid = { display: 'grid', gridTemplateColumns: '32px 1fr auto', gap: '8px', alignItems: 'center', padding: '5px 8px', boxSizing: 'border-box', borderRadius: 'var(--radius-s)' };

  const handleEnter = () => {
    setIsHovered(true);
    if (rowRef.current) {
      const rect = rowRef.current.getBoundingClientRect();
      const container = rowRef.current.closest('section') || rowRef.current.closest('.mobile-tab-view-body') || rowRef.current.closest('#generators-container-desktop');
      const containerRect = container ? container.getBoundingClientRect() : rect;
      let leftPos = containerRect.left - 250 - 5;
      if (leftPos < 5) {
        leftPos = containerRect.right + 5;
      }
      setTooltipPos({ top: rect.top, left: leftPos });
    }
  };

  const handleLeave = () => {
    setIsPressed(false);
    setIsHovered(false);
  };

  if (!unlocked) {
    return (
      <div style={{ ...sharedGrid, background: 'var(--bg-2)', border: '1px solid var(--border-subtle)', opacity: 0.5, cursor: 'not-allowed' }}>
        <div style={{ width: 32, height: 32, borderRadius: 6, background: 'var(--bg-1)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--fg-4)', flexShrink: 0 }}>
          <i className="fa-solid fa-lock" style={{ fontSize: 12 }}></i>
        </div>
        <div style={{ minWidth: 0, display: 'flex', flexDirection: 'column', gap: 2 }}>
          <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--fg-3)', lineHeight: 1.2 }}>???</div>
          <div style={{ fontSize: 10, color: 'var(--fg-4)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{t.unlockAt} <window.Odometer value={gen.unlockAt || 0} /> {t.teeth}</div>
        </div>
        <div style={{ textAlign: 'right', minWidth: 50, flexShrink: 0 }}>
          <div style={{ fontSize: 10, color: 'var(--fg-4)' }}>{t.cost}</div>
          <div style={{ fontSize: 11, color: 'var(--fg-4)', fontVariantNumeric: 'tabular-nums' }}>−<window.Odometer value={Math.max(0, (gen.unlockAt || 0) - totalTeeth)} /></div>
        </div>
      </div>);
  }
  
  return (
    <>
      <div 
        ref={rowRef}
        onMouseEnter={handleEnter}
        onMouseLeave={handleLeave} 
        onClick={canAfford ? onBuy : undefined}
        onMouseDown={() => canAfford && setIsPressed(true)}
        onMouseUp={() => setIsPressed(false)}
        style={{
          ...sharedGrid,
          background: canAfford ? 'var(--bg-1)' : 'var(--bg-2)',
          border: canAfford ? '1px solid var(--primary-i100)' : '1px solid var(--border-subtle)',
          boxShadow: canAfford ? '0 2px 4px rgba(0,0,0,0.05)' : 'none',
          cursor: canAfford ? 'pointer' : 'not-allowed',
          opacity: canAfford ? 1 : 0.65,
          transform: isPressed ? 'translateY(1px)' : 'none',
          transition: 'transform 100ms ease, border-color 150ms ease, background 150ms ease, box-shadow 150ms ease'
        }}
      >
        <div style={{ width: 32, height: 32, borderRadius: 6, background: canAfford ? 'var(--primary-i010)' : 'var(--bg-3)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: canAfford ? 'var(--primary-i100)' : 'var(--fg-3)', flexShrink: 0, transition: 'background 150ms ease, color 150ms ease', overflow: 'hidden' }}>
          {gen.iconUrl ? <img src={gen.iconUrl} style={{ width: '100%', height: '100%', objectFit: 'contain' }} /> : <i className={gen.icon} style={{ fontSize: 14 }}></i>}
        </div>
        <div style={{ minWidth: 0, display: 'flex', flexDirection: 'column', gap: 2 }}>
          <div style={{ fontSize: 11, color: 'var(--fg-3)' }}>Cant. <span style={{fontWeight: 700, color: 'var(--fg-1)'}}>{owned}</span></div>
          <div style={{ fontSize: 10, color: 'var(--positive-i100)', fontVariantNumeric: 'tabular-nums' }}>+<window.Odometer value={owned > 0 ? production : nextProduction} formatFn={(v) => window.formatNum(v, null, null, true)} />/s</div>
        </div>
        <div style={{ textAlign: 'right', minWidth: 50, flexShrink: 0 }}>
          {buyQty > 1 &&
            <div style={{ fontSize: 9, color: canAfford ? 'var(--primary-i100)' : 'var(--fg-4)', textTransform: 'uppercase', marginBottom: 2 }}>
              x{actualQty < buyQty ? `${actualQty}/${buyQty}` : buyQty}
            </div>
          }
          <div style={{ fontSize: 13, fontWeight: canAfford ? 700 : 400, color: canAfford ? 'var(--fg-1)' : 'var(--fg-3)', fontVariantNumeric: 'tabular-nums' }}>
            <window.Odometer value={cost} />
          </div>
        </div>
      </div>
      
      {isHovered && window.ReactDOM && window.ReactDOM.createPortal(
        <div style={{
          position: 'absolute',
          top: tooltipPos.top,
          left: tooltipPos.left,
          width: 250,
          minHeight: 100,
          background: 'var(--bg-1)',
          border: '1px solid var(--border-subtle)',
          borderRadius: 'var(--radius-m)',
          boxShadow: '0 8px 24px rgba(0,0,0,0.12)',
          padding: '12px',
          boxSizing: 'border-box',
          display: 'flex',
          gap: '12px',
          zIndex: 99999,
          animation: 'fadeIn 0.2s ease-out',
          pointerEvents: 'none'
        }}>
          <div style={{ width: 64, height: 64, borderRadius: 8, background: 'var(--bg-2)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, overflow: 'hidden' }}>
            {gen.iconUrl ? <img src={gen.iconUrl} style={{ width: '100%', height: '100%', objectFit: 'contain' }} /> : <i className={gen.icon} style={{ fontSize: 24, color: 'var(--fg-2)' }}></i>}
          </div>
          <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: '6px' }}>
            <div>
              <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--fg-1)', lineHeight: 1.2 }}>{name}</div>
              <div style={{ fontSize: 11, color: 'var(--fg-3)', lineHeight: 1.3, marginTop: 4 }}>{desc}</div>
            </div>
            <div style={{ background: 'var(--bg-2)', borderRadius: 6, padding: '6px 8px', display: 'flex', flexDirection: 'column', gap: 2 }}>
              <div style={{ fontSize: 11, color: 'var(--fg-2)', display: 'flex', justifyContent: 'space-between' }}>
                <span>Cantidad:</span> <span style={{fontWeight: 600}}>{owned}</span>
              </div>
              <div style={{ fontSize: 11, color: 'var(--positive-i100)', display: 'flex', justifyContent: 'space-between' }}>
                <span>Producción:</span> <span>+<window.Odometer value={owned > 0 ? production : nextProduction} formatFn={(v) => window.formatNum(v, null, null, true)} />/s</span>
              </div>
            </div>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 2 }}>
              <span style={{ fontSize: 10, textTransform: 'uppercase', color: 'var(--fg-4)', fontWeight: 600 }}>Coste</span>
              <div style={{ fontSize: 13, fontWeight: 700, color: canAfford ? 'var(--positive-i100)' : 'var(--negative-i100)', display: 'flex', alignItems: 'center', gap: 4 }}>
                <i className="fa-solid fa-tooth" style={{ fontSize: 11 }}></i>
                <window.Odometer value={cost} />
              </div>
            </div>
          </div>
        </div>,
        document.body
      )}
    </>
  );
}

function ClickUpgradeRow({ up, purchased, canAfford, unlocked, onBuy, lang, totalTeeth }) {
  const t = window.STRINGS[lang];
  const name = up.name?.[lang] || up[lang] || up.es;
  const desc = up.description?.[lang] || up[`desc_${lang}`] || up.desc_es;
  const [isPressed, setIsPressed] = React.useState(false);
  const [isHovered, setIsHovered] = React.useState(false);
  const [tooltipPos, setTooltipPos] = React.useState({ top: 0, left: 0 });
  const rowRef = React.useRef(null);

  const prevPurchased = React.useRef(purchased);
  const [showConfetti, setShowConfetti] = React.useState(false);

  React.useEffect(() => {
    if (!prevPurchased.current && purchased) {
      setShowConfetti(true);
      const id = setTimeout(() => setShowConfetti(false), 200);
      return () => clearTimeout(id);
    }
    prevPurchased.current = purchased;
  }, [purchased]);

  const sharedGrid = { display: 'grid', gridTemplateColumns: '32px 1fr', gap: '8px', alignItems: 'center', padding: '5px 8px', boxSizing: 'border-box', borderRadius: 'var(--radius-s)' };

  const handleEnter = () => {
    setIsHovered(true);
    if (rowRef.current) {
      const rect = rowRef.current.getBoundingClientRect();
      const container = rowRef.current.closest('section') || rowRef.current.closest('.mobile-tab-view-body') || rowRef.current.closest('#click-upgrades-container-desktop');
      const containerRect = container ? container.getBoundingClientRect() : rect;
      let leftPos = containerRect.left - 250 - 5;
      if (leftPos < 5) {
        leftPos = containerRect.right + 5;
      }
      setTooltipPos({ top: rect.top, left: leftPos });
    }
  };

  const handleLeave = () => {
    setIsPressed(false);
    setIsHovered(false);
  };

  const ConfettiParticle = () => {
    const [active, setActive] = React.useState(false);
    
    const { startX, startY, tx, ty } = React.useMemo(() => {
      const edge = Math.random();
      let x, y, angle;
      if (edge < 0.25) {
        x = Math.random() * 100; y = 0;
        angle = -Math.PI / 2 + (Math.random() - 0.5);
      } else if (edge < 0.5) {
        x = 100; y = Math.random() * 100;
        angle = (Math.random() - 0.5);
      } else if (edge < 0.75) {
        x = Math.random() * 100; y = 100;
        angle = Math.PI / 2 + (Math.random() - 0.5);
      } else {
        x = 0; y = Math.random() * 100;
        angle = Math.PI + (Math.random() - 0.5);
      }
      const dist = 30 + Math.random() * 30;
      return { startX: x, startY: y, tx: Math.cos(angle) * dist, ty: Math.sin(angle) * dist };
    }, []);

    React.useEffect(() => {
      let raf = requestAnimationFrame(() => requestAnimationFrame(() => setActive(true)));
      return () => cancelAnimationFrame(raf);
    }, []);
    
    const color = ['#ff3366', '#33ccff', '#ffcc00', '#33ff66', '#a64dff'][Math.floor(Math.random() * 5)];
    return (
      <div style={{
        position: 'absolute', top: `${startY}%`, left: `${startX}%`, width: 6, height: 6, background: color, borderRadius: '50%',
        transform: active ? `translate(calc(-50% + ${tx}px), calc(-50% + ${ty}px)) scale(0)` : 'translate(-50%, -50%) scale(1.5)',
        opacity: active ? 0 : 1,
        transition: 'transform 0.2s cubic-bezier(0.25, 1, 0.5, 1), opacity 0.2s ease-out',
        pointerEvents: 'none'
      }} />
    );
  };

  if (!unlocked) {
    return (
      <div style={{ ...sharedGrid, background: 'var(--bg-2)', border: '1px solid var(--border-subtle)', opacity: 0.5, cursor: 'not-allowed' }}>
        <div style={{ width: 32, height: 32, borderRadius: 6, background: 'var(--bg-1)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--fg-4)', flexShrink: 0 }}>
          <i className="fa-solid fa-lock" style={{ fontSize: 12 }}></i>
        </div>
        <div style={{ minWidth: 0, display: 'flex', flexDirection: 'column', gap: 2 }}>
          <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--fg-3)', lineHeight: 1.2 }}>???</div>
          <div style={{ fontSize: 11, color: 'var(--fg-4)', fontVariantNumeric: 'tabular-nums' }}>
            −<window.Odometer value={Math.max(0, (up.unlockAt !== undefined ? up.unlockAt : Math.floor(up.cost * 0.5)) - totalTeeth)} />
          </div>
        </div>
      </div>);
  }

  const tooltipPortal = isHovered && window.ReactDOM && window.ReactDOM.createPortal(
    <div style={{
      position: 'absolute',
      top: tooltipPos.top,
      left: tooltipPos.left,
      width: 250,
      minHeight: 100,
      background: 'var(--bg-1)',
      border: '1px solid var(--border-subtle)',
      borderRadius: 'var(--radius-m)',
      boxShadow: '0 8px 24px rgba(0,0,0,0.12)',
      padding: '12px',
      boxSizing: 'border-box',
      display: 'flex',
      gap: '12px',
      zIndex: 99999,
      animation: 'fadeIn 0.2s ease-out',
      pointerEvents: 'none'
    }}>
      <div style={{ width: 64, height: 64, borderRadius: 8, background: 'var(--bg-2)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, overflow: 'hidden' }}>
        {up.iconUrl ? <img src={up.iconUrl} style={{ width: '100%', height: '100%', objectFit: 'contain' }} /> : <i className={up.icon || "fa-solid fa-arrow-up-right-dots"} style={{ fontSize: 24, color: 'var(--fg-2)' }}></i>}
      </div>
      <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: '6px' }}>
        <div>
          <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--fg-1)', lineHeight: 1.2 }}>{name}</div>
          <div style={{ fontSize: 11, color: 'var(--fg-3)', lineHeight: 1.3, marginTop: 4 }}>{desc}</div>
        </div>
        <div style={{ background: 'var(--bg-2)', borderRadius: 6, padding: '6px 8px', display: 'flex', flexDirection: 'column', gap: 2 }}>
          <div style={{ fontSize: 11, color: 'var(--positive-i100)', display: 'flex', justifyContent: 'space-between' }}>
            <span>Prod. x click:</span> <span>{up.type === 'mult' ? `x${up.value || up.multiplier || 1}` : `+${up.value || up.multiplier || 0}`}</span>
          </div>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 2 }}>
          <span style={{ fontSize: 10, textTransform: 'uppercase', color: 'var(--fg-4)', fontWeight: 600 }}>Coste</span>
          <div style={{ fontSize: 13, fontWeight: 700, color: purchased ? 'var(--fg-2)' : (canAfford ? 'var(--positive-i100)' : 'var(--negative-i100)'), display: 'flex', alignItems: 'center', gap: 4 }}>
            <i className="fa-solid fa-tooth" style={{ fontSize: 11 }}></i>
            <window.Odometer value={up.cost} formatFn={(v) => window.formatNum(Math.floor(v))} />
          </div>
        </div>
      </div>
    </div>,
    document.body
  );

  if (purchased) {
    return (
      <>
        <div 
          ref={rowRef}
          onMouseEnter={handleEnter} 
          onMouseLeave={handleLeave} 
          style={{ ...sharedGrid, gridTemplateColumns: '32px 1fr', background: 'var(--positive-i010)', border: '1px solid var(--positive-i100)', position: 'relative', transform: isHovered ? 'translateY(-1px)' : 'none', transition: 'transform 150ms ease' }}
        >
          {showConfetti && Array.from({ length: 16 }).map((_, i) => <ConfettiParticle key={i} />)}
          <div style={{ width: 32, height: 32, borderRadius: 6, background: 'transparent', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', flexShrink: 0, overflow: 'hidden' }}>
            {up.iconUrl ? <img src={up.iconUrl} style={{ width: '100%', height: '100%', objectFit: 'contain' }} /> : <i className={up.icon || "fa-solid fa-arrow-up-right-dots"} style={{ fontSize: 13 }}></i>}
          </div>
          <div style={{ minWidth: 0, display: 'flex', flexDirection: 'column', gap: 2 }}>
            <div style={{ display: 'flex', gap: 4, alignItems: 'baseline' }}>
              <span style={{ fontSize: 10, color: 'var(--positive-i150)', fontWeight: 600, fontVariantNumeric: 'tabular-nums' }}>
                {up.type === 'mult' ? `x${up.value || up.multiplier || 1}` : `+${up.value || up.multiplier || 0}`}
              </span>
              <span style={{ fontSize: 11, color: 'var(--positive-i130)', whiteSpace: 'nowrap' }}>x click</span>
            </div>
          </div>
          <div style={{ position: 'absolute', top: -8, right: -8, width: 16, height: 16, borderRadius: '50%', background: 'var(--positive-i100)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', zIndex: 2 }}>
            <i className="fa-solid fa-check" style={{ fontSize: 8 }}></i>
          </div>
        </div>
        {tooltipPortal}
      </>
    );
  }

  return (
    <>
      <div 
        ref={rowRef}
        onMouseEnter={handleEnter} 
        onMouseLeave={handleLeave} 
        onClick={canAfford ? onBuy : undefined}
        onMouseDown={() => canAfford && setIsPressed(true)}
        onMouseUp={() => setIsPressed(false)}
        style={{
          ...sharedGrid,
          position: 'relative',
          background: canAfford ? 'var(--bg-1)' : 'var(--bg-2)',
          border: canAfford ? '1px solid var(--primary-i100)' : '1px solid var(--border-subtle)',
          boxShadow: canAfford ? '0 2px 4px rgba(0,0,0,0.05)' : 'none',
          cursor: canAfford ? 'pointer' : 'not-allowed',
          opacity: canAfford ? 1 : 0.65,
          transform: isPressed ? 'translateY(1px)' : (isHovered ? 'translateY(-1px)' : 'none'),
          transition: 'transform 100ms ease, border-color 150ms ease, background 150ms ease, box-shadow 150ms ease'
        }}
      >
        <div style={{ width: 32, height: 32, borderRadius: 6, background: canAfford ? 'var(--alternative-i010)' : 'var(--bg-3)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: canAfford ? 'var(--alternative-i100)' : 'var(--fg-3)', flexShrink: 0, overflow: 'hidden', transition: 'background 150ms ease, color 150ms ease' }}>
          {up.iconUrl ? <img src={up.iconUrl} style={{ width: '100%', height: '100%', objectFit: 'contain' }} /> : <i className={up.icon || "fa-solid fa-arrow-up-right-dots"} style={{ fontSize: 13 }}></i>}
        </div>
        <div style={{ minWidth: 0, display: 'flex', flexDirection: 'column', gap: 2 }}>
          <div style={{ display: 'flex', gap: 4, alignItems: 'baseline' }}>
            <span style={{ fontSize: 10, color: 'var(--positive-i100)', fontWeight: 600, fontVariantNumeric: 'tabular-nums' }}>
              {up.type === 'mult' ? `x${up.value || up.multiplier || 1}` : `+${up.value || up.multiplier || 0}`}
            </span>
            <span style={{ fontSize: 11, color: 'var(--fg-3)', whiteSpace: 'nowrap' }}>x click</span>
          </div>
          <div style={{ fontSize: 13, fontWeight: canAfford ? 700 : 400, color: canAfford ? 'var(--fg-1)' : 'var(--fg-3)', fontVariantNumeric: 'tabular-nums' }}>
            <window.Odometer value={up.cost} formatFn={(v) => window.formatNum(Math.floor(v))} />
          </div>
        </div>
      </div>
      {tooltipPortal}
    </>
  );
}

function AcademyUpgradeRow({ up, purchased, canAfford, unlocked, onBuy, lang, totalXP, isCoins, onHover, onLeave, state }) {
  const name = up.name?.[lang] || up[lang] || up.name?.es || up.es || "Mejora";
  const desc = up.desc?.[lang] || up.description?.[lang] || up.desc?.es || up.description?.es || "";
  const isSpecial = up.isLevelSpecial;
  const cost = isCoins ? up.costCoins : up.baseCost || up.costXP;
  
  const [isPressed, setIsPressed] = React.useState(false);
  const [isHovered, setIsHovered] = React.useState(false);
  const [tooltipPos, setTooltipPos] = React.useState({ top: 0, left: 0 });
  const rowRef = React.useRef(null);
  
  const prevPurchased = React.useRef(purchased);
  const [showConfetti, setShowConfetti] = React.useState(false);

  React.useEffect(() => {
    if (!prevPurchased.current && purchased) {
      setShowConfetti(true);
      const id = setTimeout(() => setShowConfetti(false), 200);
      return () => clearTimeout(id);
    }
    prevPurchased.current = purchased;
  }, [purchased]);

  const sharedGrid = { display: 'grid', gridTemplateColumns: '32px 1fr', gap: '8px', alignItems: 'center', padding: '5px 8px', boxSizing: 'border-box', borderRadius: 'var(--radius-s)' };

  const handleEnter = () => {
    setIsHovered(true);
    if (rowRef.current) {
      const rect = rowRef.current.getBoundingClientRect();
      const container = rowRef.current.closest('section') || rowRef.current.closest('.mobile-tab-view-body') || rowRef.current.closest('#click-upgrades-container-desktop') || rowRef.current.closest('div');
      const containerRect = container ? container.getBoundingClientRect() : rect;
      let leftPos = containerRect.left - 250 - 5;
      if (leftPos < 5) {
        leftPos = containerRect.right + 5;
      }
      setTooltipPos({ top: rect.top, left: leftPos });
    }
  };

  const handleLeave = () => {
    setIsPressed(false);
    setIsHovered(false);
  };

  const ConfettiParticle = () => {
    const [active, setActive] = React.useState(false);
    
    const { startX, startY, tx, ty } = React.useMemo(() => {
      const edge = Math.random();
      let x, y, angle;
      if (edge < 0.25) {
        x = Math.random() * 100; y = 0;
        angle = -Math.PI / 2 + (Math.random() - 0.5);
      } else if (edge < 0.5) {
        x = 100; y = Math.random() * 100;
        angle = (Math.random() - 0.5);
      } else if (edge < 0.75) {
        x = Math.random() * 100; y = 100;
        angle = Math.PI / 2 + (Math.random() - 0.5);
      } else {
        x = 0; y = Math.random() * 100;
        angle = Math.PI + (Math.random() - 0.5);
      }
      const dist = 30 + Math.random() * 30;
      return { startX: x, startY: y, tx: Math.cos(angle) * dist, ty: Math.sin(angle) * dist };
    }, []);

    React.useEffect(() => {
      let raf = requestAnimationFrame(() => requestAnimationFrame(() => setActive(true)));
      return () => cancelAnimationFrame(raf);
    }, []);
    
    const color = ['#ff3366', '#33ccff', '#ffcc00', '#33ff66', '#a64dff'][Math.floor(Math.random() * 5)];
    return (
      <div style={{
        position: 'absolute', top: `${startY}%`, left: `${startX}%`, width: 6, height: 6, background: color, borderRadius: '50%',
        transform: active ? `translate(calc(-50% + ${tx}px), calc(-50% + ${ty}px)) scale(0)` : 'translate(-50%, -50%) scale(1.5)',
        opacity: active ? 0 : 1,
        transition: 'transform 0.2s cubic-bezier(0.25, 1, 0.5, 1), opacity 0.2s ease-out',
        pointerEvents: 'none'
      }} />
    );
  };

  if (!unlocked) {
    return (
      <div ref={rowRef} onMouseEnter={handleEnter} onMouseLeave={handleLeave} style={{ ...sharedGrid, background: 'var(--bg-2)', border: '1px solid var(--border-subtle)', opacity: 0.5, cursor: 'not-allowed' }}>
        <div style={{ width: 32, height: 32, borderRadius: 6, background: 'var(--bg-1)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--fg-4)', flexShrink: 0 }}>
          <i className="fa-solid fa-lock" style={{ fontSize: 12 }}></i>
        </div>
        <div style={{ minWidth: 0, display: 'flex', flexDirection: 'column', gap: 2 }}>
          <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--fg-3)', lineHeight: 1.2 }}>???</div>
          <div style={{ fontSize: 11, color: 'var(--fg-4)', fontVariantNumeric: 'tabular-nums' }}>
             {lang === 'es' ? 'Bloqueado' : 'Locked'}
          </div>
        </div>
        
        {isHovered && window.ReactDOM && window.ReactDOM.createPortal(
          <div style={{
            position: 'absolute',
            top: tooltipPos.top,
            left: tooltipPos.left,
            width: 250,
            background: 'var(--bg-1)',
            border: '1px solid var(--border-subtle)',
            borderRadius: 'var(--radius-m)',
            boxShadow: '0 8px 24px rgba(0,0,0,0.12)',
            padding: '12px',
            boxSizing: 'border-box',
            display: 'flex',
            flexDirection: 'column',
            gap: '8px',
            zIndex: 99999,
            animation: 'fadeIn 0.2s ease-out',
            pointerEvents: 'none'
          }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--fg-1)' }}>{lang === 'es' ? 'Bloqueado' : 'Locked'}</div>
            <div style={{ fontSize: 11, color: 'var(--fg-3)' }}>{lang === 'es' ? 'Requisitos:' : 'Requirements:'}</div>
            <ul style={{ margin: 0, paddingLeft: 0, listStyle: 'none', fontSize: 11, color: 'var(--fg-3)', display: 'flex', flexDirection: 'column', gap: 4 }}>
              {(() => {
                const reqs = [];
                const lvlReq = (up.levelReq !== undefined) ? up.levelReq : (up.reqLevel || 0);
                if (lvlReq > 0) {
                  const met = state && state.level >= lvlReq;
                  reqs.push({ text: lang === 'es' ? `Nivel ${lvlReq}` : `Level ${lvlReq}`, met });
                }
                if (up.reqGeneratorId && up.reqGenQty) {
                  const gen = (window.GENERATORS || []).find(g => g.id === up.reqGeneratorId);
                  const gName = gen ? (gen.name?.[lang] || gen[lang] || gen.name?.es || gen.es || '(?)') : '(?)';
                  const met = state && (state.generators?.[up.reqGeneratorId] || 0) >= up.reqGenQty;
                  reqs.push({ text: `${up.reqGenQty}x ${gName}`, met });
                }
                const achId = up.reqAchievementId || up.achievementId;
                if (achId && achId !== "none" && String(achId).trim() !== "") {
                  const ach = (window.ACHIEVEMENTS || []).find(a => String(a.id) === String(achId));
                  if (ach) {
                    const achName = ach.name?.[lang] || ach[lang] || ach.name?.es || ach.es || '(?)';
                    const met = state && !!state.achievements[achId];
                    reqs.push({ text: lang === 'es' ? `Logro: ${achName}` : `Achievement: ${achName}`, met });
                  }
                }
                return reqs.map((r, idx) => (
                  <li key={idx} style={{ display: 'flex', alignItems: 'center', gap: 6, color: r.met ? 'var(--positive-i100)' : 'var(--fg-3)' }}>
                    <i className={r.met ? "fa-solid fa-circle-check" : "fa-solid fa-circle-xmark"} style={{ fontSize: 12 }}></i>
                    <span style={{ fontWeight: r.met ? 600 : 400 }}>{r.text}</span>
                  </li>
                ));
              })()}
            </ul>
            {cost > 0 && (
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 2 }}>
                <span style={{ fontSize: 10, textTransform: 'uppercase', color: 'var(--fg-4)', fontWeight: 600 }}>Coste</span>
                <div style={{ fontSize: 13, fontWeight: 700, color: canAfford ? 'var(--positive-i100)' : 'var(--negative-i100)', display: 'flex', alignItems: 'center', gap: 4 }}>
                  {isCoins ? <span style={{ fontSize: 11 }}>{lang === 'es' ? 'Monedas' : 'Coins'}</span> : <i className="fa-solid fa-tooth" style={{ fontSize: 11 }}></i>}
                  <window.Odometer value={cost} formatFn={(v) => window.formatNum(Math.floor(v))} />
                </div>
              </div>
            )}
          </div>,
          document.body
        )}
      </div>
    );
  }

  const tooltipPortal = isHovered && window.ReactDOM && window.ReactDOM.createPortal(
    <div style={{
      position: 'absolute',
      top: tooltipPos.top,
      left: tooltipPos.left,
      width: 250,
      minHeight: 100,
      background: 'var(--bg-1)',
      border: '1px solid var(--border-subtle)',
      borderRadius: 'var(--radius-m)',
      boxShadow: '0 8px 24px rgba(0,0,0,0.12)',
      padding: '12px',
      boxSizing: 'border-box',
      display: 'flex',
      gap: '12px',
      zIndex: 99999,
      animation: 'fadeIn 0.2s ease-out',
      pointerEvents: 'none'
    }}>
      <div style={{ width: 64, height: 64, borderRadius: 8, background: 'var(--bg-2)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, overflow: 'hidden' }}>
        {up.iconUrl ? <img src={up.iconUrl} style={{ width: '100%', height: '100%', objectFit: 'contain' }} /> : <i className={up.icon || "fa-solid fa-graduation-cap"} style={{ fontSize: 24, color: 'var(--fg-2)' }}></i>}
      </div>
      <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: '6px' }}>
        <div>
          <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--fg-1)', lineHeight: 1.2 }}>{name}</div>
        </div>
        <div style={{ background: 'var(--bg-2)', borderRadius: 6, padding: '6px 8px', display: 'flex', flexDirection: 'column', gap: 2 }}>
          {up.passiveMult > 0 && <div style={{ fontSize: 11, color: 'var(--positive-i100)', display: 'flex', justifyContent: 'space-between' }}><span>Pasiva:</span> <span>+{up.passiveMult}%</span></div>}
          {up.clickMult > 0 && <div style={{ fontSize: 11, color: 'var(--positive-i100)', display: 'flex', justifyContent: 'space-between' }}><span>Click:</span> <span>+{up.clickMult}%</span></div>}
          {up.globalMult > 0 && <div style={{ fontSize: 11, color: 'var(--positive-i100)', display: 'flex', justifyContent: 'space-between' }}><span>Global:</span> <span>+{up.globalMult}%</span></div>}
          {up.xpMult > 0 && <div style={{ fontSize: 11, color: 'var(--positive-i100)', display: 'flex', justifyContent: 'space-between' }}><span>XP:</span> <span>+{up.xpMult}%</span></div>}
          {up.genProdMult > 0 && up.reqGeneratorId && (() => {
             const gen = (window.GENERATORS || []).find(g => g.id === up.reqGeneratorId);
             const gName = gen ? (gen.name?.[lang] || gen[lang] || gen.name?.es || gen.es || '(?)') : '(?)';
             return <div style={{ fontSize: 11, color: 'var(--positive-i100)', display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap' }}><span>{gName}:</span> <span>+{up.genProdMult}%</span></div>;
          })()}
        </div>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 2 }}>
          <span style={{ fontSize: 10, textTransform: 'uppercase', color: 'var(--fg-4)', fontWeight: 600 }}>Coste</span>
          <div style={{ fontSize: 13, fontWeight: 700, color: purchased ? 'var(--fg-2)' : (canAfford ? 'var(--positive-i100)' : 'var(--negative-i100)'), display: 'flex', alignItems: 'center', gap: 4 }}>
            {isCoins ? <span style={{ fontSize: 11 }}>{lang === 'es' ? 'Monedas' : 'Coins'}</span> : <i className="fa-solid fa-tooth" style={{ fontSize: 11 }}></i>}
            <window.Odometer value={cost} formatFn={(v) => window.formatNum(Math.floor(v))} />
          </div>
        </div>
      </div>
    </div>,
    document.body
  );

  if (purchased) {
    return (
      <>
        <div 
          ref={rowRef}
          onMouseEnter={handleEnter} 
          onMouseLeave={handleLeave} 
          style={{ ...sharedGrid, background: 'var(--positive-i010)', border: '1px solid var(--positive-i100)', position: 'relative', transform: isHovered ? 'translateY(-1px)' : 'none', transition: 'transform 150ms ease' }}
        >
          {showConfetti && Array.from({ length: 16 }).map((_, i) => <ConfettiParticle key={i} />)}
          <div style={{ width: 32, height: 32, borderRadius: 6, background: 'transparent', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', flexShrink: 0, overflow: 'hidden' }}>
            {up.iconUrl ? <img src={up.iconUrl} style={{ width: '100%', height: '100%', objectFit: 'contain' }} /> : <i className={up.icon || "fa-solid fa-graduation-cap"} style={{ fontSize: 13 }}></i>}
          </div>
          <div style={{ minWidth: 0, display: 'flex', flexDirection: 'column', gap: 2 }}>
            <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--positive-i150)', lineHeight: 1.2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{name}</div>
          </div>
          <div style={{ position: 'absolute', top: -8, right: -8, width: 16, height: 16, borderRadius: '50%', background: 'var(--positive-i100)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', zIndex: 2 }}>
            <i className="fa-solid fa-check" style={{ fontSize: 8 }}></i>
          </div>
        </div>
        {tooltipPortal}
      </>
    );
  }

  return (
    <>
      <div 
        ref={rowRef}
        onMouseEnter={handleEnter} 
        onMouseLeave={handleLeave} 
        onClick={canAfford ? onBuy : undefined}
        onMouseDown={() => canAfford && setIsPressed(true)}
        onMouseUp={() => setIsPressed(false)}
        style={{
          ...sharedGrid,
          position: 'relative',
          background: isSpecial ? 'var(--warning-i005)' : (canAfford ? 'var(--bg-1)' : 'var(--bg-2)'),
          border: canAfford ? (isSpecial ? '1px solid var(--warning-i030)' : '1px solid var(--primary-i100)') : '1px solid var(--border-subtle)',
          boxShadow: canAfford ? '0 2px 4px rgba(0,0,0,0.05)' : 'none',
          cursor: canAfford ? 'pointer' : 'not-allowed',
          opacity: canAfford ? 1 : 0.65,
          transform: isPressed ? 'translateY(1px)' : (isHovered ? 'translateY(-1px)' : 'none'),
          transition: 'transform 100ms ease, border-color 150ms ease, background 150ms ease, box-shadow 150ms ease'
        }}
      >
        <div style={{ width: 32, height: 32, borderRadius: 6, background: canAfford ? (isSpecial ? 'var(--warning-i010)' : 'var(--alternative-i010)') : 'var(--bg-3)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: canAfford ? (isSpecial ? 'var(--warning-i100)' : 'var(--alternative-i100)') : 'var(--fg-3)', flexShrink: 0, overflow: 'hidden', transition: 'background 150ms ease, color 150ms ease' }}>
          {up.iconUrl ? <img src={up.iconUrl} style={{ width: '100%', height: '100%', objectFit: 'contain' }} /> : <i className={up.icon || "fa-solid fa-graduation-cap"} style={{ fontSize: 13 }}></i>}
        </div>
        <div style={{ minWidth: 0, display: 'flex', flexDirection: 'column', gap: 2 }}>
          <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--fg-1)', lineHeight: 1.2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{name}</div>
          <div style={{ fontSize: 12, fontWeight: canAfford ? 700 : 400, color: canAfford ? 'var(--fg-1)' : 'var(--fg-3)', fontVariantNumeric: 'tabular-nums', display: 'flex', alignItems: 'center', gap: 4 }}>
            {isCoins ? <span style={{ fontSize: 10, fontWeight: 600 }}>{lang === 'es' ? 'Monedas' : 'Coins'}</span> : <i className="fa-solid fa-tooth" style={{ fontSize: 11 }}></i>}
            <window.Odometer value={cost} formatFn={(v) => window.formatNum(Math.floor(v))} />
          </div>
        </div>
      </div>
      {tooltipPortal}
    </>
  );
}

function achIcon(ach) {
  const cat = ach.cat;
  if (cat === 'secret' || !cat) {
    const id = ach.id || '';
    if (id.includes('golden') || id.includes('gold')) return 'fa-star';
    if (id.includes('click')) return 'fa-hand-pointer';
    if (id.includes('prest')) return 'fa-crown';
    if (id.includes('time') || id.includes('marathon') || id.includes('idle') || id.includes('patient')) return 'fa-clock';
    if (id.includes('omega') || id.includes('divine') || id.includes('pantheon')) return 'fa-infinity';
    if (id.includes('dragon')) return 'fa-dragon';
    if (id.includes('fairy')) return 'fa-wand-sparkles';
    if (id.includes('hydra')) return 'fa-water';
    if (id.includes('balance')) return 'fa-scale-balanced';
    if (id.includes('777') || id.includes('lucky')) return 'fa-dice';
    if (id.includes('speed')) return 'fa-bolt';
    if (id.includes('mono')) return 'fa-broom';
    if (id.includes('diverse')) return 'fa-layer-group';
    if (id.includes('minimal')) return 'fa-compress';
    if (id.includes('all_click')) return 'fa-arrow-up-right-dots';
    return 'fa-user-ninja';
  }
  if (cat === 'earned') return 'fa-tooth';
  if (cat === 'prestige') return 'fa-crown';
  if (cat === 'golden') return 'fa-star';
  if (cat === 'time') return 'fa-clock';
  if (cat === 'upgrades' || cat === 'clicks') return 'fa-arrow-up-right-dots';
  if (cat === 'meta') return 'fa-trophy';
  if (cat === 'gen') {
    const parts = (ach.id || '').split('_');
    const genId = parts.slice(1, -1).join('_');
    const gen = window.GENERATORS && window.GENERATORS.find(g => g.id === genId);
    if (gen && gen.icon) return gen.icon.replace('fa-solid ', '');
    return 'fa-industry';
  }
  return 'fa-trophy';
}

window.achIcon = achIcon;

function AchievementCard({ ach, unlocked, lang, onHover, onLeave }) {
  const [isHovered, setIsHovered] = React.useState(false);
  const [tooltipPos, setTooltipPos] = React.useState({ top: 0, left: 0 });
  const rowRef = React.useRef(null);
  const icon = achIcon(ach);
  
  const handleEnter = () => {
    setIsHovered(true);
    if (rowRef.current) {
      const rect = rowRef.current.getBoundingClientRect();
      
      if (!unlocked) {
        if (onHover) onHover(ach, {
          top: rect.top + 7,
          bottom: rect.bottom - 7,
          left: rect.left,
          right: rect.right,
          x: rect.left + rect.width / 2,
          y: rect.top + rect.height / 2
        }, unlocked);
        return;
      }
      
      const container = rowRef.current.closest('section') || rowRef.current.closest('.mobile-tab-view-body') || rowRef.current.closest('#achievements-container-desktop');
      const containerRect = container ? container.getBoundingClientRect() : rect;
      
      let leftPos = containerRect.left - 250 - 5;
      if (leftPos < 5) {
        leftPos = containerRect.right + 5;
      }
      
      let topPos = rect.top;
      if (topPos + 100 > window.innerHeight) {
        topPos = window.innerHeight - 100 - 10;
      }
      
      setTooltipPos({ top: topPos, left: leftPos });
      
      // We pass null for pos to signal that we are handling the tooltip internally
      if (onHover) {
         onHover(ach, null, unlocked);
      }
    }
  };

  const handleLeave = () => {
    setIsHovered(false);
    if (onLeave) onLeave();
  };

  return (
    <>
      <div 
        ref={rowRef}
        onMouseEnter={handleEnter} 
        onMouseLeave={handleLeave} 
        style={{
          width: 48, height: 48, padding: 4, boxSizing: 'border-box',
          borderRadius: 'var(--radius-s)',
          position: 'relative',
          background: unlocked ? 'var(--warning-i010)' : 'var(--bg-2)',
          border: unlocked ? '1px solid var(--warning-i100)' : '1px solid var(--border-subtle)',
          boxShadow: unlocked ? '0 2px 4px rgba(0,0,0,0.05)' : 'none',
          cursor: 'default',
          opacity: unlocked ? 1 : 0.65,
          transform: isHovered ? 'translateY(-1px)' : 'none',
          transition: 'transform 100ms ease, border-color 150ms ease, background 150ms ease, box-shadow 150ms ease',
          display: 'flex', alignItems: 'center', justifyContent: 'center'
        }}
      >
        <div style={{ width: '100%', height: '100%', borderRadius: 4, background: unlocked ? 'transparent' : 'var(--bg-3)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: unlocked ? 'var(--warning-i100)' : 'var(--fg-3)', overflow: 'hidden', transition: 'background 150ms ease, color 150ms ease' }}>
          {(ach.iconUrl && unlocked) ? <img src={ach.iconUrl} style={{ width: '100%', height: '100%', objectFit: 'contain' }} /> : <i className={`fa-solid ${unlocked ? icon : 'fa-question'}`} style={{ fontSize: 16 }}></i>}
        </div>
        {ach.isNew && (
          <div style={{ position: 'absolute', top: -3, right: -3, width: 8, height: 8, borderRadius: '50%', background: 'var(--negative-i100)', border: '1px solid var(--bg-1)', zIndex: 2 }}></div>
        )}
      </div>
      
      {isHovered && unlocked && window.ReactDOM && window.ReactDOM.createPortal(
        <div style={{
          position: 'absolute',
          top: tooltipPos.top,
          left: tooltipPos.left,
          width: 250,
          background: 'var(--bg-1)',
          border: '1px solid var(--border-subtle)',
          borderRadius: 'var(--radius-m)',
          boxShadow: '0 8px 24px rgba(0,0,0,0.12)',
          padding: '12px',
          boxSizing: 'border-box',
          display: 'flex',
          gap: '12px',
          zIndex: 99999,
          animation: 'fadeIn 0.2s ease-out',
          pointerEvents: 'none'
        }}>
          <div style={{ width: 64, height: 64, borderRadius: 8, background: 'var(--bg-2)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, overflow: 'hidden' }}>
            {ach.iconUrl ? <img src={ach.iconUrl} style={{ width: '100%', height: '100%', objectFit: 'contain' }} /> : <i className={`fa-solid ${icon}`} style={{ fontSize: 24, color: unlocked ? 'var(--warning-i100)' : 'var(--fg-2)' }}></i>}
          </div>
          <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: '6px' }}>
            {unlocked ? (
              <>
                <div>
                  <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--fg-1)', lineHeight: 1.2 }}>{ach.name?.[lang] || ach[lang] || ach.name?.es || ach.es}</div>
                  <div style={{ fontSize: 11, color: 'var(--fg-3)', lineHeight: 1.3, marginTop: 4 }}>{ach.description?.[lang] || ach[`desc_${lang}`] || ach.desc_es}</div>
                </div>
                <div style={{ color: 'var(--positive-i100)', fontSize: 11, fontWeight: 600, marginTop: 2 }}>
                  {lang === 'es' ? `+${ach.rewardPercent !== undefined ? ach.rewardPercent : 1}% prod. global permanente` : `+${ach.rewardPercent !== undefined ? ach.rewardPercent : 1}% permanent global prod.`}
                </div>
              </>
            ) : (
              <div style={{ display: 'flex', alignItems: 'center', height: '100%', color: 'var(--fg-3)', fontSize: 12, lineHeight: 1.4, fontWeight: 500 }}>
                {window.getAchReqText ? window.getAchReqText(ach, lang) : (lang === 'es' ? 'Sigue jugando para descubrirlo' : 'Keep playing to discover')}
              </div>
            )}
          </div>
        </div>,
        document.body
      )}
    </>
  );
}

function Toast({ toast, lang, styleOverride, onClose }) {
  if (!toast) return null;
  const t = window.STRINGS[lang];
  const isAch = !!toast.cat || toast.reqType !== undefined || toast.rewardPercent !== undefined;
  const isUpgrade = !!toast.id && toast.id.startsWith('buy_up_');
  const isSpecial = !!toast.id && (toast.id.startsWith('__') || toast.id.startsWith('bonus_') || toast.id.startsWith('eff_'));
  const isFeedback = toast.id === 'feedback_success';
  
  let icon = toast.icon;
  let title = lang === 'en' && toast.titleEn ? toast.titleEn : toast.title;
  let accent = toast.accent;
  let accentText = toast.accentText;

  if (isAch) {
    icon = icon || (window.achIcon ? `fa-solid ${window.achIcon(toast)}` : "fa-solid fa-trophy");
    title = title || (lang === 'es' ? '¡NUEVO LOGRO OBTENIDO!' : 'NEW ACHIEVEMENT UNLOCKED!');
    accent = accent || "var(--warning-i100)";
    accentText = accentText || "var(--warning-i070)";
  } else if (isUpgrade) {
    icon = icon || "fa-solid fa-cart-shopping";
    title = title || (lang === 'es' ? 'Mejora comprada' : 'Upgrade bought');
    accent = accent || "var(--primary-i100)";
    accentText = accentText || "var(--primary-i070)";
  } else if (isSpecial) {
    icon = icon || "fa-solid fa-bolt";
    title = title || (lang === 'es' ? 'Bonus activo' : 'Bonus active');
    accent = accent || "var(--alternative-i100)";
    accentText = accentText || "var(--alternative-i070)";
  } else if (isFeedback) {
    icon = icon || "fa-solid fa-comment-dots";
    title = title || (lang === 'es' ? 'Mensaje enviado' : 'Message sent');
    accent = accent || "var(--positive-i100)";
    accentText = accentText || "var(--positive-i070)";
  } else {
    icon = icon || "fa-solid fa-info-circle";
    title = title || "Info";
    accent = accent || "var(--primary-i100)";
    accentText = accentText || "var(--primary-i070)";
  }

  const animationStyle = isAch ? 'toastIn 300ms cubic-bezier(0.175, 0.885, 0.32, 1.275)' : 'toastIn 250ms ease, toastOut 250ms ease 3.2s forwards';
  
  let borderStyle = isAch ? '2px solid transparent' : 'none';
  let customAnimation = isAch ? 'rainbow-border-anim 2s linear infinite, toastIn 300ms cubic-bezier(0.175, 0.885, 0.32, 1.275)' : animationStyle;

  if (isAch) {
    const thickness = toast.borderThickness !== undefined ? toast.borderThickness : 2;
    const style = toast.borderStyle || 'solid';
    const color = toast.borderColor || 'transparent';
    const effect = toast.borderEffect || (toast.id ? 'rainbow' : 'none');

    borderStyle = `${thickness}px ${style} ${color}`;
    
    let effectAnim = '';
    if (effect === 'rainbow') effectAnim = 'rainbow-border-anim 2s linear infinite, ';
    else if (effect === 'pulse') effectAnim = 'pulse-border-anim 1.5s ease-in-out infinite, ';
    
    customAnimation = `${effectAnim}toastIn 300ms cubic-bezier(0.175, 0.885, 0.32, 1.275)`;
  }

  const containerStyle = {
    position: 'fixed', bottom: 24, left: '50%', transform: 'translateX(-50%)',
    background: 'var(--complementary-i080)', color: '#fff', padding: 'var(--spacing-3) var(--spacing-4)',
    borderRadius: 'var(--radius-m)', boxShadow: isAch ? 'none' : 'var(--elevation-20)',
    display: 'flex', alignItems: 'center', gap: 'var(--spacing-3)', zIndex: 1000,
    animation: customAnimation, maxWidth: 420, border: borderStyle,
    ...styleOverride
  };

  return (
    <div className="game-toast-container" style={containerStyle}>
      <div style={{ width: 36, height: 36, borderRadius: 'var(--radius-s)', background: (toast.iconUrl || toast.img) ? 'transparent' : accent, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', flexShrink: 0, overflow: 'hidden' }}>
        {(toast.iconUrl || toast.img) ? (
          <img src={toast.iconUrl || toast.img} style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
        ) : (
          <i className={icon} style={{ fontSize: 16 }}></i>
        )}
      </div>
      <div>
        <div className="t-mini-caps" style={{ color: isAch ? '#fff' : accentText, fontWeight: isAch ? 800 : 600, fontSize: isAch ? 13 : undefined, textShadow: isAch ? '0 0 8px rgba(255,215,0,0.8), 0 0 15px rgba(255,140,0,0.6)' : 'none', letterSpacing: isAch ? '1px' : undefined }}>{title}</div>
        <div className="t-heading-xs" style={{ color: '#fff' }}>{toast[lang] || toast.es}</div>
        {toast.desc_es && (
          <div style={{ fontSize: 11, opacity: 0.8, marginTop: 2 }}>
            {toast[`desc_${lang}`] || toast.desc_es}
          </div>
        )}
        {isAch && (
          <div style={{ fontSize: 11, color: 'var(--positive-i100)', fontWeight: 600, marginTop: 2 }}>
            {lang === 'es' ? `+${toast.rewardPercent !== undefined ? toast.rewardPercent : 1}% prod. global permanente` : `+${toast.rewardPercent !== undefined ? toast.rewardPercent : 1}% permanent global prod.`}
          </div>
        )}
      </div>
      {onClose && (
        <button 
          onClick={(e) => { e.stopPropagation(); onClose(); }} 
          style={{ all: 'unset', background: 'rgba(255,255,255,0.1)', width: 24, height: 24, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', marginLeft: 8, flexShrink: 0, transition: 'background 0.2s' }}
          onMouseOver={e => e.currentTarget.style.background = 'rgba(255,255,255,0.2)'}
          onMouseOut={e => e.currentTarget.style.background = 'rgba(255,255,255,0.1)'}
        >
          <i className="fa-solid fa-xmark"></i>
        </button>
      )}
    </div>);
}

function StoreUpgradeIcon({ up, canAfford, purchased, onBuy, lang, fmt, onHover, onLeave, draggable, onDragStart, onDragEnter, onDragOver, onDragLeave, onDrop, onDragEnd }) {
  const [isPressed, setIsPressed] = React.useState(false);
  const [isHovered, setIsHovered] = React.useState(false);
  const rowRef = React.useRef(null);
  
  const handleEnter = () => {
    setIsHovered(true);
    if (onHover && rowRef.current) {
      const rect = rowRef.current.getBoundingClientRect();
      onHover(up, { 
        x: rect.left + rect.width / 2, 
        y: rect.top,
        top: rect.top,
        bottom: rect.bottom,
        left: rect.left,
        right: rect.right
      });
    }
  };

  const handleLeave = () => {
    setIsHovered(false);
    setIsPressed(false);
    if (onLeave) onLeave();
  };

  return (
    <div 
      ref={rowRef}
      onMouseEnter={handleEnter} 
      onMouseLeave={handleLeave} 
      onClick={() => !purchased && canAfford && onBuy(up)}
      onMouseDown={() => !purchased && canAfford && setIsPressed(true)}
      onMouseUp={() => setIsPressed(false)}
      draggable={draggable ? "true" : "false"}
      onDragStart={onDragStart}
      onDragEnter={onDragEnter}
      onDragOver={onDragOver}
      onDragLeave={onDragLeave}
      onDrop={onDrop}
      onDragEnd={onDragEnd}
      style={{
        width: 64, height: 64, padding: 5, boxSizing: 'border-box',
        borderRadius: 'var(--radius-s)',
        position: 'relative',
        background: purchased ? 'var(--positive-i010)' : (canAfford ? 'var(--bg-1)' : 'var(--bg-2)'),
        border: purchased ? '1px solid var(--positive-i100)' : (canAfford ? '1px solid var(--primary-i100)' : '1px solid var(--border-subtle)'),
        boxShadow: (canAfford && !purchased) ? '0 2px 4px rgba(0,0,0,0.05)' : 'none',
        cursor: purchased ? 'grab' : (canAfford ? 'pointer' : 'not-allowed'),
        opacity: (!purchased && !canAfford) ? 0.65 : 1,
        transform: isPressed ? 'translateY(1px)' : (isHovered ? 'translateY(-1px)' : 'none'),
        transition: 'transform 100ms ease, border-color 150ms ease, background 150ms ease, box-shadow 150ms ease',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        WebkitUserDrag: draggable ? 'element' : 'none'
      }}
    >
      <div style={{ width: '100%', height: '100%', borderRadius: 4, background: purchased ? 'transparent' : (canAfford ? 'var(--alternative-i010)' : 'var(--bg-3)'), display: 'flex', alignItems: 'center', justifyContent: 'center', color: purchased ? 'var(--positive-i100)' : (canAfford ? 'var(--alternative-i100)' : 'var(--fg-3)'), overflow: 'hidden', transition: 'background 150ms ease, color 150ms ease', pointerEvents: 'none' }}>
        {up.iconUrl ? <img src={up.iconUrl} draggable={false} style={{ width: '100%', height: '100%', objectFit: 'contain', pointerEvents: 'none' }} /> : <i className={`fa-solid ${up.icon || 'fa-store'}`} style={{ fontSize: 24, pointerEvents: 'none' }}></i>}
      </div>
      {purchased && (
        <div style={{ position: 'absolute', top: -8, right: -8, width: 16, height: 16, borderRadius: '50%', background: 'var(--positive-i100)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', zIndex: 2 }}>
          <i className="fa-solid fa-check" style={{ fontSize: 8 }}></i>
        </div>
      )}
    </div>
  );
}

function VersionLogModal({ onClose, lang }) {
  const versions = (window.VERSION_HISTORY || []).map((item) => ({
    v: item.v,
    date: item.date,
    desc: lang === 'es' ? item.es : item.en
  }));

  return (
    <window.Modal onClose={onClose} maxWidth={650}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16 }}>
        <i className="fa-solid fa-clock-rotate-left" style={{ color: '#1a8fff', fontSize: 20 }}></i>
        <div style={{ fontSize: 18, fontWeight: 700, color: 'var(--fg-1)', fontFamily: 'var(--font-sans)' }}>{lang === 'es' ? 'Historial de Versiones' : 'Version History'}</div>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 12, overflowY: 'auto', flex: 1, minHeight: 0, paddingRight: 6 }}>
        {versions.map((v, i) => (
          <div key={v.v + i} style={{ borderLeft: '2px solid #e1e8ef', paddingLeft: 16, paddingBottom: 4 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
              <span style={{ fontSize: 11, fontWeight: 700, color: '#1a8fff', background: 'rgba(26,143,255,0.1)', padding: '2px 8px', borderRadius: 6, fontFamily: 'var(--font-sans)' }}>{v.v}</span>
              {v.date && <span style={{ fontSize: 10, color: '#8aaacc', fontFamily: 'var(--font-sans)' }}>{v.date}</span>}
            </div>
            <div style={{ fontSize: 13, color: 'var(--fg-1)', lineHeight: 1.5, fontFamily: 'var(--font-sans)', whiteSpace: 'pre-wrap' }} dangerouslySetInnerHTML={{ __html: v.desc }} />
          </div>
        ))}
      </div>
      <button onClick={onClose} style={{ ...primaryBtnStyle, flex: 'none', width: '100%', marginTop: 20 }}>
        {lang === 'es' ? 'Volver' : 'Back'}
      </button>
    </window.Modal>
  );
}
function LegalModal({ onClose, lang }) {
  const content = {
    title: lang === 'es' ? 'Aviso Legal y Privacidad' : 'Legal Notice & Privacy',
    sections: [
      {
        title: lang === 'es' ? '1. Privacidad de la Información' : '1. Information Privacy',
        body: lang === 'es' 
          ? 'El juego no utiliza, almacena ni recopila datos sensibles, confidenciales o privados de clínicas, pacientes, colaboradores ni de ninguna entidad relacionada con Healthatom o sus filiales. Toda la interacción del usuario se procesa sin vincularse a expedientes médicos o administrativos reales.'
          : 'The game does not use, store, or collect sensitive, confidential, or private data of clinics, patients, collaborators, or any entity related to Healthatom or its affiliates. All user interaction is processed without linking to real medical or administrative records.'
      },
      {
        title: lang === 'es' ? '2. Naturaleza del Contenido' : '2. Nature of the Content',
        body: lang === 'es'
          ? 'Toda la información, mecánicas y elementos educativos presentes en el juego tienen un propósito estrictamente de aprendizaje y entretenimiento. Dicho contenido ha sido elaborado a partir de información de carácter general y extraída de fuentes y documentación públicas pertenecientes a Healthatom, Medlink, Dentalink y Gerty.'
          : 'All information, mechanics, and educational elements present in the game have a strict learning and entertainment purpose. Such content has been developed from general information and extracted from public sources and documentation belonging to Healthatom, Medlink, Dentalink, and Gerty.'
      },
      {
        title: lang === 'es' ? '3. Comunicaciones y Datos Personales' : '3. Communications & Personal Data',
        body: lang === 'es'
          ? 'Garantizamos que el juego en ningún momento enviará ningún tipo de correo electrónico (email) ni solicitará información personal, de contacto, ubicación o financiera a ningún jugador. La experiencia está diseñada íntegramente para preservar el anonimato y la seguridad digital del usuario en todo momento.'
          : 'We guarantee that the game will at no time send any kind of email or request personal, contact, location, or financial information from any player. The experience is entirely designed to preserve the anonymity and digital security of the user at all times.'
      },
      {
        title: lang === 'es' ? '4. Gratuidad del Servicio' : '4. Free Service',
        body: lang === 'es'
          ? '"ToothClicker" ha sido concebido, diseñado y publicado para ser disfrutado de forma cien por ciento (100%) gratuita. No existen compras ocultas, suscripciones, microtransacciones ni requerimientos de pago bajo ninguna circunstancia, garantizando un acceso equitativo para todos.'
          : '"ToothClicker" has been conceived, designed, and published to be enjoyed one hundred percent (100%) for free. There are no hidden purchases, subscriptions, microtransactions, or payment requirements under any circumstances, ensuring equitable access for all.'
      }
    ]
  };

  return (
    <window.Modal onClose={onClose} maxWidth={650}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16 }}>
        <i className="fa-solid fa-scale-balanced" style={{ color: '#1a8fff', fontSize: 20 }}></i>
        <div style={{ fontSize: 18, fontWeight: 700, color: 'var(--fg-1)', fontFamily: 'var(--font-sans)' }}>{content.title}</div>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 16, overflowY: 'auto', flex: 1, minHeight: 0, paddingRight: 6 }}>
        {content.sections.map((sec, i) => (
          <div key={i} style={{ borderLeft: '2px solid #e1e8ef', paddingLeft: 16, paddingBottom: 4 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
              <span style={{ fontSize: 13, fontWeight: 700, color: '#1a8fff', fontFamily: 'var(--font-sans)' }}>{sec.title}</span>
            </div>
            <div style={{ fontSize: 13, color: 'var(--fg-1)', lineHeight: 1.6, fontFamily: 'var(--font-sans)', whiteSpace: 'pre-wrap', opacity: 0.9 }}>
              {sec.body}
            </div>
          </div>
        ))}
        <div style={{ fontSize: 12, color: 'var(--fg-2)', marginTop: 8, fontStyle: 'italic', fontFamily: 'var(--font-sans)', opacity: 0.8 }}>
          {lang === 'es' 
            ? 'Al utilizar esta aplicación, reconoces haber leído y comprendido este aviso legal, aceptando que el propósito de esta plataforma es puramente lúdico y educativo.'
            : 'By using this application, you acknowledge having read and understood this legal notice, accepting that the purpose of this platform is purely playful and educational.'}
        </div>
      </div>
      <button onClick={onClose} style={{ ...primaryBtnStyle, flex: 'none', width: '100%', marginTop: 24 }}>
        {lang === 'es' ? 'Entendido' : 'Understood'}
      </button>
    </window.Modal>
  );
}

function AboutModal({ onClose, lang }) {
  const [showLog, setShowLog] = React.useState(false);
  const [showLegal, setShowLegal] = React.useState(false);
  const versions = (window.VERSION_HISTORY || []).map((item) => ({
    v: item.v,
    date: item.date,
    desc: lang === 'es' ? item.es : item.en,
    latest: item.latest
  }));

  if (showLog) return <VersionLogModal onClose={() => setShowLog(false)} lang={lang} />;
  if (showLegal) return <LegalModal onClose={() => setShowLegal(false)} lang={lang} />;

  return (
    <window.Modal onClose={onClose} maxWidth={480}>
      <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0, minWidth: 300, maxWidth: 600, minHeight: 0 }}>
        <img src={window.GAME_CONTENT?.terminology?.images?.logoVertical || "uploads/logo-vertical.png"} alt="ToothClicker" style={{ width: 180, objectFit: 'contain', marginBottom: 8, flexShrink: 0 }} />
        <div style={{ fontSize: 15, fontWeight: 600, color: '#333', fontFamily: 'var(--font-sans)', marginBottom: 16, flexShrink: 0, padding: "20px 0px" }}>
          {lang === 'es' ? 'Creado por Jaime Arias' : 'Created by Jaime Arias'}
        </div>
        <div style={{ width: '100%', borderTop: '1.5px dashed #d0dce8', marginBottom: 16, flexShrink: 0 }} />
        <div style={{ width: '100%' }}>
          {(() => {
            const latest = versions[0];
            if (!latest) return null;
            return (
              <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6 }}>
                <span style={{ fontSize: 13, fontWeight: 700, color: '#fff', background: '#1a8fff', padding: '4px 14px', borderRadius: 999, fontFamily: 'var(--font-sans)' }}>{latest.v}</span>
                {latest.date && <span style={{ fontSize: 11, color: '#aac0d4', fontFamily: 'var(--font-sans)' }}>{latest.date}</span>}
                  <button 
                    onClick={() => setShowLog(true)}
                    style={{ all: 'unset', color: '#1a8fff', fontSize: 12, fontWeight: 600, marginTop: 10, cursor: 'pointer', textDecoration: 'underline', fontFamily: 'var(--font-sans)' }}
                  >
                    {lang === 'es' ? 'Ver log de versiones' : 'View version log'}
                  </button>
                  <button 
                    onClick={() => setShowLegal(true)}
                    style={{ all: 'unset', color: '#1a8fff', fontSize: 12, fontWeight: 600, marginTop: 4, cursor: 'pointer', textDecoration: 'underline', fontFamily: 'var(--font-sans)' }}
                  >
                    {lang === 'es' ? 'Legal' : 'Legal'}
                  </button>
                </div>);
            })()}
        </div>
        <button onClick={onClose} style={{ all: 'unset', boxSizing: 'border-box', marginTop: 24, width: '100%', textAlign: 'center', padding: '13px 0', borderRadius: 999, background: '#1a8fff', color: '#fff', fontSize: 16, fontWeight: 600, fontFamily: "'PixelifySans', var(--font-sans)", cursor: 'pointer', flexShrink: 0 }}>
          {lang === 'es' ? 'Cerrar' : 'Close'}
        </button>
      </div>
    </window.Modal>);
}

function GameTour({ step, lang, onNext, onPrev, onClose, dontShowAgain, onToggleShowAgain }) {
  const t = window.STRINGS[lang];
  const [closing, setClosing] = React.useState(false);
  
  React.useEffect(() => {
    setClosing(false);
  }, [step]);
  const steps = [
    {
      targetId: null,
      title: { es: '¡Bienvenido a Tooth Clicker!', en: 'Welcome to Tooth Clicker!' },
      desc: { es: 'Prepárate para convertirte en el magnate dental definitivo. 🦷✨', en: 'Get ready to become the ultimate dental tycoon. 🦷✨' }
    },
    {
      targetId: 'main-tooth-target',
      title: { es: '¡Dale caña!', en: 'Go for it!' },
      desc: { es: 'Haz click en este diente para empezar a recolectar piezas. ¡Cada click cuenta!', en: 'Click on this tooth to start collecting pieces. Every click counts!' }
    },
    {
      targetId: 'game-tabs-tour',
      title: { es: 'Progreso constante', en: 'Constant progress' },
      desc: { es: 'Aquí es donde ocurre la magia. Compra clínicas, desbloquea mejoras locas y presume de tus logros dentales.', en: 'This is where the magic happens. Buy clinics, unlock crazy upgrades, and show off your dental achievements.' }
    },
    {
      targetId: 'tab-prestige',
      title: { es: 'Poder ancestral', en: 'Ancient power' },
      desc: { es: '¿Listo para el siguiente nivel? El prestigio te da bonus permanentes que te harán imparable.', en: 'Ready for the next level? Prestige gives you permanent bonuses that will make you unstoppable.' }
    },
    {
      targetId: 'tab-leaderboard',
      title: { es: 'La cima te espera', en: 'The top awaits' },
      desc: { es: 'Mira cómo te comparas con otros dentistas de todo el mundo en tiempo real.', en: 'See how you compare with other dentists around the world in real time.' }
    },
    {
      targetId: 'manual-tour-trigger',
      title: { es: 'Siempre aquí para ayudarte', en: 'Always here to help' },
      desc: { es: 'Si alguna vez necesitas repasar algo, puedes volver a iniciar este tour haciendo click en este icono de ayuda. ¡A cepillar!', en: 'If you ever need to review anything, you can restart this tour by clicking this help icon. Happy brushing!' }
    }
  ];

  const current = steps[step];
  if (!current) return null;

  const [rect, setRect] = React.useState(null);

  React.useLayoutEffect(() => {
    if (current.targetId) {
      const el = document.getElementById(current.targetId);
      if (el) {
        setRect(el.getBoundingClientRect());
      } else {
        setRect(null);
      }
    } else {
      setRect(null);
    }
  }, [step, current.targetId]);

  const handleClose = () => {
    setClosing(true);
    setTimeout(onClose, 250);
  };

  const modalStyle = rect ? {
    position: 'fixed',
    left: rect.left + rect.width / 2,
    top: rect.bottom + 20,
    transform: 'translateX(-50%)',
    zIndex: 10000,
    width: 320
  } : {
    position: 'fixed',
    left: '50%',
    top: '50%',
    transform: 'translate(-50%, -50%)',
    zIndex: 10000,
    width: 380
  };

  // Adjust for edges
  if (rect) {
    if (modalStyle.left - 160 < 20) modalStyle.left = 180;
    if (modalStyle.left + 160 > window.innerWidth - 20) modalStyle.left = window.innerWidth - 180;
    if (modalStyle.top + 200 > window.innerHeight - 20) modalStyle.top = rect.top - 220;
  }

  return (
    <div style={{ 
      position: 'fixed', inset: 0, zIndex: 9999, pointerEvents: closing ? 'none' : 'auto',
      animation: closing ? 'fadeOut 250ms forwards' : 'fadeIn 300ms forwards',
      display: rect ? 'block' : 'flex',
      alignItems: 'center',
      justifyContent: 'center'
    }}>
      {/* Blurred and dimmed overlay with a hole cut out */}
      <div style={{
        position: 'absolute',
        inset: 0,
        background: 'rgba(5,9,13,0.5)',
        backdropFilter: 'blur(4px)',
        zIndex: 9998,
        transition: 'clip-path 400ms cubic-bezier(0.4, 0, 0.2, 1)',
        clipPath: rect ? `polygon(
          0% 0%, 100% 0%, 100% 100%, 0% 100%, 0% 0%, 
          ${rect.left - 8}px ${rect.top - 8}px, 
          ${rect.left - 8}px ${rect.bottom + 8}px, 
          ${rect.right + 8}px ${rect.bottom + 8}px, 
          ${rect.right + 8}px ${rect.top - 8}px, 
          ${rect.left - 8}px ${rect.top - 8}px
        )` : 'none'
      }} />
      
      {/* Highlight border and glow */}
      {rect && (
        <div style={{
          position: 'fixed',
          left: rect.left - 8,
          top: rect.top - 8,
          width: rect.width + 16,
          height: rect.height + 16,
          border: '2px solid var(--primary-i100)',
          boxShadow: '0 0 40px var(--primary-i100)',
          borderRadius: 12,
          pointerEvents: 'none',
          zIndex: 9999,
          transition: 'all 400ms cubic-bezier(0.4, 0, 0.2, 1)',
          opacity: closing ? 0 : 1
        }} />
      )}

      {/* Modal Container: Handles fixed positioning if rect exists, else flex centering handles it */}
      <div style={rect ? {
        position: 'fixed',
        left: modalStyle.left,
        top: modalStyle.top,
        transform: modalStyle.transform,
        zIndex: 10000,
        width: modalStyle.width
      } : {
        position: 'relative',
        zIndex: 10000,
        width: modalStyle.width
      }}>
        <div className="tour-modal" style={{
          background: 'var(--bg-1)',
          padding: 'var(--spacing-6)',
          borderRadius: 'var(--radius-l)',
          boxShadow: 'var(--elevation-30)',
          display: 'flex',
          flexDirection: 'column',
          gap: 12,
          animation: closing ? 'modalOut 250ms forwards' : 'modalIn 300ms cubic-bezier(0.34, 1.56, 0.64, 1)'
        }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <span style={{ fontSize: 11, fontWeight: 700, color: 'var(--primary-i100)', textTransform: 'uppercase', letterSpacing: 1 }}>
              {lang === 'es' ? `Paso ${step + 1} de ${steps.length}` : `Step ${step + 1} of ${steps.length}`}
            </span>
            <button onClick={handleClose} style={{ all: 'unset', cursor: 'pointer', color: 'var(--fg-3)', fontSize: 18 }} className="hover-bg-danger">
              <i className="fa-solid fa-xmark"></i>
            </button>
          </div>

          <div className="t-heading-m" style={{ color: 'var(--fg-1)' }}>{current.title[lang] || current.title.es}</div>
          <div className="t-body-m" style={{ color: 'var(--fg-2)', lineHeight: 1.5 }}>{current.desc[lang] || current.desc.es}</div>

          <div style={{ marginTop: 12, display: 'flex', alignItems: 'center', gap: 10 }}>
            <label style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', userSelect: 'none' }}>
              <input type="checkbox" checked={dontShowAgain} onChange={onToggleShowAgain} style={{ cursor: 'pointer' }} />
              <span style={{ fontSize: 12, color: 'var(--fg-3)', fontWeight: 500 }}>{lang === 'es' ? 'No volver a mostrar' : 'Don\'t show again'}</span>
            </label>
          </div>

          <div style={{ marginTop: 16, display: 'flex', gap: 8 }}>
            {step > 0 && (
              <button onClick={onPrev} style={secondaryBtnStyle}>
                {lang === 'es' ? 'Anterior' : 'Previous'}
              </button>
            )}
            <button onClick={step === steps.length - 1 ? handleClose : onNext} style={primaryBtnStyle}>
              {step === steps.length - 1 ? (lang === 'es' ? '¡Entendido!' : 'Got it!') : (lang === 'es' ? 'Siguiente' : 'Next')}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}
function Dropdown({ value, onChange, options, style, disabled = false, searchable = false, placeholder = "Buscar..." }) {
  const [isOpen, setIsOpen] = useStateC(false);
  const [rect, setRect] = useStateC(null);
  const [searchTerm, setSearchTerm] = useStateC('');
  const dropdownRef = React.useRef(null);
  const inputRef = React.useRef(null);

  const updatePosition = React.useCallback(() => {
    if (dropdownRef.current) {
      setRect(dropdownRef.current.getBoundingClientRect());
    }
  }, []);

  React.useEffect(() => {
    if (isOpen) {
      updatePosition();
      const handleScroll = () => {
        updatePosition();
      };
      // Usar capture = true para interceptar todos los scrolls (incluso de contenedores internos)
      window.addEventListener('scroll', handleScroll, true);
      window.addEventListener('resize', updatePosition);
      return () => {
        window.removeEventListener('scroll', handleScroll, true);
        window.removeEventListener('resize', updatePosition);
      };
    }
  }, [isOpen, updatePosition]);

  const handleToggle = () => {
    if (disabled) return;
    if (!isOpen) {
      updatePosition();
      setSearchTerm('');
    }
    setIsOpen(!isOpen);
  };

  React.useEffect(() => {
    if (isOpen && searchable && inputRef.current) {
      inputRef.current.focus();
    }
  }, [isOpen, searchable]);

  const selectedOption = options.find(o => String(o.value) === String(value));
  const displayLabel = selectedOption ? selectedOption.label : 'Selecciona...';

  let portalStyle = {};
  if (rect) {
    const spaceBelow = window.innerHeight - rect.bottom;
    const spaceAbove = rect.top;
    const dropUp = spaceBelow < 250 && spaceAbove > spaceBelow;

    portalStyle = {
      position: 'fixed',
      left: rect.left,
      width: rect.width,
      zIndex: 99999,
    };

    if (dropUp) {
      portalStyle.bottom = window.innerHeight - rect.top + 4;
    } else {
      portalStyle.top = rect.bottom + 4;
    }
  }

  return (
    <div ref={dropdownRef} style={{ position: 'relative', width: '100%', ...style }}>
      <button 
        type="button"
        disabled={disabled}
        onClick={handleToggle}
        style={{
          all: 'unset', boxSizing: 'border-box', width: '100%', padding: '12px 16px',
          background: disabled ? 'var(--bg-2)' : 'var(--bg-1)', 
          borderRadius: 14, fontSize: 14, border: isOpen ? '2px solid var(--primary-i100)' : '2px solid var(--border-default)',
          transition: 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)', fontFamily: 'var(--font-sans)',
          color: 'var(--fg-1)', boxShadow: isOpen ? '0 0 0 4px rgba(0,118,219,0.15), 0 4px 12px rgba(0,0,0,0.1)' : 'inset 0 2px 4px rgba(0,0,0,0.02)',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between', cursor: disabled ? 'not-allowed' : 'pointer'
        }}
      >
        <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{displayLabel}</span>
        <i className={`fa-solid fa-chevron-${isOpen ? 'up' : 'down'}`} style={{ color: 'var(--primary-i100)', fontSize: 12, marginLeft: 8 }}></i>
      </button>

      {isOpen && ReactDOM.createPortal(
        <>
          <div 
            style={{ position: 'fixed', inset: 0, zIndex: 99998 }} 
            onMouseDown={(e) => { e.stopPropagation(); setIsOpen(false); }}
          />
          <div style={{
            ...portalStyle,
            background: 'var(--bg-1)', border: '1px solid var(--border-default)', borderRadius: 12,
            boxShadow: 'var(--elevation-20)',
            maxHeight: 250, display: 'flex', flexDirection: 'column'
          }}>
            {searchable && (
              <div style={{ padding: 8, borderBottom: '1px solid var(--border-default)' }}>
                <div style={{ position: 'relative' }}>
                  <i className="fa-solid fa-search" style={{ position: 'absolute', left: 10, top: '50%', transform: 'translateY(-50%)', color: 'var(--fg-3)', fontSize: 12 }}></i>
                  <input
                    ref={inputRef}
                    type="text"
                    value={searchTerm}
                    onChange={e => setSearchTerm(e.target.value)}
                    placeholder={placeholder}
                    onKeyDown={e => e.stopPropagation()}
                    style={{
                      width: '100%', boxSizing: 'border-box', padding: '6px 10px 6px 28px',
                      borderRadius: 6, border: '1px solid var(--border-default)', background: 'var(--bg-2)',
                      color: 'var(--fg-1)', fontSize: 13, outline: 'none'
                    }}
                  />
                </div>
              </div>
            )}
            <div style={{ overflowY: 'auto', padding: 6, display: 'flex', flexDirection: 'column', gap: 2 }}>
              {options.filter(opt => !searchable || !searchTerm || (opt.label || '').toString().toLowerCase().includes(searchTerm.toLowerCase())).map((opt, i) => (
              <button
                key={i}
                type="button"
                onMouseDown={(e) => { e.stopPropagation(); onChange(opt.value); setIsOpen(false); }}
                style={{
                  all: 'unset', boxSizing: 'border-box', padding: '10px 12px',
                  borderRadius: 8, fontSize: 13, cursor: 'pointer', fontFamily: 'var(--font-sans)',
                  background: String(value) === String(opt.value) ? 'var(--primary-i010)' : 'transparent',
                  color: String(value) === String(opt.value) ? 'var(--primary-i100)' : 'var(--fg-1)',
                  fontWeight: String(value) === String(opt.value) ? 600 : 500,
                  transition: 'background 0.2s',
                  display: 'flex', alignItems: 'center'
                }}
                onMouseEnter={(e) => {
                  if (String(value) !== String(opt.value)) {
                    e.currentTarget.style.background = 'var(--bg-2)';
                  }
                }}
                onMouseLeave={(e) => {
                  if (String(value) !== String(opt.value)) {
                    e.currentTarget.style.background = 'transparent';
                  }
                }}
              >
                {opt.label}
              </button>
            ))}
            {options.filter(opt => !searchable || !searchTerm || (opt.label || '').toString().toLowerCase().includes(searchTerm.toLowerCase())).length === 0 && (
              <div style={{ padding: '10px 12px', fontSize: 13, color: 'var(--fg-3)', textAlign: 'center' }}>
                No hay opciones
              </div>
            )}
            </div>
          </div>
        </>,
        document.body
      )}
    </div>
  );
}

const OdometerChar = React.memo(function OdometerChar({ char }) {
  const isDigit = /^[0-9]$/.test(char);
  if (!isDigit) {
    return <span style={{ display: 'inline-block', height: '1em', lineHeight: '1em', verticalAlign: 'bottom' }}>{char}</span>;
  }
  return (
    <span style={{ display: 'inline-block', height: '1em', overflow: 'hidden', verticalAlign: 'bottom' }}>
      <span style={{ 
        display: 'flex', 
        flexDirection: 'column', 
        transition: 'transform 300ms cubic-bezier(0.4, 0, 0.2, 1)', 
        transform: `translateY(-${parseInt(char, 10)}em)`,
        willChange: 'transform'
      }}>
        {['0','1','2','3','4','5','6','7','8','9'].map(d => (
          <span key={d} style={{ height: '1em', lineHeight: '1em', textAlign: 'center' }}>{d}</span>
        ))}
      </span>
    </span>
  );
});

function Odometer({ value, formatFn, className, style, ...props }) {
  const [displayValue, setDisplayValue] = React.useState(0);
  
  React.useEffect(() => {
    const raf = requestAnimationFrame(() => {
      setDisplayValue(value);
    });
    return () => cancelAnimationFrame(raf);
  }, [value]);

  const str = formatFn ? formatFn(displayValue) : (window.formatNum ? window.formatNum(displayValue) : String(displayValue));
  return (
    <span className={className} style={{ display: 'inline-flex', verticalAlign: 'bottom', fontVariantNumeric: 'tabular-nums', ...style }} {...props}>
      {str.split('').map((char, i) => {
         const posFromEnd = str.length - i;
         return <OdometerChar key={posFromEnd} char={char} />;
      })}
    </span>
  );
}

window.Tooth3DViewer = function({ glbData, textureUrl, onClick, onPointerDownOut, onPointerUpOut, style, className, animateFloat = true, onModelLoaded, modelScale = 2.4, disableRotation = false }) {
  const mountRef = React.useRef(null);
  const onClickRef = React.useRef(onClick);
  const animateFloatRef = React.useRef(animateFloat);
  const [error, setError] = React.useState(null);

  React.useEffect(() => {
    onClickRef.current = onClick;
  }, [onClick]);

  React.useEffect(() => {
    animateFloatRef.current = animateFloat;
  }, [animateFloat]);

  React.useEffect(() => {
    if (!window.THREE) {
      setError("Three.js not loaded");
      return;
    }
    if (!window.THREE.GLTFLoader) {
      setError("GLTFLoader not loaded");
      return;
    }

    const { THREE } = window;
    const width = mountRef.current.clientWidth || 260;
    const height = mountRef.current.clientHeight || 260;

    let scene = new THREE.Scene();
    
    // Transparent background, preserve drawing buffer for snapshot extraction
    let renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, preserveDrawingBuffer: true });
    renderer.setSize(width, height);
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.outputEncoding = THREE.sRGBEncoding;
    mountRef.current.innerHTML = '';
    mountRef.current.appendChild(renderer.domElement);

    let camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);
    camera.position.z = 5;

    // Lights
    const ambientLight = new THREE.AmbientLight(0xffffff, 1.0);
    scene.add(ambientLight);
    const dirLight = new THREE.DirectionalLight(0xffffff, 1.2);
    dirLight.position.set(5, 10, 7);
    scene.add(dirLight);
    const backLight = new THREE.DirectionalLight(0xffffff, 0.6);
    backLight.position.set(-5, -5, -5);
    scene.add(backLight);

    let mesh = null;
    let isDragging = false;
    let previousMousePosition = { x: 0, y: 0 };
    let hasMoved = false;
    let animationFrameId;
    let rotationVelocity = 0;
    let time = 0;

    const animate = () => {
      animationFrameId = requestAnimationFrame(animate);
      if (mesh) {
        if (animateFloatRef.current) {
          time += 0.03;
          // Floating up and down
          mesh.position.y = Math.sin(time) * 0.12;
        } else {
          mesh.position.y += (0 - mesh.position.y) * 0.1;
        }

        if (!isDragging) {
          if (Math.abs(rotationVelocity) > 0.001 && !disableRotation) {
            mesh.rotation.y += rotationVelocity;
            rotationVelocity *= 0.95; // Friction
          } else {
            rotationVelocity = 0;
          }
        }
        if (renderer && scene && camera) {
          renderer.render(scene, camera);
        }
      }
    };
    animate();

    const loader = new THREE.GLTFLoader();
    
    // Add Draco support for optimized GLBs
    let dracoLoader = null;
    if (window.THREE.DRACOLoader) {
      dracoLoader = new window.THREE.DRACOLoader();
      dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/libs/draco/');
      loader.setDRACOLoader(dracoLoader);
    }

    let isMounted = true;

    const disposeMaterial = (mat) => {
      if (!mat) return;
      for (const key of Object.keys(mat)) {
        const value = mat[key];
        if (value && typeof value === 'object' && value.dispose) {
          try {
            value.dispose();
          } catch (e) {}
        }
      }
      if (mat.dispose) {
        try {
          mat.dispose();
        } catch (e) {}
      }
    };

    loader.load(glbData, (gltf) => {
      if (!isMounted) {
        // Dispose GLTF right away if component unmounted while loading
        gltf.scene.traverse((object) => {
          if (object.isMesh) {
            if (object.geometry) {
              try { object.geometry.dispose(); } catch (e) {}
            }
            if (object.material) {
              if (Array.isArray(object.material)) {
                object.material.forEach(disposeMaterial);
              } else {
                disposeMaterial(object.material);
              }
            }
          }
        });
        return;
      }

      const object = gltf.scene;
      
      // Center and scale
      const box = new THREE.Box3().setFromObject(object);
      const center = box.getCenter(new THREE.Vector3());
      const size = box.getSize(new THREE.Vector3());
      const maxDim = Math.max(size.x, size.y, size.z);
      
      const scale = modelScale / maxDim; // Adjust scale based on prop
      object.scale.set(scale, scale, scale);
      object.position.sub(center.multiplyScalar(scale));
      object.rotation.y = -Math.PI / 2; // Rotate 90 degrees to face front

      // Do not apply external texture. Let the GLB use its own baked materials.
      
      // Add a soft circular shadow underneath the model to look floating
      const shadowCanvas = document.createElement('canvas');
      shadowCanvas.width = 128;
      shadowCanvas.height = 128;
      const ctx = shadowCanvas.getContext('2d');
      const gradient = ctx.createRadialGradient(64, 64, 0, 64, 64, 64);
      gradient.addColorStop(0, 'rgba(0,0,0,0.25)');
      gradient.addColorStop(1, 'rgba(0,0,0,0)');
      ctx.fillStyle = gradient;
      ctx.fillRect(0, 0, 128, 128);
      
      const shadowTexture = new THREE.CanvasTexture(shadowCanvas);
      const shadowMaterial = new THREE.MeshBasicMaterial({
        map: shadowTexture,
        transparent: true,
        depthWrite: false
      });
      const shadowMesh = new THREE.Mesh(new THREE.PlaneGeometry(2.4, 2.4), shadowMaterial);
      shadowMesh.rotation.x = -Math.PI / 2; // Flat on the floor
      shadowMesh.position.y = -1.4; // Slightly below the scaled model
      if (scene) scene.add(shadowMesh);

      // Wrap in a group for rotation
      mesh = new THREE.Group();
      mesh.add(object);
      if (scene) scene.add(mesh);

      // Take snapshot if requested
      if (onModelLoaded) {
        // Force a render so the canvas has the model drawn
        if (renderer && scene && camera) {
          const oldAspect = camera.aspect;
          const oldFov = camera.fov;
          const oldWidth = mountRef.current.clientWidth || 260;
          const oldHeight = mountRef.current.clientHeight || 260;
          
          // Temporarily render as square to get a perfect icon snapshot without clipping
          renderer.setSize(256, 256);
          camera.aspect = 1;
          camera.fov = 38; // Zoom out slightly to avoid cutting off
          camera.updateProjectionMatrix();
          
          renderer.render(scene, camera);
          const dataUrl = renderer.domElement.toDataURL('image/png');
          onModelLoaded(dataUrl);
          
          // Restore
          renderer.setSize(oldWidth, oldHeight);
          camera.aspect = oldAspect;
          camera.fov = oldFov;
          camera.updateProjectionMatrix();
          renderer.render(scene, camera);
        }
      }
    }, undefined, (err) => {
      console.error(err);
      setError("Invalid GLB data");
    });

    const onPointerDown = (e) => {
      e.preventDefault(); // Stop native dragging
      if (mountRef.current) mountRef.current.style.cursor = 'grabbing';
      isDragging = true;
      hasMoved = false;
      const clientX = e.touches ? e.touches[0].clientX : e.clientX;
      const clientY = e.touches ? e.touches[0].clientY : e.clientY;
      previousMousePosition = { x: clientX, y: clientY };
      if (onPointerDownOut) onPointerDownOut(e);
    };

    const onPointerMove = (e) => {
      if (isDragging && mesh) {
        e.preventDefault(); // Prevent scrolling while rotating
        const clientX = e.touches ? e.touches[0].clientX : e.clientX;
        const deltaMove = clientX - previousMousePosition.x;
        
        if (!disableRotation) {
          hasMoved = true;
          mesh.rotation.y += deltaMove * 0.01;
          rotationVelocity = deltaMove * 0.01;
        } else {
          if (Math.abs(deltaMove) > 5) hasMoved = true;
        }
        
        previousMousePosition = { x: clientX, y: previousMousePosition.y };
      }
    };

    const onPointerUp = (e) => {
      if (isDragging && !hasMoved) {
        // Raycaster for click
        const raycaster = new THREE.Raycaster();
        const mouse = new THREE.Vector2();
        
        const rect = renderer.domElement.getBoundingClientRect();
        const clientX = e.changedTouches ? e.changedTouches[0].clientX : e.clientX;
        const clientY = e.changedTouches ? e.changedTouches[0].clientY : e.clientY;
        
        mouse.x = ((clientX - rect.left) / rect.width) * 2 - 1;
        mouse.y = -((clientY - rect.top) / rect.height) * 2 + 1;
        
        raycaster.setFromCamera(mouse, camera);
        const intersects = raycaster.intersectObject(mesh, true);
        
        if (intersects.length > 0) {
          // Reset rotation briefly for visual effect
          mesh.scale.setScalar(0.96);
          setTimeout(() => {
            if (mesh) {
              mesh.scale.setScalar(1.0);
            }
          }, 100);
          
          const mockEvent = {
            clientX,
            clientY,
            currentTarget: mountRef.current
          };
          if (onClickRef.current) onClickRef.current(mockEvent);
        }
      }
      isDragging = false;
      hasMoved = false;
      if (mountRef.current) mountRef.current.style.cursor = 'pointer';
      if (onPointerUpOut) onPointerUpOut(e);
    };

    const canvas = renderer.domElement;
    canvas.addEventListener('mousedown', onPointerDown);
    canvas.addEventListener('mousemove', onPointerMove);
    window.addEventListener('mouseup', onPointerUp);
    
    canvas.addEventListener('touchstart', onPointerDown, {passive: false});
    canvas.addEventListener('touchmove', onPointerMove, {passive: false});
    window.addEventListener('touchend', onPointerUp);

    const resizeObserver = new ResizeObserver(entries => {
      for (let entry of entries) {
        const { width, height } = entry.contentRect;
        if (width > 0 && height > 0 && renderer && camera) {
          renderer.setSize(width, height);
          camera.aspect = width / height;
          camera.updateProjectionMatrix();
        }
      }
    });
    if (mountRef.current) {
      resizeObserver.observe(mountRef.current);
    }

    return () => {
      isMounted = false;
      canvas.removeEventListener('mousedown', onPointerDown);
      canvas.removeEventListener('mousemove', onPointerMove);
      window.removeEventListener('mouseup', onPointerUp);
      canvas.removeEventListener('touchstart', onPointerDown);
      canvas.removeEventListener('touchmove', onPointerMove);
      window.removeEventListener('touchend', onPointerUp);
      resizeObserver.disconnect();
      if (animationFrameId) cancelAnimationFrame(animationFrameId);
      
      // Memory cleanup: Traverse and dispose everything
      if (scene) {
        scene.traverse((object) => {
          if (object.geometry) {
            try { object.geometry.dispose(); } catch (e) {}
          }
          if (object.material) {
            if (Array.isArray(object.material)) {
              object.material.forEach(disposeMaterial);
            } else {
              disposeMaterial(object.material);
            }
          }
        });
      }

      if (dracoLoader) {
        try { dracoLoader.dispose(); } catch (e) {}
      }

      if (mountRef.current) mountRef.current.innerHTML = '';
      if (renderer) {
        try { renderer.dispose(); } catch (e) {}
      }
      
      // Nullify all references for GC
      scene = null;
      renderer = null;
      camera = null;
      mesh = null;
      dracoLoader = null;
    };
  }, [glbData]);

  if (error) {
    return <div style={{...style, display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#ff000022'}} className={className}>Error 3D: {error}</div>;
  }

  return (
    <div ref={mountRef} style={{ width: '100%', height: '100%', cursor: 'pointer', ...style }} className={className} />
  );
};

Object.assign(window, { Odometer, ToothbrushRing, AboutModal, VersionLogModal, GameTour, StatTile, StatsGroup, TabBar, GeneratorRow, ClickUpgradeRow, AcademyUpgradeRow, AchievementCard, Toast, StoreUpgradeIcon, ToothIcon, primaryBtnStyle, secondaryBtnStyle, Dropdown, Tooth3DViewer });