// MedHub 共享组件 — Icons, Avatars, Mobile/Desktop Shells, common bits

const Icon = ({ name, size = 18, color = 'currentColor', strokeWidth = 1.7, className = '', style }) => {
  const paths = ICONS[name] || ICONS.help;
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color}
      strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round"
      className={className} style={style}>
      {paths}
    </svg>
  );
};

const ICONS = {
  home: <><path d="M3 10.5L12 3l9 7.5"/><path d="M5 9.5V20a1 1 0 0 0 1 1h4v-6h4v6h4a1 1 0 0 0 1-1V9.5"/></>,
  stethoscope: <><path d="M4.5 3v6a4 4 0 0 0 8 0V3"/><path d="M4.5 3h2"/><path d="M10.5 3h2"/><path d="M8.5 13v3a4 4 0 0 0 4 4 4 4 0 0 0 4-4v-1"/><circle cx="17.5" cy="11" r="1.5"/></>,
  microscope: <><path d="M6 18h12"/><path d="M9 18v-3"/><path d="M15 15v3"/><path d="M9 15h6a3 3 0 0 0 0-6V6a3 3 0 0 0-6 0v3a3 3 0 0 0 0 6Z"/></>,
  briefcase: <><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M3 13h18"/></>,
  user: <><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></>,
  bell: <><path d="M6 8a6 6 0 0 1 12 0c0 7 3 7 3 9H3c0-2 3-2 3-9Z"/><path d="M10 21a2 2 0 0 0 4 0"/></>,
  search: <><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></>,
  filter: <><path d="M3 5h18"/><path d="M6 12h12"/><path d="M10 19h4"/></>,
  plus: <><path d="M12 5v14M5 12h14"/></>,
  arrowRight: <><path d="M5 12h14M13 5l7 7-7 7"/></>,
  arrowLeft: <><path d="M19 12H5M11 5l-7 7 7 7"/></>,
  chevronRight: <><path d="m9 6 6 6-6 6"/></>,
  chevronDown: <><path d="m6 9 6 6 6-6"/></>,
  chevronUp: <><path d="m18 15-6-6-6 6"/></>,
  close: <><path d="m18 6-12 12M6 6l12 12"/></>,
  check: <><path d="M5 12l5 5 9-11"/></>,
  more: <><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></>,
  send: <><path d="m22 2-7 20-4-9-9-4 20-7Z"/></>,
  paperclip: <><path d="m21 11-9.5 9.5a5 5 0 0 1-7-7L13 5a3 3 0 0 1 4 4L8 18a1 1 0 0 1-1.5-1.5l8-8"/></>,
  mic: <><rect x="9" y="3" width="6" height="12" rx="3"/><path d="M5 11a7 7 0 0 0 14 0"/><path d="M12 18v3"/></>,
  image: <><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-5-5L5 21"/></>,
  warning: <><path d="M12 3 2 21h20L12 3Z"/><path d="M12 10v5"/><circle cx="12" cy="18" r="0.8" fill="currentColor"/></>,
  info: <><circle cx="12" cy="12" r="9"/><path d="M12 11v5"/><circle cx="12" cy="8" r="0.8" fill="currentColor"/></>,
  checkCircle: <><circle cx="12" cy="12" r="9"/><path d="m8 12 3 3 5-6"/></>,
  star: <><path d="m12 3 2.7 6 6.3.5-4.8 4.3 1.5 6.3L12 17l-5.7 3 1.5-6.3L3 9.5 9.3 9 12 3Z"/></>,
  trophy: <><path d="M8 21h8M12 17v4M7 4h10v5a5 5 0 0 1-10 0V4Z"/><path d="M17 5h2a2 2 0 0 1 0 4h-2M7 5H5a2 2 0 0 0 0 4h2"/></>,
  award: <><circle cx="12" cy="9" r="6"/><path d="m9 13-2 8 5-3 5 3-2-8"/></>,
  brain: <><path d="M9 4a3 3 0 0 0-3 3 2.5 2.5 0 0 0-2 4.5A2.5 2.5 0 0 0 4 16a3 3 0 0 0 5 2v-1"/><path d="M9 4a3 3 0 0 1 3 3v13"/><path d="M15 4a3 3 0 0 1 3 3 2.5 2.5 0 0 1 2 4.5 2.5 2.5 0 0 1 0 4.5 3 3 0 0 1-5 2"/></>,
  flask: <><path d="M9 3h6"/><path d="M10 3v7L4.5 19a1.5 1.5 0 0 0 1.3 2.3h12.4a1.5 1.5 0 0 0 1.3-2.3L14 10V3"/><path d="M7.5 14h9"/></>,
  fileText: <><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8l-5-5Z"/><path d="M14 3v5h5"/><path d="M9 13h6M9 17h4"/></>,
  chart: <><path d="M3 3v18h18"/><path d="M7 14l4-4 3 3 5-7"/></>,
  radar: <><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1.2" fill="currentColor"/><path d="M12 3v18M3 12h18"/></>,
  users: <><circle cx="9" cy="8" r="4"/><path d="M2 21a7 7 0 0 1 14 0"/><path d="M16 4a4 4 0 0 1 0 8"/><path d="M22 21a6 6 0 0 0-4-5.7"/></>,
  message: <><path d="M21 12a8 8 0 0 1-12 7l-5 1 1-5a8 8 0 1 1 16-3Z"/></>,
  hash: <><path d="M5 9h14M5 15h14M10 3 8 21M16 3l-2 18"/></>,
  flag: <><path d="M5 21V4h12l-2 4 2 4H5"/></>,
  building: <><rect x="4" y="3" width="16" height="18" rx="1.5"/><path d="M9 7h2M13 7h2M9 11h2M13 11h2M9 15h2M13 15h2M10 21v-3h4v3"/></>,
  mapPin: <><path d="M12 21s7-7 7-12a7 7 0 0 0-14 0c0 5 7 12 7 12Z"/><circle cx="12" cy="9" r="2.5"/></>,
  calendar: <><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/></>,
  bookmark: <><path d="M19 21V5a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v16l7-4 7 4Z"/></>,
  settings: <><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1Z"/></>,
  edit: <><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 1 1 3 3L7 19l-4 1 1-4Z"/></>,
  upload: <><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m17 8-5-5-5 5"/><path d="M12 3v12"/></>,
  share: <><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="m8.6 13.5 6.8 4M15.4 6.5l-6.8 4"/></>,
  thumbsUp: <><path d="M7 10h-3v10h3"/><path d="M7 10v10h10a2 2 0 0 0 2-1.7l1.5-7A1.5 1.5 0 0 0 19 9.4h-5l1-4.5a2 2 0 0 0-2-2.4L9 9v1"/></>,
  bot: <><rect x="3" y="8" width="18" height="12" rx="3"/><path d="M12 4v4"/><circle cx="12" cy="3" r="1"/><circle cx="9" cy="14" r="1" fill="currentColor"/><circle cx="15" cy="14" r="1" fill="currentColor"/></>,
  sparkle: <><path d="M12 3v3M12 18v3M3 12h3M18 12h3"/><path d="m6 6 2 2M16 16l2 2M6 18l2-2M16 8l2-2"/><circle cx="12" cy="12" r="3"/></>,
  zap: <><path d="M13 2 4 14h7l-1 8 9-12h-7l1-8Z"/></>,
  tooth: <><path d="M12 2c-3 0-5 2-7 2-2 0-3 2-3 5 0 4 2 5 3 8 1 2 1 5 3 5 1 0 2-3 4-3s3 3 4 3c2 0 2-3 3-5 1-3 3-4 3-8 0-3-1-5-3-5-2 0-4-2-7-2Z"/></>,
  play: <><path d="M6 4v16l14-8L6 4Z"/></>,
  refresh: <><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/><path d="M3 21v-5h5"/></>,
  lightbulb: <><path d="M9 18h6"/><path d="M10 21h4"/><path d="M12 3a6 6 0 0 0-4 10.5c1 1 1.5 2 1.5 3.5h5c0-1.5.5-2.5 1.5-3.5A6 6 0 0 0 12 3Z"/></>,
  clipboard: <><rect x="6" y="4" width="12" height="17" rx="2"/><path d="M9 4V3a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v1"/><path d="M9 11h6M9 15h4"/></>,
  help: <><circle cx="12" cy="12" r="9"/><path d="M9.5 9.5a2.5 2.5 0 1 1 3.5 2.3c-.7.3-1 .8-1 1.7"/><circle cx="12" cy="17" r="0.8" fill="currentColor"/></>,
  layers: <><path d="m12 2 10 5-10 5L2 7l10-5Z"/><path d="m2 12 10 5 10-5"/><path d="m2 17 10 5 10-5"/></>,
  database: <><ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v14a8 3 0 0 0 16 0V5"/><path d="M4 12a8 3 0 0 0 16 0"/></>,
  trending: <><path d="m3 17 6-6 4 4 8-8"/><path d="M14 7h7v7"/></>,
  globe: <><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></>,
  eye: <><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/></>,
  clock: <><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></>,
  bookOpen: <><path d="M2 5h7a3 3 0 0 1 3 3v13a2 2 0 0 0-2-2H2V5Z"/><path d="M22 5h-7a3 3 0 0 0-3 3v13a2 2 0 0 1 2-2h8V5Z"/></>,
  shield: <><path d="M12 2 4 5v6c0 5 4 9 8 11 4-2 8-6 8-11V5l-8-3Z"/></>,
  list: <><path d="M8 6h13M8 12h13M8 18h13"/><circle cx="3.5" cy="6" r="0.8" fill="currentColor"/><circle cx="3.5" cy="12" r="0.8" fill="currentColor"/><circle cx="3.5" cy="18" r="0.8" fill="currentColor"/></>,
  fingerprint: <><path d="M12 11a3 3 0 0 1 6 0c0 4-2 6-2 9"/><path d="M9 5.6A6 6 0 0 1 18 11c0 6-2 8-2 10"/><path d="M12 15v4"/><path d="M5 13c0-4 3-7 7-7"/></>,
  graduationCap: <><path d="M2 9 12 4l10 5-10 5L2 9Z"/><path d="M6 11v5c0 2 3 3 6 3s6-1 6-3v-5"/></>,
  arrowUpRight: <><path d="M7 17 17 7M7 7h10v10"/></>,
  pulse: <><path d="M3 12h4l3-8 4 16 3-8h4"/></>,
};

const Avatar = ({ name = '?', size = 32, color, style }) => {
  const initials = name.slice(0, 1);
  const palette = [
    'linear-gradient(135deg, #87b6dc, #2f76b3)',
    'linear-gradient(135deg, #79c8bd, #2ea597)',
    'linear-gradient(135deg, #efbb6e, #d68a2b)',
    'linear-gradient(135deg, #b9d5ec, #5494c8)',
    'linear-gradient(135deg, #d2ede9, #79c8bd)',
  ];
  const idx = (name.charCodeAt(0) || 0) % palette.length;
  return (
    <div className="mh-avatar" style={{
      width: size, height: size, fontSize: size * 0.4,
      background: color || palette[idx], ...style,
    }}>{initials}</div>
  );
};

const Stars = ({ value = 3, max = 5 }) => (
  <span className="mh-stars">
    {Array.from({ length: max }).map((_, i) => (
      <span key={i} className={i < value ? '' : 'dim'}>★</span>
    ))}
  </span>
);

const MobileShell = ({ children, statusBg = 'transparent', statusColor }) => (
  <div className="mh-device-mobile">
    <div className="mh-statusbar" style={{ background: statusBg, color: statusColor }}>
      <span>9:41</span>
      <span style={{ display: 'flex', gap: 4, alignItems: 'center', fontSize: 11 }}>
        <svg width="16" height="10" viewBox="0 0 16 10" fill="none"><path d="M1 6h2v3H1zM5 4h2v5H5zM9 2h2v7H9zM13 0h2v9h-2z" fill="currentColor"/></svg>
        <svg width="22" height="10" viewBox="0 0 22 10" fill="none"><rect x="0.5" y="0.5" width="18" height="9" rx="2" stroke="currentColor"/><rect x="2" y="2" width="14" height="6" rx="1" fill="currentColor"/><rect x="19" y="3.5" width="1.5" height="3" rx="0.5" fill="currentColor"/></svg>
      </span>
    </div>
    <div style={{ height: 'calc(100% - 36px)', position: 'relative', overflow: 'hidden' }}>
      {children}
    </div>
  </div>
);

const MobileTabBar = ({ active = 'home', onChange = () => {} }) => {
  const tabs = [
    { id: 'home', label: '首页', icon: 'home' },
    { id: 'training', label: '临床训练', icon: 'stethoscope' },
    { id: 'research', label: '科研', icon: 'microscope' },
    { id: 'career', label: '就业', icon: 'briefcase' },
    { id: 'profile', label: '我的', icon: 'user' },
  ];
  return (
    <div style={{
      position: 'absolute', bottom: 0, left: 0, right: 0,
      display: 'flex', height: 64,
      background: 'rgba(255,255,255,0.92)', backdropFilter: 'blur(12px)',
      borderTop: '1px solid var(--border)', paddingBottom: 6, zIndex: 50,
    }}>
      {tabs.map(t => {
        const isActive = active === t.id;
        return (
          <button key={t.id} onClick={() => onChange(t.id)} style={{
            flex: 1, display: 'flex', flexDirection: 'column',
            alignItems: 'center', justifyContent: 'center', gap: 3, padding: '6px 0',
            border: 'none', background: 'transparent',
            color: isActive ? 'var(--blue-600)' : 'var(--ink-500)',
            cursor: 'pointer', fontFamily: 'inherit',
          }}>
            <Icon name={t.icon} size={22} strokeWidth={isActive ? 2 : 1.6} />
            <span style={{ fontSize: 10, fontWeight: isActive ? 600 : 400 }}>{t.label}</span>
          </button>
        );
      })}
    </div>
  );
};

const MobileTopBar = ({ title, left, right, subtitle, transparent }) => (
  <div style={{
    height: 52, padding: '0 16px',
    display: 'flex', alignItems: 'center', gap: 8,
    background: transparent ? 'transparent' : 'var(--white)',
    borderBottom: transparent ? 'none' : '1px solid var(--border)',
    position: 'relative', zIndex: 5,
  }}>
    <div style={{ width: 32, display: 'flex', alignItems: 'center' }}>{left}</div>
    <div style={{ flex: 1, textAlign: 'center' }}>
      <div style={{ fontSize: 16, fontWeight: 600 }}>{title}</div>
      {subtitle && <div style={{ fontSize: 11, color: 'var(--ink-500)', marginTop: 1 }}>{subtitle}</div>}
    </div>
    <div style={{ width: 32, display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}>{right}</div>
  </div>
);

const DesktopShell = ({ active = 'home', onNav = () => {}, children, user }) => {
  const navs = [
    { id: 'home', label: '首页', icon: 'home' },
    { type: 'group', label: '临床训练' },
    { id: 'training', label: '病例训练场', icon: 'stethoscope' },
    { id: 'lab', label: '实验室操作', icon: 'flask' },
    { type: 'group', label: '科研赋能' },
    { id: 'research', label: '师生对接', icon: 'users' },
    { id: 'community', label: '热点社区', icon: 'hash' },
    { id: 'competition', label: '竞赛助手', icon: 'trophy' },
    { type: 'group', label: '就业深造' },
    { id: 'career', label: '职业规划', icon: 'fingerprint' },
    { id: 'jobs', label: '招聘广场', icon: 'building' },
    { id: 'evaluation', label: '能力认证', icon: 'shield' },
    { type: 'group', label: '个人' },
    { id: 'profile', label: '个人中心', icon: 'user' },
  ];
  return (
    <div className="mh-device-desktop" style={{
      display: 'flex', width: '100%', height: '100%',
      borderRadius: 0, border: 'none', boxShadow: 'none',
    }}>
      <aside style={{
        width: 232, flexShrink: 0,
        background: 'var(--white)', borderRight: '1px solid var(--border)',
        display: 'flex', flexDirection: 'column',
      }}>
        <div style={{ padding: '20px 20px 16px', display: 'flex', alignItems: 'center', gap: 10 }}>
          <div style={{
            width: 32, height: 32, borderRadius: 8,
            background: 'linear-gradient(135deg, var(--blue-500), var(--teal-500))',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            color: 'white', fontWeight: 800, fontSize: 14,
          }}>M+</div>
          <div>
            <div style={{ fontSize: 15, fontWeight: 700, letterSpacing: '-0.01em' }}>MedHub</div>
            <div style={{ fontSize: 10, color: 'var(--ink-500)', marginTop: -1 }}>医应俱全</div>
          </div>
        </div>
        <nav className="mh-scroll" style={{ flex: 1, padding: '4px 12px 12px', overflowY: 'auto' }}>
          {navs.map((n, i) => {
            if (n.type === 'group') {
              return <div key={i} style={{
                fontSize: 10, fontWeight: 600, color: 'var(--ink-400)',
                textTransform: 'uppercase', letterSpacing: '0.08em',
                padding: '14px 10px 6px',
              }}>{n.label}</div>;
            }
            const isActive = active === n.id;
            return (
              <button key={n.id} onClick={() => onNav(n.id)} style={{
                width: '100%', display: 'flex', alignItems: 'center', gap: 10,
                padding: '8px 10px', borderRadius: 8, border: 'none',
                background: isActive ? 'var(--blue-50)' : 'transparent',
                color: isActive ? 'var(--blue-700)' : 'var(--ink-700)',
                fontSize: 13, fontWeight: isActive ? 600 : 500,
                cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left',
                transition: 'all 0.12s',
              }}>
                <Icon name={n.icon} size={17} strokeWidth={isActive ? 2 : 1.6} />
                <span>{n.label}</span>
              </button>
            );
          })}
        </nav>
        <div style={{
          padding: 12, borderTop: '1px solid var(--border)',
          display: 'flex', alignItems: 'center', gap: 10,
        }}>
          <Avatar name={user?.name || '林'} size={34} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 13, fontWeight: 600 }}>{user?.name || '林思远'}</div>
            <div style={{ fontSize: 11, color: 'var(--ink-500)' }}>{user?.dept || '口腔医学 · 大三'}</div>
          </div>
        </div>
      </aside>
      <main style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
        {children}
      </main>
    </div>
  );
};

const DesktopTopBar = ({ title, breadcrumb, right }) => (
  <header style={{
    height: 56, padding: '0 24px',
    display: 'flex', alignItems: 'center', gap: 16,
    background: 'var(--white)', borderBottom: '1px solid var(--border)',
    flexShrink: 0,
  }}>
    <div style={{ flex: 1, minWidth: 0 }}>
      {breadcrumb && <div style={{ fontSize: 11, color: 'var(--ink-500)', marginBottom: 1 }}>{breadcrumb}</div>}
      <div style={{ fontSize: 16, fontWeight: 600 }}>{title}</div>
    </div>
    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
      {right}
      <button className="mh-btn mh-btn-ghost" style={{ padding: 7, position: 'relative' }}>
        <Icon name="bell" size={18} />
        <span style={{
          position: 'absolute', top: 6, right: 6,
          width: 6, height: 6, borderRadius: '50%',
          background: 'var(--red-500)', border: '1.5px solid white',
        }}/>
      </button>
    </div>
  </header>
);

const RadarChart = ({ values = [80, 65, 72, 88, 70, 75], labels = ['临床思维', '操作技能', '科研能力', '基础理论', '医患沟通', '循证素养'], size = 240, max = 100, color = 'var(--blue-500)' }) => {
  const cx = size / 2, cy = size / 2;
  const radius = size * 0.35;
  const sides = values.length;
  const angle = (i) => (Math.PI * 2 * i) / sides - Math.PI / 2;
  const point = (i, v) => {
    const r = (v / max) * radius;
    return [cx + r * Math.cos(angle(i)), cy + r * Math.sin(angle(i))];
  };
  const polyPoints = values.map((v, i) => point(i, v).join(',')).join(' ');
  const rings = [0.25, 0.5, 0.75, 1];
  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
      {rings.map((r, ri) => {
        const pts = values.map((_, i) => {
          const x = cx + radius * r * Math.cos(angle(i));
          const y = cy + radius * r * Math.sin(angle(i));
          return `${x},${y}`;
        }).join(' ');
        return <polygon key={ri} points={pts} fill="none" stroke="var(--ink-200)" strokeWidth="1"/>;
      })}
      {values.map((_, i) => {
        const [x, y] = point(i, max);
        return <line key={i} x1={cx} y1={cy} x2={x} y2={y} stroke="var(--ink-200)" strokeWidth="1"/>;
      })}
      <polygon points={polyPoints} fill={color} fillOpacity="0.18" stroke={color} strokeWidth="1.7"/>
      {values.map((v, i) => {
        const [x, y] = point(i, v);
        return <circle key={i} cx={x} cy={y} r="3.5" fill={color} stroke="white" strokeWidth="1.5"/>;
      })}
      {labels.map((l, i) => {
        const [x, y] = point(i, max * 1.22);
        return <text key={i} x={x} y={y} textAnchor="middle" dominantBaseline="middle"
          fontSize="10.5" fill="var(--ink-700)" fontWeight="500">{l}</text>;
      })}
    </svg>
  );
};

const ImagePlaceholder = ({ width = '100%', height = 120, label = '影像', tone = 'blue', icon = 'image' }) => {
  const tones = {
    blue: { bg: 'var(--blue-50)', stripe: 'var(--blue-100)', text: 'var(--blue-700)' },
    teal: { bg: 'var(--teal-50)', stripe: 'var(--teal-100)', text: 'var(--teal-700)' },
    amber: { bg: 'var(--amber-50)', stripe: 'var(--amber-100)', text: 'var(--amber-700)' },
    ink: { bg: 'var(--ink-100)', stripe: 'var(--ink-150)', text: 'var(--ink-600)' },
    dark: { bg: '#0f1722', stripe: '#1c2735', text: '#94a0ad' },
  };
  const t = tones[tone];
  return (
    <div style={{
      width, height, borderRadius: 'var(--radius-md)',
      background: `repeating-linear-gradient(45deg, ${t.bg}, ${t.bg} 8px, ${t.stripe} 8px, ${t.stripe} 16px)`,
      display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
      color: t.text, fontSize: 11, gap: 6,
    }}>
      <Icon name={icon} size={22} color={t.text} strokeWidth={1.5}/>
      <span style={{ fontWeight: 500 }}>{label}</span>
    </div>
  );
};

const AgentDot = ({ status = 'idle' }) => {
  const colors = {
    active: 'var(--teal-500)', pending: 'var(--amber-500)',
    done: 'var(--teal-500)', idle: 'var(--ink-300)', error: 'var(--red-500)',
  };
  return (
    <span style={{
      display: 'inline-block', width: 6, height: 6, borderRadius: '50%',
      background: colors[status], flexShrink: 0,
      boxShadow: status === 'active' ? `0 0 0 3px ${colors[status]}33` : 'none',
      animation: status === 'active' ? 'mhPulse 1.4s infinite' : 'none',
    }}/>
  );
};

if (typeof document !== 'undefined' && !document.getElementById('mh-anims')) {
  const s = document.createElement('style');
  s.id = 'mh-anims';
  s.textContent = `
    @keyframes mhPulse { 0%,100%{opacity:1} 50%{opacity:0.45} }
    @keyframes mhFadeIn { from{opacity:0;transform:translateY(4px)} to{opacity:1;transform:translateY(0)} }
    .mh-fade-in { animation: mhFadeIn 0.3s ease-out; }
  `;
  document.head.appendChild(s);
}

const SectionTitle = ({ children, action }) => (
  <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 12 }}>
    <h3 style={{ margin: 0, fontSize: 15, fontWeight: 600, color: 'var(--ink-900)' }}>{children}</h3>
    {action}
  </div>
);

// AI 小精灵：极简抽象图标
const AIPixie = ({ size = 32, state = 'idle', onClick, style }) => {
  // 抽象：圆角方形+脑波弧，stroke based
  return (
    <button onClick={onClick} style={{
      width: size, height: size, border: 'none', cursor: 'pointer', padding: 0,
      borderRadius: size * 0.28,
      background: 'linear-gradient(135deg, #2f76b3 0%, #2ea597 100%)',
      boxShadow: state === 'active'
        ? '0 0 0 4px rgba(46,165,151,0.22), 0 6px 16px rgba(47,118,179,0.3)'
        : '0 4px 12px rgba(47,118,179,0.25)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      position: 'relative', transition: 'all 0.18s',
      ...style,
    }}>
      <svg width={size * 0.55} height={size * 0.55} viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
        <path d="M5 13c0-3 2-5 4-5"/>
        <path d="M19 13c0-3-2-5-4-5"/>
        <path d="M9 8a3 3 0 0 1 6 0"/>
        <circle cx="12" cy="14" r="4"/>
        <circle cx="10.5" cy="13.5" r="0.6" fill="white"/>
        <circle cx="13.5" cy="13.5" r="0.6" fill="white"/>
        <path d="M12 18v2"/>
      </svg>
      {state === 'active' && (
        <span style={{
          position: 'absolute', top: -2, right: -2,
          width: 8, height: 8, borderRadius: '50%',
          background: '#79c8bd', border: '2px solid white',
        }}/>
      )}
    </button>
  );
};

Object.assign(window, {
  Icon, ICONS, Avatar, Stars,
  MobileShell, MobileTabBar, MobileTopBar,
  DesktopShell, DesktopTopBar,
  RadarChart, ImagePlaceholder, AgentDot, SectionTitle, AIPixie,
});
