Sfoglia il codice sorgente

feat: init project

王晓东 6 giorni fa
commit
6afb9635dc
63 ha cambiato i file con 3920 aggiunte e 0 eliminazioni
  1. 7 0
      .gitignore
  2. 44 0
      App.tsx
  3. 118 0
      CloneChatPage.tsx
  4. 54 0
      ContactPage.tsx
  5. 368 0
      EditPage.tsx
  6. 35 0
      LandingPage.tsx
  7. 20 0
      README.md
  8. 79 0
      components/AuthenticityScore.tsx
  9. 18 0
      components/Bio.tsx
  10. 38 0
      components/Certifications.tsx
  11. 71 0
      components/SocialLinks.tsx
  12. 28 0
      components/VideoIntroduction.tsx
  13. 46 0
      components/VirtualIdCard.tsx
  14. 9 0
      components/icons/ArrowRightIcon.tsx
  15. 27 0
      components/icons/BrainCircuitIcon.tsx
  16. 14 0
      components/icons/CertificateIcon.tsx
  17. 8 0
      components/icons/DiscordIcon.tsx
  18. 9 0
      components/icons/FacebookIcon.tsx
  19. 9 0
      components/icons/GithubIcon.tsx
  20. 9 0
      components/icons/InstagramIcon.tsx
  21. 9 0
      components/icons/LinkedInIcon.tsx
  22. 21 0
      components/icons/ShieldIcon.tsx
  23. 16 0
      components/icons/TiktokIcon.tsx
  24. 9 0
      components/icons/UploadIcon.tsx
  25. 9 0
      components/icons/VerifiedIcon.tsx
  26. 9 0
      components/icons/XIcon.tsx
  27. 59 0
      index.html
  28. 18 0
      index.tsx
  29. 5 0
      metadata.json
  30. 63 0
      oauth-callback.html
  31. 23 0
      package.json
  32. 1423 0
      pnpm-lock.yaml
  33. 25 0
      routes.tsx
  34. 44 0
      src/App.tsx
  35. 79 0
      src/components/AuthenticityScore.tsx
  36. 46 0
      src/components/VirtualIdCard.tsx
  37. 9 0
      src/components/icons/ArrowRightIcon.tsx
  38. 27 0
      src/components/icons/BrainCircuitIcon.tsx
  39. 14 0
      src/components/icons/CertificateIcon.tsx
  40. 8 0
      src/components/icons/DiscordIcon.tsx
  41. 9 0
      src/components/icons/FacebookIcon.tsx
  42. 9 0
      src/components/icons/GithubIcon.tsx
  43. 9 0
      src/components/icons/InstagramIcon.tsx
  44. 9 0
      src/components/icons/LinkedInIcon.tsx
  45. 21 0
      src/components/icons/ShieldIcon.tsx
  46. 16 0
      src/components/icons/TiktokIcon.tsx
  47. 9 0
      src/components/icons/UploadIcon.tsx
  48. 9 0
      src/components/icons/VerifiedIcon.tsx
  49. 9 0
      src/components/icons/XIcon.tsx
  50. 18 0
      src/index.tsx
  51. 118 0
      src/pages/CloneChat/CloneChatPage.tsx
  52. 54 0
      src/pages/Contact/ContactPage.tsx
  53. 18 0
      src/pages/Contact/components/Bio.tsx
  54. 38 0
      src/pages/Contact/components/Certifications.tsx
  55. 71 0
      src/pages/Contact/components/SocialLinks.tsx
  56. 28 0
      src/pages/Contact/components/VideoIntroduction.tsx
  57. 368 0
      src/pages/Edit/EditPage.tsx
  58. 35 0
      src/pages/Landing/LandingPage.tsx
  59. 25 0
      src/routes.tsx
  60. 34 0
      src/types.ts
  61. 29 0
      tsconfig.json
  62. 34 0
      types.ts
  63. 23 0
      vite.config.ts

+ 7 - 0
.gitignore

@@ -0,0 +1,7 @@
+node_modules
+.DS_Store
+dist*
+dist-ssr
+*.local
+.eslintcache
+.stylelintcache

+ 44 - 0
App.tsx

@@ -0,0 +1,44 @@
+import React, { useState } from 'react';
+import AppRoutes from './routes';
+import type { ContactData } from './types';
+
+const App: React.FC = () => {
+  // Mock data for the contact, now managed by state
+  const [contactData, setContactData] = useState<ContactData>({
+    name: 'Alex Doe',
+    avatarUrl: 'https://picsum.photos/200',
+    virtualId: 'VID-2024-8A4B-C7D2-F1E0',
+    bio: 'Senior Frontend Engineer with a passion for creating beautiful and functional user interfaces. Expert in React, TypeScript, and modern web technologies. In my free time, I explore the intersection of AI and art.',
+    socials: {
+      linkedin: 'https://www.linkedin.com/in/alex-doe',
+      x: 'https://x.com/alex_doe',
+      github: 'https://github.com/alexdoe',
+      tiktok: 'https://www.tiktok.com/@alexdoe.dev',
+      instagram: 'https://www.instagram.com/alex.doe',
+      facebook: 'https://www.facebook.com/alex.doe',
+      discord: 'alex.doe#1234',
+    },
+    authenticityScore: {
+      videoVerification: 15, // max 15
+      socialBinding: 45, // max 45
+      cloneMaturity: 48, // max 60
+    },
+    videoIntroUrl: 'https://storage.googleapis.com/web-dev-assets/video-introduction-demo.mp4',
+    videoPosterUrl: 'https://picsum.photos/seed/video-poster/800/450',
+  });
+
+  const handleSave = (newData: ContactData) => {
+    setContactData(newData);
+  };
+
+  return (
+    <div className="min-h-screen bg-slate-50 font-sans text-slate-800">
+      <AppRoutes contactData={contactData} onSave={handleSave} />
+      <footer className="text-center p-4 text-slate-500 text-sm">
+        <p>&copy; 2024 Virtual Identity System. All rights reserved.</p>
+      </footer>
+    </div>
+  );
+};
+
+export default App;

+ 118 - 0
CloneChatPage.tsx

@@ -0,0 +1,118 @@
+import React, { useState, useRef, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+
+interface Message {
+  id: number;
+  text: string;
+  sender: 'user' | 'clone';
+}
+
+const CloneChatPage: React.FC = () => {
+  const navigate = useNavigate();
+  const [messages, setMessages] = useState<Message[]>([
+    { id: 1, text: "Hello! You can start training me by talking to me. What's on your mind?", sender: 'clone' }
+  ]);
+  const [inputValue, setInputValue] = useState('');
+  const [isTyping, setIsTyping] = useState(false);
+  const messagesEndRef = useRef<HTMLDivElement>(null);
+
+  const scrollToBottom = () => {
+    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+  };
+
+  useEffect(scrollToBottom, [messages, isTyping]);
+
+  const handleSendMessage = (e: React.FormEvent) => {
+    e.preventDefault();
+    if (inputValue.trim() === '') return;
+
+    const userMessage: Message = {
+      id: Date.now(),
+      text: inputValue,
+      sender: 'user',
+    };
+
+    setMessages(prev => [...prev, userMessage]);
+    setInputValue('');
+    setIsTyping(true);
+
+    // Simulate clone response
+    setTimeout(() => {
+      const cloneResponse: Message = {
+        id: Date.now() + 1,
+        text: `That's an interesting point. It reminds me of how you once said... (simulated response based on your persona).`,
+        sender: 'clone',
+      };
+      setIsTyping(false);
+      setMessages(prev => [...prev, cloneResponse]);
+    }, 1500 + Math.random() * 1000);
+  };
+
+  return (
+    <div className="flex flex-col h-[calc(100vh-60px)] max-w-3xl mx-auto bg-white rounded-2xl overflow-hidden border border-slate-200">
+        {/* Header */}
+        <header className="flex items-center justify-between p-4 border-b border-slate-200 bg-slate-50 flex-shrink-0">
+            <h1 className="text-xl font-bold text-slate-800">Train My Digital Clone</h1>
+            <button
+                onClick={() => navigate('/edit')}
+                className="px-4 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-300 rounded-md hover:bg-slate-100"
+            >
+                Back to Edit
+            </button>
+        </header>
+
+        {/* Chat Area */}
+        <main className="flex-1 overflow-y-auto p-6 space-y-4">
+            {messages.map(msg => (
+                <div key={msg.id} className={`flex items-end gap-2 ${msg.sender === 'user' ? 'justify-end' : 'justify-start'}`}>
+                    {msg.sender === 'clone' && (
+                        <div className="w-8 h-8 rounded-full bg-gradient-to-br from-indigo-500 to-purple-600 flex-shrink-0"></div>
+                    )}
+                    <div className={`max-w-xs md:max-w-md lg:max-w-lg px-4 py-2 rounded-2xl ${msg.sender === 'user' ? 'bg-blue-600 text-white rounded-br-none' : 'bg-slate-200 text-slate-800 rounded-bl-none'}`}>
+                       <p className="text-sm leading-relaxed">{msg.text}</p>
+                    </div>
+                </div>
+            ))}
+             {isTyping && (
+                <div className="flex items-end gap-2 justify-start">
+                    <div className="w-8 h-8 rounded-full bg-gradient-to-br from-indigo-500 to-purple-600 flex-shrink-0"></div>
+                    <div className="px-4 py-2 rounded-2xl bg-slate-200 rounded-bl-none">
+                        <div className="flex items-center justify-center space-x-1">
+                            <span className="w-1.5 h-1.5 bg-slate-500 rounded-full animate-bounce [animation-delay:-0.3s]"></span>
+                            <span className="w-1.5 h-1.5 bg-slate-500 rounded-full animate-bounce [animation-delay:-0.15s]"></span>
+                            <span className="w-1.5 h-1.5 bg-slate-500 rounded-full animate-bounce"></span>
+                        </div>
+                    </div>
+                </div>
+            )}
+            <div ref={messagesEndRef} />
+        </main>
+
+        {/* Input Form */}
+        <footer className="p-4 border-t border-slate-200 bg-slate-50 flex-shrink-0">
+            <form onSubmit={handleSendMessage} className="flex items-center gap-3">
+                <input
+                    type="text"
+                    value={inputValue}
+                    onChange={e => setInputValue(e.target.value)}
+                    placeholder="Talk to your clone..."
+                    className="flex-1 w-full px-4 py-2 bg-white border border-slate-300 rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500"
+                    autoComplete="off"
+                />
+                <button
+                    type="submit"
+                    aria-label="Send message"
+                    className="w-10 h-10 flex-shrink-0 bg-blue-600 text-white rounded-full flex items-center justify-center hover:bg-blue-700 disabled:bg-blue-300 transition-colors"
+                    disabled={inputValue.trim() === ''}
+                >
+                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="w-5 h-5">
+                      <path d="M3.105 2.289a.75.75 0 00-.826.95l1.414 4.925A1.5 1.5 0 005.135 9.25h6.115a.75.75 0 010 1.5H5.135a1.5 1.5 0 00-1.442 1.086L2.279 16.76a.75.75 0 00.826.95l14.433-6.414a.75.75 0 000-1.392L3.105 2.289z" />
+                    </svg>
+                </button>
+            </form>
+        </footer>
+    </div>
+  );
+};
+
+export default CloneChatPage;

+ 54 - 0
ContactPage.tsx

@@ -0,0 +1,54 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import VirtualIdCard from './components/VirtualIdCard';
+import AuthenticityScore from './components/AuthenticityScore';
+import Bio from './components/Bio';
+import SocialLinks from './components/SocialLinks';
+import VideoIntroduction from './components/VideoIntroduction';
+import type { ContactData } from './types';
+
+interface ContactPageProps {
+  data: ContactData;
+}
+
+const ContactPage: React.FC<ContactPageProps> = ({ data }) => {
+  const totalScore = data.authenticityScore.videoVerification + data.authenticityScore.socialBinding + data.authenticityScore.cloneMaturity;
+
+  return (
+    <main className="max-w-3xl mx-auto p-4 sm:p-6 md:p-8">
+      <div className="space-y-8">
+        <div className="flex justify-between items-center mb-4">
+            <h1 className="text-2xl font-bold text-slate-800">Contact Profile</h1>
+            <div>
+                <Link to="/" className="text-sm text-slate-600 hover:text-blue-600 mr-4">Home</Link>
+                <Link to="/edit" className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700">Edit Page</Link>
+            </div>
+        </div>
+        <VirtualIdCard 
+          name={data.name}
+          avatarUrl={data.avatarUrl}
+          virtualId={data.virtualId}
+          totalScore={totalScore}
+        />
+        
+        <AuthenticityScore 
+          videoVerificationScore={data.authenticityScore.videoVerification}
+          socialBindingScore={data.authenticityScore.socialBinding}
+          cloneMaturityScore={data.authenticityScore.cloneMaturity}
+        />
+
+        <Bio text={data.bio} />
+
+        <VideoIntroduction
+          videoUrl={data.videoIntroUrl}
+          posterUrl={data.videoPosterUrl}
+        />
+
+        <SocialLinks links={data.socials} />
+
+      </div>
+    </main>
+  );
+};
+
+export default ContactPage;

+ 368 - 0
EditPage.tsx

@@ -0,0 +1,368 @@
+import React, { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import type { ContactData, SocialLinksData } from './types';
+import LinkedInIcon from './components/icons/LinkedInIcon';
+import XIcon from './components/icons/XIcon';
+import GithubIcon from './components/icons/GithubIcon';
+import TiktokIcon from './components/icons/TiktokIcon';
+import InstagramIcon from './components/icons/InstagramIcon';
+import FacebookIcon from './components/icons/FacebookIcon';
+import DiscordIcon from './components/icons/DiscordIcon';
+import VerifiedIcon from './components/icons/VerifiedIcon';
+import VirtualIdCard from './components/VirtualIdCard';
+import UploadIcon from './components/icons/UploadIcon';
+import AuthenticityScore from './components/AuthenticityScore';
+import BrainCircuitIcon from './components/icons/BrainCircuitIcon';
+import ArrowRightIcon from './components/icons/ArrowRightIcon';
+
+interface EditPageProps {
+  initialData: ContactData;
+  onSave: (data: ContactData) => void;
+}
+
+// Reusable Section Component for editable fields
+const EditSection: React.FC<{title: string, children: React.ReactNode}> = ({ title, children }) => (
+    <div className="bg-white rounded-2xl border border-slate-200 p-6 space-y-4">
+        <h2 className="text-xl font-bold text-slate-800 mb-4">{title}</h2>
+        {children}
+    </div>
+);
+
+const socialPlatforms = [
+    { 
+        key: 'linkedin', name: 'LinkedIn', icon: <LinkedInIcon />,
+        brandClasses: {
+            iconBg: 'bg-gradient-to-br from-blue-500 to-sky-400',
+            button: 'bg-blue-600 hover:bg-blue-700',
+        }
+    },
+    { 
+        key: 'x', name: 'X (Twitter)', icon: <XIcon />,
+        brandClasses: {
+            iconBg: 'bg-gradient-to-br from-slate-900 to-slate-700',
+            button: 'bg-slate-800 hover:bg-slate-900',
+        }
+    },
+    { 
+        key: 'github', name: 'GitHub', icon: <GithubIcon />,
+        brandClasses: {
+            iconBg: 'bg-gradient-to-br from-slate-900 to-slate-700',
+            button: 'bg-slate-800 hover:bg-slate-900',
+        }
+    },
+    { 
+        key: 'tiktok', name: 'TikTok', icon: <TiktokIcon />,
+        brandClasses: {
+            iconBg: 'bg-gradient-to-br from-black via-rose-500 to-cyan-400',
+            button: 'bg-black hover:bg-gray-800',
+        }
+    },
+    { 
+        key: 'instagram', name: 'Instagram', icon: <InstagramIcon />,
+        brandClasses: {
+            iconBg: 'bg-gradient-to-br from-purple-500 via-pink-500 to-yellow-500',
+            button: 'bg-pink-600 hover:bg-pink-700',
+        }
+    },
+    { 
+        key: 'facebook', name: 'Facebook', icon: <FacebookIcon />,
+        brandClasses: {
+            iconBg: 'bg-gradient-to-br from-blue-700 to-blue-500',
+            button: 'bg-blue-700 hover:bg-blue-800',
+        }
+    },
+    { 
+        key: 'discord', name: 'Discord', icon: <DiscordIcon />,
+        brandClasses: {
+            iconBg: 'bg-gradient-to-br from-indigo-600 to-purple-500',
+            button: 'bg-indigo-600 hover:bg-indigo-700',
+        }
+    },
+] as const;
+
+
+const EditPage: React.FC<EditPageProps> = ({ initialData, onSave }) => {
+    const navigate = useNavigate();
+    const [formData, setFormData] = useState<ContactData>(initialData);
+
+    useEffect(() => {
+        const handleOauthMessage = (event: MessageEvent) => {
+            if (event.origin !== window.location.origin) {
+                return;
+            }
+
+            const { type, platform, profileUrl, error } = event.data;
+
+            if (type === 'oauth-success' && platform && profileUrl) {
+                setFormData(prev => ({
+                    ...prev,
+                    socials: {
+                        ...prev.socials,
+                        [platform]: profileUrl
+                    }
+                }));
+            } else if (type === 'oauth-error') {
+                console.error(`OAuth Error for ${platform}:`, error);
+                alert(`Failed to connect ${platform}. Please try again.`);
+            }
+        };
+        
+        window.addEventListener('message', handleOauthMessage);
+
+        return () => {
+            window.removeEventListener('message', handleOauthMessage);
+        };
+    }, []);
+
+    useEffect(() => {
+        // Recalculate authenticity score when dependencies change
+        const connectedSocials = Object.values(formData.socials).filter(link => !!link).length;
+        
+        let socialScore = 0;
+        if (connectedSocials === 1) {
+            socialScore = 15;
+        } else if (connectedSocials === 2) {
+            socialScore = 30;
+        } else if (connectedSocials > 2) {
+            socialScore = 30 + (connectedSocials - 2) * 5;
+        }
+        
+        const finalSocialScore = Math.min(socialScore, 45);
+
+        const videoScore = formData.videoIntroUrl ? 15 : 0;
+        
+        // Only update if the scores have changed to prevent infinite loops
+        if (
+            finalSocialScore !== formData.authenticityScore.socialBinding ||
+            videoScore !== formData.authenticityScore.videoVerification
+        ) {
+            setFormData(prev => ({
+                ...prev,
+                authenticityScore: {
+                    ...prev.authenticityScore,
+                    socialBinding: finalSocialScore,
+                    videoVerification: videoScore,
+                }
+            }));
+        }
+
+    }, [formData.socials, formData.videoIntroUrl, formData.authenticityScore]);
+
+    const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
+        const { name, value } = e.target;
+        setFormData(prev => ({
+            ...prev,
+            [name]: value,
+        }));
+    };
+
+    const handleVideoChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        const file = e.target.files?.[0];
+        if (file) {
+            const videoUrl = URL.createObjectURL(file);
+            setFormData(prev => ({
+                ...prev,
+                videoIntroUrl: videoUrl,
+                videoPosterUrl: '' // Reset poster for new video
+            }));
+        }
+    };
+
+    const handleSocialConnect = (platform: keyof SocialLinksData) => {
+        const isConnected = !!formData.socials[platform];
+
+        if (isConnected) {
+            // Disconnect logic
+            const newSocials = { ...formData.socials, [platform]: '' };
+            setFormData(prev => ({ ...prev, socials: newSocials }));
+            return;
+        }
+
+        // --- Real OAuth URLs ---
+        const redirectUri = `${window.location.origin}/oauth-callback.html`;
+        let oauthUrl = '';
+
+        switch (platform) {
+            case 'github':
+                oauthUrl = `https://github.com/login/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&scope=read:user&state=github`;
+                break;
+            case 'linkedin':
+                oauthUrl = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&state=linkedin&scope=profile%20email%20openid`;
+                break;
+            case 'x':
+                 oauthUrl = `https://twitter.com/i/oauth2/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&scope=users.read%20tweet.read&state=x&code_challenge=challenge&code_challenge_method=plain`;
+                break;
+            case 'tiktok':
+                oauthUrl = `https://www.tiktok.com/v2/auth/authorize?client_key=YOUR_CLIENT_KEY&scope=user.info.basic&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&state=tiktok`;
+                break;
+            case 'instagram':
+                oauthUrl = `https://api.instagram.com/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&scope=user_profile,user_media&response_type=code&state=instagram`;
+                break;
+            case 'facebook':
+                oauthUrl = `https://www.facebook.com/v19.0/dialog/oauth?client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&state=facebook&scope=public_profile,email`;
+                break;
+            case 'discord':
+                oauthUrl = `https://discord.com/api/oauth2/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=identify%20email&state=discord`;
+                break;
+        }
+
+        // --- Popup Window Logic ---
+        const width = 600;
+        const height = 700;
+        const left = (window.innerWidth - width) / 2;
+        const top = (window.innerHeight - height) / 2;
+
+        window.open(
+            oauthUrl,
+            'socialLogin',
+            `width=${width},height=${height},left=${left},top=${top},toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes`
+        );
+    };
+
+    const handleSubmit = (e: React.FormEvent) => {
+        e.preventDefault();
+        onSave(formData);
+        navigate('/contact');
+    };
+
+    const totalScore = formData.authenticityScore.videoVerification + formData.authenticityScore.socialBinding + formData.authenticityScore.cloneMaturity;
+
+    return (
+        <main className="max-w-3xl mx-auto p-4 sm:p-6 md:p-8">
+            <form onSubmit={handleSubmit} className="space-y-8">
+                <div className="flex justify-between items-center mb-4">
+                    <h1 className="text-2xl font-bold text-slate-800">Certify My Homepage</h1>
+                    <div>
+                        <button type="button" onClick={() => navigate('/contact')} className="px-4 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-300 rounded-md hover:bg-slate-50 mr-2">Cancel</button>
+                        <button type="submit" className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700">Save Changes</button>
+                    </div>
+                </div>
+
+                <VirtualIdCard
+                    name={formData.name}
+                    avatarUrl={formData.avatarUrl}
+                    virtualId={formData.virtualId}
+                    totalScore={totalScore}
+                />
+
+                <AuthenticityScore 
+                  videoVerificationScore={formData.authenticityScore.videoVerification}
+                  socialBindingScore={formData.authenticityScore.socialBinding}
+                  cloneMaturityScore={formData.authenticityScore.cloneMaturity}
+                />
+
+                <div className="bg-sky-50 border border-sky-200 text-sky-800 rounded-xl p-3 text-center text-sm -mt-4">
+                    <p>At your current score, you'll earn <span className="font-bold">100 tokens/minute</span> from calls.</p>
+                </div>
+
+                <div 
+                  onClick={() => navigate('/clone-chat')}
+                  className="bg-white rounded-2xl border border-slate-200 p-6 group cursor-pointer transition-colors duration-300 ease-in-out hover:bg-slate-50"
+                  role="button"
+                  aria-label="Train My Digital Clone"
+                >
+                  <div className="flex items-center gap-4">
+                      <div className="flex-shrink-0 w-12 h-12 rounded-2xl flex items-center justify-center bg-gradient-to-br from-indigo-500 to-purple-600 text-white group-hover:scale-110 transition-transform duration-300">
+                          <BrainCircuitIcon />
+                      </div>
+                      <div className="flex-grow">
+                          <h2 className="text-xl font-bold text-slate-800">Train My Digital Clone</h2>
+                          <p className="text-sm text-slate-600 mt-1">
+                              Improve your clone's quality score by continuously chatting with it.
+                          </p>
+                          <p className="text-sm text-indigo-600 font-medium mt-1">
+                              A higher score leads to greater call earnings.
+                          </p>
+                      </div>
+                      <div className="text-slate-400 group-hover:text-slate-800 transition-transform duration-300 group-hover:translate-x-1">
+                        <ArrowRightIcon className="w-5 h-5" />
+                      </div>
+                  </div>
+                </div>
+
+                <EditSection title="About Me">
+                    <label htmlFor="bio" className="sr-only">About Me</label>
+                    <textarea
+                        id="bio"
+                        name="bio"
+                        rows={5}
+                        className="w-full px-3 py-2 text-slate-700 bg-white border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition"
+                        placeholder="Tell us about yourself..."
+                        value={formData.bio}
+                        onChange={handleInputChange}
+                    />
+                </EditSection>
+
+                <EditSection title="Video Introduction">
+                    <label htmlFor="video-upload" className="block aspect-video w-full rounded-lg bg-slate-200 relative overflow-hidden group cursor-pointer">
+                        <video key={formData.videoIntroUrl} poster={formData.videoPosterUrl} src={formData.videoIntroUrl} className="w-full h-full object-cover" />
+                        
+                        <div className="absolute inset-0 bg-black/60 flex flex-col items-center justify-center text-white opacity-0 group-hover:opacity-100 transition-opacity duration-300">
+                            <UploadIcon />
+                            <span className="mt-2 block font-semibold">Upload New Video</span>
+                            <span className="text-xs text-slate-300">Click or drag & drop</span>
+                        </div>
+                         <input id="video-upload" type="file" onChange={handleVideoChange} accept="video/*" className="sr-only"/>
+                    </label>
+                </EditSection>
+                
+                <EditSection title="Verified Social Accounts">
+                    <p className="text-sm text-slate-600">Connect your social accounts to verify your identity. This enhances your Authenticity Score.</p>
+                    <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
+                        {socialPlatforms.map((platform) => {
+                            const isConnected = !!formData.socials[platform.key];
+                            
+                            if (!isConnected) {
+                                return (
+                                    <div key={platform.key} className="flex items-center p-4 rounded-xl border-2 border-dashed border-slate-200 bg-slate-50 transition-colors hover:border-slate-300 hover:bg-slate-100">
+                                        <div className="flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center text-slate-400 bg-slate-200">
+                                            {platform.icon}
+                                        </div>
+                                        <div className="ml-4 flex-grow">
+                                            <h3 className="font-bold text-slate-700 text-sm">{platform.name}</h3>
+                                            <p className="text-xs text-slate-500">Not Connected</p>
+                                        </div>
+                                        <button 
+                                            type="button" 
+                                            onClick={() => handleSocialConnect(platform.key)} 
+                                            className={`px-3 py-1.5 text-xs font-semibold text-white ${platform.brandClasses.button} rounded-md transition-transform transform hover:scale-105`}
+                                        >
+                                            Connect
+                                        </button>
+                                    </div>
+                                );
+                            }
+
+                            return (
+                                <div key={platform.key} className="flex items-center p-4 rounded-xl border border-slate-200 bg-white">
+                                  <div className={`flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center text-white ${platform.brandClasses.iconBg}`}>
+                                      {platform.icon}
+                                  </div>
+                                  <div className="ml-4 flex-grow overflow-hidden">
+                                      <div className="flex items-center">
+                                          <h3 className="font-bold text-slate-800 text-sm">{platform.name}</h3>
+                                          <div className="ml-2 flex items-center bg-green-100 text-green-800 text-xs font-medium px-2 py-0.5 rounded-full">
+                                            <VerifiedIcon className="w-3 h-3 mr-1" />
+                                            Verified
+                                          </div>
+                                      </div>
+                                      <p className="text-xs text-slate-500 font-mono truncate max-w-[120px] sm:max-w-xs">{formData.socials[platform.key].replace(/^https?:\/\/(www\.)?/, '')}</p>
+                                  </div>
+                                  <button 
+                                    type="button" 
+                                    onClick={() => handleSocialConnect(platform.key)} 
+                                    className="text-xs font-semibold text-red-600 hover:text-red-800 hover:bg-red-100 px-2 py-1 rounded-md transition-colors"
+                                  >
+                                    Disconnect
+                                  </button>
+                                </div>
+                            );
+                        })}
+                    </div>
+                </EditSection>
+            </form>
+        </main>
+    );
+};
+
+export default EditPage;

+ 35 - 0
LandingPage.tsx

@@ -0,0 +1,35 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+
+const LandingPage: React.FC = () => {
+  const navigate = useNavigate();
+
+  return (
+    <div className="flex flex-col items-center justify-center min-h-[calc(100vh-60px)]">
+      <div className="text-center p-8">
+        <h1 className="text-4xl sm:text-5xl font-extrabold text-slate-900 tracking-tight">
+          Virtual Identity System
+        </h1>
+        <p className="mt-4 max-w-xl mx-auto text-lg text-slate-600">
+          Your unified, verifiable online presence.
+        </p>
+        <div className="mt-8 flex flex-col sm:flex-row justify-center gap-4">
+          <button
+            onClick={() => navigate('/contact')}
+            className="w-full sm:w-auto inline-flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-transform transform hover:scale-105"
+          >
+            Contact Homepage
+          </button>
+          <button
+            onClick={() => navigate('/edit')}
+            className="w-full sm:w-auto inline-flex items-center justify-center px-8 py-3 border border-slate-300 text-base font-medium rounded-md text-slate-700 bg-white hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-transform transform hover:scale-105"
+          >
+            Certify My Page
+          </button>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default LandingPage;

+ 20 - 0
README.md

@@ -0,0 +1,20 @@
+<div align="center">
+<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
+</div>
+
+# Run and deploy your AI Studio app
+
+This contains everything you need to run your app locally.
+
+View your app in AI Studio: https://ai.studio/apps/drive/19o8dRod9SJYV2EtKSNEon5aaVQG2Erw7
+
+## Run Locally
+
+**Prerequisites:**  Node.js
+
+
+1. Install dependencies:
+   `npm install`
+2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
+3. Run the app:
+   `npm run dev`

+ 79 - 0
components/AuthenticityScore.tsx

@@ -0,0 +1,79 @@
+import React from 'react';
+import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts';
+
+interface AuthenticityScoreProps {
+  videoVerificationScore: number;
+  socialBindingScore: number;
+  cloneMaturityScore: number;
+}
+
+const ScoreBreakdownBar: React.FC<{ label: string; score: number; maxScore: number; color: string }> = ({ label, score, maxScore, color }) => {
+    const colorVariants: {[key: string]: string} = {
+        'text-blue-500': 'from-blue-400 to-blue-600',
+        'text-indigo-500': 'from-indigo-400 to-indigo-600',
+        'text-green-500': 'from-green-400 to-green-600',
+    }
+    return (
+        <div>
+            <div className="flex justify-between items-center mb-1">
+            <span className="text-sm font-medium text-slate-600">{label}</span>
+            <span className={`text-sm font-bold ${color}`}>{score} / {maxScore}</span>
+            </div>
+            <div className="w-full bg-slate-200 rounded-full h-2.5">
+            <div className={`bg-gradient-to-r ${colorVariants[color] || 'from-slate-400 to-slate-600'} h-2.5 rounded-full`} style={{ width: `${(score / maxScore) * 100}%` }}></div>
+            </div>
+        </div>
+    );
+};
+
+const AuthenticityScore: React.FC<AuthenticityScoreProps> = ({ videoVerificationScore, socialBindingScore, cloneMaturityScore }) => {
+  const totalScore = videoVerificationScore + socialBindingScore + cloneMaturityScore;
+  const maxTotalScore = 120; // 15 + 45 + 60
+  const scoreData = [
+    { name: 'Score', value: totalScore },
+    { name: 'Remaining', value: maxTotalScore - totalScore },
+  ];
+  const COLORS = ['#3b82f6', '#e5e7eb'];
+
+  return (
+    <div className="bg-white rounded-2xl border border-slate-200 p-6">
+      <h2 className="text-xl font-bold text-slate-800 mb-4 text-center">Authenticity Score</h2>
+      <div className="flex flex-col md:flex-row items-center gap-6 md:gap-8">
+        <div className="w-48 h-48 relative">
+          <ResponsiveContainer width="100%" height="100%">
+            <PieChart>
+              <Pie
+                data={scoreData}
+                cx="50%"
+                cy="50%"
+                innerRadius={60}
+                outerRadius={80}
+                startAngle={90}
+                endAngle={450}
+                paddingAngle={0}
+                dataKey="value"
+                cornerRadius={10}
+              >
+                {scoreData.map((entry, index) => (
+                  <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} stroke="none"/>
+                ))}
+              </Pie>
+              <Tooltip formatter={(value: number, name: string) => [value, '']} />
+            </PieChart>
+          </ResponsiveContainer>
+          <div className="absolute inset-0 flex flex-col items-center justify-center">
+            <span className="text-4xl font-extrabold text-blue-600">{totalScore}</span>
+            <span className="text-slate-500">/ {maxTotalScore}</span>
+          </div>
+        </div>
+        <div className="flex-1 w-full space-y-4">
+          <ScoreBreakdownBar label="Video Verification" score={videoVerificationScore} maxScore={15} color="text-green-500" />
+          <ScoreBreakdownBar label="Social Account Binding" score={socialBindingScore} maxScore={45} color="text-blue-500" />
+          <ScoreBreakdownBar label="Digital Clone Maturity" score={cloneMaturityScore} maxScore={60} color="text-indigo-500" />
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default AuthenticityScore;

+ 18 - 0
components/Bio.tsx

@@ -0,0 +1,18 @@
+import React from 'react';
+
+interface BioProps {
+  text: string;
+}
+
+const Bio: React.FC<BioProps> = ({ text }) => {
+  return (
+    <div className="bg-white rounded-2xl border border-slate-200 p-6">
+      <h2 className="text-xl font-bold text-slate-800 mb-3">About Me</h2>
+      <p className="text-slate-600 leading-relaxed">
+        {text}
+      </p>
+    </div>
+  );
+};
+
+export default Bio;

+ 38 - 0
components/Certifications.tsx

@@ -0,0 +1,38 @@
+import React from 'react';
+import type { Certification } from '../types';
+
+interface CertificationsProps {
+  certifications: Certification[];
+}
+
+const Certifications: React.FC<CertificationsProps> = ({ certifications }) => {
+  if (!certifications || certifications.length === 0) {
+    return null;
+  }
+
+  return (
+    <div className="bg-white rounded-2xl border border-slate-200 p-6">
+      <h2 className="text-xl font-bold text-slate-800 mb-4">Licenses & Certifications</h2>
+      <ul className="space-y-4">
+        {certifications.map((cert, index) => (
+          <li key={index} className="flex items-start gap-4 pt-4 first:pt-0 border-t border-slate-100 first:border-t-0">
+            <div className="flex-shrink-0 w-12 h-12 bg-slate-100 rounded-md flex items-center justify-center mt-1 p-1">
+              <img 
+                src={cert.imageUrl} 
+                alt={`${cert.issuer} logo`} 
+                className="w-full h-full object-contain rounded-sm"
+              />
+            </div>
+            <div>
+              <h3 className="font-semibold text-slate-800">{cert.name}</h3>
+              <p className="text-sm text-slate-600">{cert.issuer}</p>
+              <p className="text-xs text-slate-400 mt-1">{cert.date}</p>
+            </div>
+          </li>
+        ))}
+      </ul>
+    </div>
+  );
+};
+
+export default Certifications;

+ 71 - 0
components/SocialLinks.tsx

@@ -0,0 +1,71 @@
+import React from 'react';
+import type { SocialLinksData } from '../types';
+import LinkedInIcon from './icons/LinkedInIcon';
+import XIcon from './icons/XIcon';
+import GithubIcon from './icons/GithubIcon';
+import TiktokIcon from './icons/TiktokIcon';
+import VerifiedIcon from './icons/VerifiedIcon';
+import ArrowRightIcon from './icons/ArrowRightIcon';
+import InstagramIcon from './icons/InstagramIcon';
+import FacebookIcon from './icons/FacebookIcon';
+import DiscordIcon from './icons/DiscordIcon';
+
+interface SocialLinksProps {
+  links: SocialLinksData;
+}
+
+const socialPlatforms = [
+  { key: 'linkedin', name: 'LinkedIn', icon: <LinkedInIcon />, iconBg: 'bg-gradient-to-br from-blue-500 to-sky-400' },
+  { key: 'x', name: 'X (Twitter)', icon: <XIcon />, iconBg: 'bg-gradient-to-br from-slate-900 to-slate-700' },
+  { key: 'github', name: 'GitHub', icon: <GithubIcon />, iconBg: 'bg-gradient-to-br from-slate-900 to-slate-700' },
+  { key: 'tiktok', name: 'TikTok', icon: <TiktokIcon />, iconBg: 'bg-gradient-to-br from-black via-rose-500 to-cyan-400' },
+  { key: 'instagram', name: 'Instagram', icon: <InstagramIcon />, iconBg: 'bg-gradient-to-br from-purple-500 via-pink-500 to-yellow-500' },
+  { key: 'facebook', name: 'Facebook', icon: <FacebookIcon />, iconBg: 'bg-gradient-to-br from-blue-700 to-blue-500' },
+  { key: 'discord', name: 'Discord', icon: <DiscordIcon />, iconBg: 'bg-gradient-to-br from-indigo-600 to-purple-500' },
+] as const;
+
+
+const SocialLinks: React.FC<SocialLinksProps> = ({ links }) => {
+  return (
+    <div className="bg-white rounded-2xl border border-slate-200 p-6">
+      <h2 className="text-xl font-bold text-slate-800 mb-4">Verified Social Accounts</h2>
+      <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
+        {socialPlatforms.map((platform) => {
+          const link = links[platform.key];
+          if (!link) return null;
+          return (
+            <a
+              key={platform.key}
+              href={link}
+              target="_blank"
+              rel="noopener noreferrer"
+              className={`group flex items-center justify-between p-4 rounded-xl bg-slate-50 transition-colors duration-300 ease-in-out hover:bg-slate-100`}
+            >
+              <div className="flex items-center overflow-hidden">
+                <div className={`flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center text-white ${platform.iconBg}`}>
+                    {platform.icon}
+                </div>
+                <div className="ml-4">
+                    <div className="flex items-center">
+                        <h3 className="font-bold text-slate-800 text-sm">{platform.name}</h3>
+                        <div className="ml-2 flex items-center bg-green-100 text-green-800 text-xs font-medium px-2 py-0.5 rounded-full">
+                            <VerifiedIcon className="w-3 h-3 mr-1" />
+                            Verified
+                        </div>
+                    </div>
+                    <p className="text-xs text-slate-500 font-mono truncate max-w-[120px] sm:max-w-xs">{link.replace(/^https?:\/\/(www\.)?/, '')}</p>
+                </div>
+              </div>
+
+              <div className="text-slate-400 group-hover:text-slate-800 transition-transform duration-300 group-hover:translate-x-1">
+                <ArrowRightIcon className="w-4 h-4" />
+              </div>
+            </a>
+          );
+        })}
+      </div>
+    </div>
+  );
+};
+
+export default SocialLinks;

+ 28 - 0
components/VideoIntroduction.tsx

@@ -0,0 +1,28 @@
+import React from 'react';
+
+interface VideoIntroductionProps {
+  videoUrl: string;
+  posterUrl: string;
+}
+
+const VideoIntroduction: React.FC<VideoIntroductionProps> = ({ videoUrl, posterUrl }) => {
+  return (
+    <div className="bg-white rounded-2xl border border-slate-200 p-6">
+      <h2 className="text-xl font-bold text-slate-800 mb-4">Video Introduction</h2>
+      <div className="aspect-video w-full">
+        <video
+          controls
+          poster={posterUrl}
+          className="w-full h-full rounded-lg object-cover shadow-md bg-slate-200"
+          preload="metadata"
+          aria-label="Video Introduction"
+        >
+          <source src={videoUrl} type="video/mp4" />
+          <p>Your browser does not support the video tag. You can <a href={videoUrl} download className="text-blue-600 hover:underline">download the video</a> instead.</p>
+        </video>
+      </div>
+    </div>
+  );
+};
+
+export default VideoIntroduction;

+ 46 - 0
components/VirtualIdCard.tsx

@@ -0,0 +1,46 @@
+import React from 'react';
+import ShieldIcon from './icons/ShieldIcon';
+
+interface VirtualIdCardProps {
+  name: string;
+  avatarUrl: string;
+  virtualId: string;
+  totalScore: number;
+}
+
+const VirtualIdCard: React.FC<VirtualIdCardProps> = ({ name, avatarUrl, virtualId, totalScore }) => {
+  return (
+    <div className="bg-gradient-to-br from-slate-900 via-slate-800 to-sky-900 rounded-2xl shadow-2xl p-6 text-white overflow-hidden relative group">
+      {/* Decorative Circles */}
+      <div className="absolute -top-10 -right-10 w-40 h-40 bg-sky-500/10 rounded-full"></div>
+      <div className="absolute -bottom-16 -left-12 w-48 h-48 bg-slate-500/10 rounded-full"></div>
+
+      <div className="relative z-10 flex flex-col sm:flex-row items-center gap-6">
+        <img
+          src={avatarUrl}
+          alt={name}
+          className="w-24 h-24 rounded-full border-4 border-slate-600 shadow-lg object-cover"
+        />
+        <div className="text-center sm:text-left">
+          <h1 className="text-3xl font-bold tracking-wide">{name}</h1>
+          <p className="text-sm text-slate-400 font-mono tracking-widest mt-1">{virtualId}</p>
+        </div>
+      </div>
+      
+      {/* Holographic Security Tag */}
+      <div 
+        className="absolute bottom-4 right-6 w-14 h-14 flex items-center justify-center z-20" 
+        aria-label="Holographic Security Seal"
+      >
+        <div className="absolute inset-0 rounded-full bg-slate-700/30 backdrop-blur-sm border border-white/10"></div>
+        <div className="absolute inset-1 rounded-full border border-white/10 animate-[spin_8s_linear_infinite]"></div>
+        <div className={`relative w-12 h-12 rounded-full flex items-center justify-center overflow-hidden ${totalScore >= 30 ? 'holographic-gleam' : ''}`}>
+          <div className="absolute inset-0 bg-gradient-to-br from-sky-400/20 to-purple-500/20"></div>
+          <ShieldIcon />
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default VirtualIdCard;

+ 9 - 0
components/icons/ArrowRightIcon.tsx

@@ -0,0 +1,9 @@
+import React from 'react';
+
+const ArrowRightIcon: React.FC<{className?: string}> = ({ className }) => (
+  <svg xmlns="http://www.w3.org/2000/svg" className={className || "h-6 w-6"} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="3">
+    <path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
+  </svg>
+);
+
+export default ArrowRightIcon;

+ 27 - 0
components/icons/BrainCircuitIcon.tsx

@@ -0,0 +1,27 @@
+import React from 'react';
+
+const BrainCircuitIcon: React.FC = () => (
+  <svg 
+    xmlns="http://www.w3.org/2000/svg" 
+    width="28" 
+    height="28" 
+    viewBox="0 0 24 24" 
+    fill="none" 
+    stroke="currentColor" 
+    strokeWidth="2" 
+    strokeLinecap="round" 
+    strokeLinejoin="round"
+  >
+    <path d="M12 8V4m-2 2h4"/>
+    <path d="M12 20v-4m-2 2h4"/>
+    <path d="M8 12H4m2-2v4"/>
+    <path d="M20 12h-4m2-2v4"/>
+    <path d="M9.4 9.4l-2.8-2.8"/>
+    <path d="M17.4 17.4l-2.8-2.8"/>
+    <path d="M9.4 14.6l-2.8 2.8"/>
+    <path d="M17.4 6.6l-2.8 2.8"/>
+    <circle cx="12" cy="12" r="3"/>
+  </svg>
+);
+
+export default BrainCircuitIcon;

+ 14 - 0
components/icons/CertificateIcon.tsx

@@ -0,0 +1,14 @@
+import React from 'react';
+
+const CertificateIcon: React.FC = () => (
+  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
+    <path d="M12 21l-8-4.5v-9l8 4.5 8-4.5v9L12 21z"></path>
+    <path d="M12 12l8-4.5"></path>
+    <path d="M12 12v9"></path>
+    <path d="M12 12L4 7.5"></path>
+    <path d="M4 7.5l8-4.5 8 4.5"></path>
+    <path d="M4 7.5v9"></path>
+  </svg>
+);
+
+export default CertificateIcon;

+ 8 - 0
components/icons/DiscordIcon.tsx

@@ -0,0 +1,8 @@
+import React from 'react';
+
+const DiscordIcon: React.FC = () => (
+    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+      <path d="M20.317 4.36982C18.9185 3.59382 17.3595 3.03382 15.704 2.71382C15.5115 3.13382 15.2895 3.78382 15.0445 4.36982C13.2255 3.93382 11.3815 3.93382 9.5625 4.36982C9.3175 3.78382 9.0955 3.13382 8.903 2.71382C7.2475 3.03382 5.6885 3.59382 4.29 4.36982C1.693 8.16382 1.258 11.8218 2.011 15.3468C3.6555 16.7118 5.5475 17.5878 7.5755 18.1068C7.9045 17.5878 8.1945 17.0388 8.4555 16.4608C7.7375 16.2408 7.0485 15.9618 6.4055 15.6228C6.551 15.5428 6.6965 15.4578 6.8325 15.3728C10.4595 17.0138 14.1535 17.0138 17.771 15.3728C17.907 15.4578 18.0525 15.5428 18.198 15.6228C17.555 15.9618 16.866 16.2408 16.148 16.4608C16.409 17.0388 16.699 17.5878 17.028 18.1068C19.056 17.5878 20.948 16.7118 22.5925 15.3468C23.374 11.4328 22.883 7.82882 20.317 4.36982ZM14.1225 13.3308C13.1235 13.3308 12.2895 12.4818 12.2895 11.4708C12.2895 10.4598 13.114 9.61082 14.1225 9.61082C15.131 9.61082 15.965 10.4598 15.9555 11.4708C15.9555 12.4818 15.131 13.3308 14.1225 13.3308ZM10.4785 13.3308C9.4795 13.3308 8.6455 12.4818 8.6455 11.4708C8.6455 10.4598 9.47 9.61082 10.4785 9.61082C11.487 9.61082 12.321 10.4598 12.321 11.4708C12.321 12.4818 11.487 13.3308 10.4785 13.3308Z" />
+    </svg>
+);
+export default DiscordIcon;

+ 9 - 0
components/icons/FacebookIcon.tsx

@@ -0,0 +1,9 @@
+import React from 'react';
+
+const FacebookIcon: React.FC = () => (
+  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+    <path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
+  </svg>
+);
+
+export default FacebookIcon;

+ 9 - 0
components/icons/GithubIcon.tsx

@@ -0,0 +1,9 @@
+import React from 'react';
+
+const GithubIcon: React.FC = () => (
+  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+    <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
+  </svg>
+);
+
+export default GithubIcon;

+ 9 - 0
components/icons/InstagramIcon.tsx

@@ -0,0 +1,9 @@
+import React from 'react';
+
+const InstagramIcon: React.FC = () => (
+  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+    <path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.85s-.011 3.584-.069 4.85c-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07s-3.584-.012-4.85-.07c-3.252-.148-4.771-1.691-4.919-4.919-.058-1.265-.069-1.645-.069-4.85s.011-3.584.069-4.85c.149-3.225 1.664-4.771 4.919-4.919 1.266-.057 1.644-.069 4.85-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948s.014 3.667.072 4.947c.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072s3.667-.014 4.947-.072c4.358-.2 6.78-2.618 6.98-6.98.059-1.281.073-1.689.073-4.948s-.014-3.667-.072-4.947c-.2-4.358-2.618-6.78-6.98-6.98-1.281-.059-1.689-.073-4.948-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.162 6.162 6.162 6.162-2.759 6.162-6.162-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4s1.791-4 4-4 4 1.79 4 4-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.441 1.441 1.441 1.441-.645 1.441-1.441-.645-1.44-1.441-1.44z"/>
+  </svg>
+);
+
+export default InstagramIcon;

+ 9 - 0
components/icons/LinkedInIcon.tsx

@@ -0,0 +1,9 @@
+import React from 'react';
+
+const LinkedInIcon: React.FC = () => (
+  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+    <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667h-3.554v-12h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.281zM8.077 6.427h.022c1.225 0 2.002-.824 2.002-1.852C10.08 3.506 9.28 2.6 8.1 2.6S6.1 3.506 6.1 4.575c0 1.028.777 1.852 1.977 1.852zM6.35 8.452h3.554v12H6.35v-12z" />
+  </svg>
+);
+
+export default LinkedInIcon;

+ 21 - 0
components/icons/ShieldIcon.tsx

@@ -0,0 +1,21 @@
+import React from 'react';
+
+const ShieldIcon: React.FC = () => (
+    <svg 
+        xmlns="http://www.w3.org/2000/svg" 
+        width="32" 
+        height="32" 
+        viewBox="0 0 24 24" 
+        fill="none" 
+        stroke="currentColor" 
+        strokeWidth="1.5" 
+        strokeLinecap="round" 
+        strokeLinejoin="round"
+        className="opacity-80"
+        aria-hidden="true"
+    >
+        <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
+    </svg>
+);
+
+export default ShieldIcon;

+ 16 - 0
components/icons/TiktokIcon.tsx

@@ -0,0 +1,16 @@
+import React from 'react';
+
+const TiktokIcon: React.FC = () => (
+  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+    <defs>
+      <path id="tiktok-path-a" d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-2.43.05-4.86-.95-6.69-2.81-1.77-1.8-2.6-4.15-2.47-6.54.04-1.38.36-2.76.94-4.04 1.01-2.2 2.82-3.95 5.05-4.78.68-.26 1.38-.49 2.1-.7.02-3.46.02-6.92.01-10.38z" />
+    </defs>
+    <g>
+      <use href="#tiktok-path-a" fill="#FE2C55" transform="translate(-1.2, -1.2)" />
+      <use href="#tiktok-path-a" fill="#25F4EE" transform="translate(1.2, 1.2)" />
+      <use href="#tiktok-path-a" fill="white" />
+    </g>
+  </svg>
+);
+
+export default TiktokIcon;

+ 9 - 0
components/icons/UploadIcon.tsx

@@ -0,0 +1,9 @@
+import React from 'react';
+
+const UploadIcon: React.FC = () => (
+  <svg xmlns="http://www.w3.org/2000/svg" className="mx-auto h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
+    <path strokeLinecap="round" strokeLinejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
+  </svg>
+);
+
+export default UploadIcon;

+ 9 - 0
components/icons/VerifiedIcon.tsx

@@ -0,0 +1,9 @@
+import React from 'react';
+
+const VerifiedIcon: React.FC<{ className?: string }> = ({ className = 'h-5 w-5' }) => (
+    <svg xmlns="http://www.w3.org/2000/svg" className={className} viewBox="0 0 20 20" fill="currentColor">
+        <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
+    </svg>
+);
+
+export default VerifiedIcon;

+ 9 - 0
components/icons/XIcon.tsx

@@ -0,0 +1,9 @@
+import React from 'react';
+
+const XIcon: React.FC = () => (
+  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+    <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
+  </svg>
+);
+
+export default XIcon;

+ 59 - 0
index.html

@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Contact Homepage</title>
+    <script src="https://cdn.tailwindcss.com"></script>
+    <script type="importmap">
+{
+  "imports": {
+    "react": "https://esm.sh/react@18.2.0",
+    "react-dom/client": "https://esm.sh/react-dom@18.2.0/client",
+    "react-router-dom": "https://esm.sh/react-router-dom@6",
+    "recharts": "https://esm.sh/recharts@2.12.7",
+    "react-dom/": "https://aistudiocdn.com/react-dom@^19.2.0/",
+    "react/": "https://aistudiocdn.com/react@^19.2.0/"
+  }
+}
+</script>
+<style>
+   /* Holographic Gleam Effect */
+  .holographic-gleam::before {
+    content: '';
+    position: absolute;
+    top: -10%;
+    left: -50%;
+    width: 200%;
+    height: 200%;
+    background: linear-gradient(
+      110deg,
+      rgba(0, 255, 255, 0) 40%,
+      rgba(0, 255, 255, 0.5) 48%,
+      rgba(255, 0, 255, 0.5) 52%,
+      rgba(255, 0, 255, 0) 60%
+    );
+    background-size: 200% 100%;
+    animation: holographic-gleam 3s linear infinite;
+    transform: rotate(25deg);
+    z-index: 1;
+    opacity: 1; /* Always active */
+  }
+
+   @keyframes holographic-gleam {
+    0% { background-position: 200% 0; }
+    100% { background-position: -200% 0; }
+  }
+   @keyframes spin {
+    from { transform: rotate(0deg); }
+    to { transform: rotate(360deg); }
+  }
+</style>
+<link rel="stylesheet" href="/index.css">
+</head>
+  <body class="bg-slate-100">
+    <div id="root"></div>
+    <script type="module" src="/src/index.tsx"></script>
+  <script type="module" src="/index.tsx"></script>
+</body>
+</html>

+ 18 - 0
index.tsx

@@ -0,0 +1,18 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { BrowserRouter } from 'react-router-dom';
+import App from './App';
+
+const rootElement = document.getElementById('root');
+if (!rootElement) {
+  throw new Error("Could not find root element to mount to");
+}
+
+const root = ReactDOM.createRoot(rootElement);
+root.render(
+  <React.StrictMode>
+    <BrowserRouter>
+      <App />
+    </BrowserRouter>
+  </React.StrictMode>
+);

+ 5 - 0
metadata.json

@@ -0,0 +1,5 @@
+{
+  "name": "Copy of Contact Homepage",
+  "description": "A simple and modern contact homepage that displays social media links, a bio, and a unique authenticity score based on social account verification and digital clone maturity.",
+  "requestFramePermissions": []
+}

+ 63 - 0
oauth-callback.html

@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Connecting...</title>
+    <script src="https://cdn.tailwindcss.com"></script>
+    <style>
+        @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
+        .spinner { border-top-color: #3b82f6; animation: spin 1s linear infinite; }
+    </style>
+</head>
+<body class="bg-slate-100 flex items-center justify-center min-h-screen">
+    <div class="text-center p-8 bg-white rounded-lg shadow-md">
+        <div class="spinner w-12 h-12 border-4 border-slate-200 rounded-full mx-auto mb-4"></div>
+        <h1 class="text-xl font-semibold text-slate-700">Finalizing Connection...</h1>
+        <p class="text-slate-500 mt-2">Please wait while we verify your account.</p>
+    </div>
+    <script>
+        window.onload = () => {
+            const params = new URLSearchParams(window.location.search);
+            const code = params.get('code');
+            const error = params.get('error');
+            const platform = params.get('state'); // Real OAuth providers use 'state' to return context
+
+            if (window.opener) {
+                if (code && platform) {
+                    // In a real app, the backend would use this `code` to get an access token and user info.
+                    // Here, we'll simulate success and create a placeholder profile URL.
+                    const username = 'verified.user'; 
+                    const profileUrls = {
+                        linkedin: `https://www.linkedin.com/in/${username}`,
+                        x: `https://x.com/${username}`,
+                        github: `https://github.com/${username}`,
+                        tiktok: `https://www.tiktok.com/@${username}`,
+                        instagram: `https://www.instagram.com/${username}`,
+                        facebook: `https://www.facebook.com/${username}`,
+                        discord: `VerifiedUser#1234`
+                    };
+                    
+                    const message = {
+                        type: 'oauth-success',
+                        platform: platform,
+                        profileUrl: profileUrls[platform] || `https://example.com/${username}`
+                    };
+                    window.opener.postMessage(message, window.location.origin);
+
+                } else {
+                    // Handle failure
+                    const message = {
+                        type: 'oauth-error',
+                        platform: platform,
+                        error: error || 'Authentication failed or was cancelled by the user.'
+                    };
+                    window.opener.postMessage(message, window.location.origin);
+                }
+            }
+
+            // Close the popup after a short delay to allow the message to be sent
+            setTimeout(() => window.close(), 500);
+        };
+    </script>
+</body>
+</html>

+ 23 - 0
package.json

@@ -0,0 +1,23 @@
+{
+  "name": "copy-of-contact-homepage",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "react": "^19.2.0",
+    "react-router-dom": "6",
+    "recharts": "2.12.7",
+    "react-dom": "^19.2.0"
+  },
+  "devDependencies": {
+    "@types/node": "^22.14.0",
+    "@vitejs/plugin-react": "^5.0.0",
+    "typescript": "~5.8.2",
+    "vite": "^6.2.0"
+  }
+}

+ 1423 - 0
pnpm-lock.yaml

@@ -0,0 +1,1423 @@
+lockfileVersion: '9.0'
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
+
+importers:
+
+  .:
+    dependencies:
+      react:
+        specifier: ^19.2.0
+        version: 19.2.0
+      react-dom:
+        specifier: ^19.2.0
+        version: 19.2.0(react@19.2.0)
+      react-router-dom:
+        specifier: '6'
+        version: 6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+      recharts:
+        specifier: 2.12.7
+        version: 2.12.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+    devDependencies:
+      '@types/node':
+        specifier: ^22.14.0
+        version: 22.19.0
+      '@vitejs/plugin-react':
+        specifier: ^5.0.0
+        version: 5.1.0(vite@6.4.1(@types/node@22.19.0))
+      typescript:
+        specifier: ~5.8.2
+        version: 5.8.3
+      vite:
+        specifier: ^6.2.0
+        version: 6.4.1(@types/node@22.19.0)
+
+packages:
+
+  '@babel/code-frame@7.27.1':
+    resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/compat-data@7.28.5':
+    resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/core@7.28.5':
+    resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/generator@7.28.5':
+    resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-compilation-targets@7.27.2':
+    resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-globals@7.28.0':
+    resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-module-imports@7.27.1':
+    resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-module-transforms@7.28.3':
+    resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+
+  '@babel/helper-plugin-utils@7.27.1':
+    resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-string-parser@7.27.1':
+    resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-validator-identifier@7.28.5':
+    resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-validator-option@7.27.1':
+    resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helpers@7.28.4':
+    resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/parser@7.28.5':
+    resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+
+  '@babel/plugin-transform-react-jsx-self@7.27.1':
+    resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-transform-react-jsx-source@7.27.1':
+    resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/runtime@7.28.4':
+    resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/template@7.27.2':
+    resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/traverse@7.28.5':
+    resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/types@7.28.5':
+    resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
+    engines: {node: '>=6.9.0'}
+
+  '@esbuild/aix-ppc64@0.25.12':
+    resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [aix]
+
+  '@esbuild/android-arm64@0.25.12':
+    resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [android]
+
+  '@esbuild/android-arm@0.25.12':
+    resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
+    engines: {node: '>=18'}
+    cpu: [arm]
+    os: [android]
+
+  '@esbuild/android-x64@0.25.12':
+    resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [android]
+
+  '@esbuild/darwin-arm64@0.25.12':
+    resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@esbuild/darwin-x64@0.25.12':
+    resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [darwin]
+
+  '@esbuild/freebsd-arm64@0.25.12':
+    resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [freebsd]
+
+  '@esbuild/freebsd-x64@0.25.12':
+    resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@esbuild/linux-arm64@0.25.12':
+    resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [linux]
+
+  '@esbuild/linux-arm@0.25.12':
+    resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
+    engines: {node: '>=18'}
+    cpu: [arm]
+    os: [linux]
+
+  '@esbuild/linux-ia32@0.25.12':
+    resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
+    engines: {node: '>=18'}
+    cpu: [ia32]
+    os: [linux]
+
+  '@esbuild/linux-loong64@0.25.12':
+    resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
+    engines: {node: '>=18'}
+    cpu: [loong64]
+    os: [linux]
+
+  '@esbuild/linux-mips64el@0.25.12':
+    resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
+    engines: {node: '>=18'}
+    cpu: [mips64el]
+    os: [linux]
+
+  '@esbuild/linux-ppc64@0.25.12':
+    resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [linux]
+
+  '@esbuild/linux-riscv64@0.25.12':
+    resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
+    engines: {node: '>=18'}
+    cpu: [riscv64]
+    os: [linux]
+
+  '@esbuild/linux-s390x@0.25.12':
+    resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
+    engines: {node: '>=18'}
+    cpu: [s390x]
+    os: [linux]
+
+  '@esbuild/linux-x64@0.25.12':
+    resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [linux]
+
+  '@esbuild/netbsd-arm64@0.25.12':
+    resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [netbsd]
+
+  '@esbuild/netbsd-x64@0.25.12':
+    resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [netbsd]
+
+  '@esbuild/openbsd-arm64@0.25.12':
+    resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [openbsd]
+
+  '@esbuild/openbsd-x64@0.25.12':
+    resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [openbsd]
+
+  '@esbuild/openharmony-arm64@0.25.12':
+    resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [openharmony]
+
+  '@esbuild/sunos-x64@0.25.12':
+    resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [sunos]
+
+  '@esbuild/win32-arm64@0.25.12':
+    resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [win32]
+
+  '@esbuild/win32-ia32@0.25.12':
+    resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
+    engines: {node: '>=18'}
+    cpu: [ia32]
+    os: [win32]
+
+  '@esbuild/win32-x64@0.25.12':
+    resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [win32]
+
+  '@jridgewell/gen-mapping@0.3.13':
+    resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+  '@jridgewell/remapping@2.3.5':
+    resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+  '@jridgewell/resolve-uri@3.1.2':
+    resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+    engines: {node: '>=6.0.0'}
+
+  '@jridgewell/sourcemap-codec@1.5.5':
+    resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+  '@jridgewell/trace-mapping@0.3.31':
+    resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+  '@remix-run/router@1.23.0':
+    resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==}
+    engines: {node: '>=14.0.0'}
+
+  '@rolldown/pluginutils@1.0.0-beta.43':
+    resolution: {integrity: sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==}
+
+  '@rollup/rollup-android-arm-eabi@4.52.5':
+    resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==}
+    cpu: [arm]
+    os: [android]
+
+  '@rollup/rollup-android-arm64@4.52.5':
+    resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==}
+    cpu: [arm64]
+    os: [android]
+
+  '@rollup/rollup-darwin-arm64@4.52.5':
+    resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@rollup/rollup-darwin-x64@4.52.5':
+    resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==}
+    cpu: [x64]
+    os: [darwin]
+
+  '@rollup/rollup-freebsd-arm64@4.52.5':
+    resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==}
+    cpu: [arm64]
+    os: [freebsd]
+
+  '@rollup/rollup-freebsd-x64@4.52.5':
+    resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@rollup/rollup-linux-arm-gnueabihf@4.52.5':
+    resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==}
+    cpu: [arm]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-arm-musleabihf@4.52.5':
+    resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==}
+    cpu: [arm]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-linux-arm64-gnu@4.52.5':
+    resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==}
+    cpu: [arm64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-arm64-musl@4.52.5':
+    resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==}
+    cpu: [arm64]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-linux-loong64-gnu@4.52.5':
+    resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==}
+    cpu: [loong64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-ppc64-gnu@4.52.5':
+    resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==}
+    cpu: [ppc64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-riscv64-gnu@4.52.5':
+    resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==}
+    cpu: [riscv64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-riscv64-musl@4.52.5':
+    resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==}
+    cpu: [riscv64]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-linux-s390x-gnu@4.52.5':
+    resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==}
+    cpu: [s390x]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-x64-gnu@4.52.5':
+    resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==}
+    cpu: [x64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-x64-musl@4.52.5':
+    resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==}
+    cpu: [x64]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-openharmony-arm64@4.52.5':
+    resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==}
+    cpu: [arm64]
+    os: [openharmony]
+
+  '@rollup/rollup-win32-arm64-msvc@4.52.5':
+    resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==}
+    cpu: [arm64]
+    os: [win32]
+
+  '@rollup/rollup-win32-ia32-msvc@4.52.5':
+    resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==}
+    cpu: [ia32]
+    os: [win32]
+
+  '@rollup/rollup-win32-x64-gnu@4.52.5':
+    resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==}
+    cpu: [x64]
+    os: [win32]
+
+  '@rollup/rollup-win32-x64-msvc@4.52.5':
+    resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==}
+    cpu: [x64]
+    os: [win32]
+
+  '@types/babel__core@7.20.5':
+    resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+  '@types/babel__generator@7.27.0':
+    resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+  '@types/babel__template@7.4.4':
+    resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+  '@types/babel__traverse@7.28.0':
+    resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
+  '@types/d3-array@3.2.2':
+    resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
+
+  '@types/d3-color@3.1.3':
+    resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
+
+  '@types/d3-ease@3.0.2':
+    resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
+
+  '@types/d3-interpolate@3.0.4':
+    resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
+
+  '@types/d3-path@3.1.1':
+    resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
+
+  '@types/d3-scale@4.0.9':
+    resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
+
+  '@types/d3-shape@3.1.7':
+    resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==}
+
+  '@types/d3-time@3.0.4':
+    resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
+
+  '@types/d3-timer@3.0.2':
+    resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
+
+  '@types/estree@1.0.8':
+    resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+  '@types/node@22.19.0':
+    resolution: {integrity: sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==}
+
+  '@vitejs/plugin-react@5.1.0':
+    resolution: {integrity: sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew==}
+    engines: {node: ^20.19.0 || >=22.12.0}
+    peerDependencies:
+      vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+
+  baseline-browser-mapping@2.8.23:
+    resolution: {integrity: sha512-616V5YX4bepJFzNyOfce5Fa8fDJMfoxzOIzDCZwaGL8MKVpFrXqfNUoIpRn9YMI5pXf/VKgzjB4htFMsFKKdiQ==}
+    hasBin: true
+
+  browserslist@4.27.0:
+    resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==}
+    engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+    hasBin: true
+
+  caniuse-lite@1.0.30001753:
+    resolution: {integrity: sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==}
+
+  clsx@2.1.1:
+    resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+    engines: {node: '>=6'}
+
+  convert-source-map@2.0.0:
+    resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+  csstype@3.1.3:
+    resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+
+  d3-array@3.2.4:
+    resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
+    engines: {node: '>=12'}
+
+  d3-color@3.1.0:
+    resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+    engines: {node: '>=12'}
+
+  d3-ease@3.0.1:
+    resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+    engines: {node: '>=12'}
+
+  d3-format@3.1.0:
+    resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
+    engines: {node: '>=12'}
+
+  d3-interpolate@3.0.1:
+    resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+    engines: {node: '>=12'}
+
+  d3-path@3.1.0:
+    resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
+    engines: {node: '>=12'}
+
+  d3-scale@4.0.2:
+    resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
+    engines: {node: '>=12'}
+
+  d3-shape@3.2.0:
+    resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
+    engines: {node: '>=12'}
+
+  d3-time-format@4.1.0:
+    resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
+    engines: {node: '>=12'}
+
+  d3-time@3.1.0:
+    resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
+    engines: {node: '>=12'}
+
+  d3-timer@3.0.1:
+    resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+    engines: {node: '>=12'}
+
+  debug@4.4.3:
+    resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+    engines: {node: '>=6.0'}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+
+  decimal.js-light@2.5.1:
+    resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
+
+  dom-helpers@5.2.1:
+    resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
+
+  electron-to-chromium@1.5.244:
+    resolution: {integrity: sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==}
+
+  esbuild@0.25.12:
+    resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
+    engines: {node: '>=18'}
+    hasBin: true
+
+  escalade@3.2.0:
+    resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+    engines: {node: '>=6'}
+
+  eventemitter3@4.0.7:
+    resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
+
+  fast-equals@5.3.2:
+    resolution: {integrity: sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==}
+    engines: {node: '>=6.0.0'}
+
+  fdir@6.5.0:
+    resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+    engines: {node: '>=12.0.0'}
+    peerDependencies:
+      picomatch: ^3 || ^4
+    peerDependenciesMeta:
+      picomatch:
+        optional: true
+
+  fsevents@2.3.3:
+    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+    os: [darwin]
+
+  gensync@1.0.0-beta.2:
+    resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+    engines: {node: '>=6.9.0'}
+
+  internmap@2.0.3:
+    resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
+    engines: {node: '>=12'}
+
+  js-tokens@4.0.0:
+    resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+  jsesc@3.1.0:
+    resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+    engines: {node: '>=6'}
+    hasBin: true
+
+  json5@2.2.3:
+    resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+    engines: {node: '>=6'}
+    hasBin: true
+
+  lodash@4.17.21:
+    resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
+  loose-envify@1.4.0:
+    resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+    hasBin: true
+
+  lru-cache@5.1.1:
+    resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+  ms@2.1.3:
+    resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+  nanoid@3.3.11:
+    resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+    hasBin: true
+
+  node-releases@2.0.27:
+    resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+
+  object-assign@4.1.1:
+    resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+    engines: {node: '>=0.10.0'}
+
+  picocolors@1.1.1:
+    resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+  picomatch@4.0.3:
+    resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+    engines: {node: '>=12'}
+
+  postcss@8.5.6:
+    resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+    engines: {node: ^10 || ^12 || >=14}
+
+  prop-types@15.8.1:
+    resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+  react-dom@19.2.0:
+    resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==}
+    peerDependencies:
+      react: ^19.2.0
+
+  react-is@16.13.1:
+    resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+  react-refresh@0.18.0:
+    resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
+    engines: {node: '>=0.10.0'}
+
+  react-router-dom@6.30.1:
+    resolution: {integrity: sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==}
+    engines: {node: '>=14.0.0'}
+    peerDependencies:
+      react: '>=16.8'
+      react-dom: '>=16.8'
+
+  react-router@6.30.1:
+    resolution: {integrity: sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==}
+    engines: {node: '>=14.0.0'}
+    peerDependencies:
+      react: '>=16.8'
+
+  react-smooth@4.0.4:
+    resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==}
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+  react-transition-group@4.4.5:
+    resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
+    peerDependencies:
+      react: '>=16.6.0'
+      react-dom: '>=16.6.0'
+
+  react@19.2.0:
+    resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==}
+    engines: {node: '>=0.10.0'}
+
+  recharts-scale@0.4.5:
+    resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==}
+
+  recharts@2.12.7:
+    resolution: {integrity: sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==}
+    engines: {node: '>=14'}
+    peerDependencies:
+      react: ^16.0.0 || ^17.0.0 || ^18.0.0
+      react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0
+
+  rollup@4.52.5:
+    resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==}
+    engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+    hasBin: true
+
+  scheduler@0.27.0:
+    resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+  semver@6.3.1:
+    resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+    hasBin: true
+
+  source-map-js@1.2.1:
+    resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+    engines: {node: '>=0.10.0'}
+
+  tiny-invariant@1.3.3:
+    resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
+
+  tinyglobby@0.2.15:
+    resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+    engines: {node: '>=12.0.0'}
+
+  typescript@5.8.3:
+    resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
+    engines: {node: '>=14.17'}
+    hasBin: true
+
+  undici-types@6.21.0:
+    resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+  update-browserslist-db@1.1.4:
+    resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==}
+    hasBin: true
+    peerDependencies:
+      browserslist: '>= 4.21.0'
+
+  victory-vendor@36.9.2:
+    resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
+
+  vite@6.4.1:
+    resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==}
+    engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+    hasBin: true
+    peerDependencies:
+      '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+      jiti: '>=1.21.0'
+      less: '*'
+      lightningcss: ^1.21.0
+      sass: '*'
+      sass-embedded: '*'
+      stylus: '*'
+      sugarss: '*'
+      terser: ^5.16.0
+      tsx: ^4.8.1
+      yaml: ^2.4.2
+    peerDependenciesMeta:
+      '@types/node':
+        optional: true
+      jiti:
+        optional: true
+      less:
+        optional: true
+      lightningcss:
+        optional: true
+      sass:
+        optional: true
+      sass-embedded:
+        optional: true
+      stylus:
+        optional: true
+      sugarss:
+        optional: true
+      terser:
+        optional: true
+      tsx:
+        optional: true
+      yaml:
+        optional: true
+
+  yallist@3.1.1:
+    resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+snapshots:
+
+  '@babel/code-frame@7.27.1':
+    dependencies:
+      '@babel/helper-validator-identifier': 7.28.5
+      js-tokens: 4.0.0
+      picocolors: 1.1.1
+
+  '@babel/compat-data@7.28.5': {}
+
+  '@babel/core@7.28.5':
+    dependencies:
+      '@babel/code-frame': 7.27.1
+      '@babel/generator': 7.28.5
+      '@babel/helper-compilation-targets': 7.27.2
+      '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5)
+      '@babel/helpers': 7.28.4
+      '@babel/parser': 7.28.5
+      '@babel/template': 7.27.2
+      '@babel/traverse': 7.28.5
+      '@babel/types': 7.28.5
+      '@jridgewell/remapping': 2.3.5
+      convert-source-map: 2.0.0
+      debug: 4.4.3
+      gensync: 1.0.0-beta.2
+      json5: 2.2.3
+      semver: 6.3.1
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/generator@7.28.5':
+    dependencies:
+      '@babel/parser': 7.28.5
+      '@babel/types': 7.28.5
+      '@jridgewell/gen-mapping': 0.3.13
+      '@jridgewell/trace-mapping': 0.3.31
+      jsesc: 3.1.0
+
+  '@babel/helper-compilation-targets@7.27.2':
+    dependencies:
+      '@babel/compat-data': 7.28.5
+      '@babel/helper-validator-option': 7.27.1
+      browserslist: 4.27.0
+      lru-cache: 5.1.1
+      semver: 6.3.1
+
+  '@babel/helper-globals@7.28.0': {}
+
+  '@babel/helper-module-imports@7.27.1':
+    dependencies:
+      '@babel/traverse': 7.28.5
+      '@babel/types': 7.28.5
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)':
+    dependencies:
+      '@babel/core': 7.28.5
+      '@babel/helper-module-imports': 7.27.1
+      '@babel/helper-validator-identifier': 7.28.5
+      '@babel/traverse': 7.28.5
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/helper-plugin-utils@7.27.1': {}
+
+  '@babel/helper-string-parser@7.27.1': {}
+
+  '@babel/helper-validator-identifier@7.28.5': {}
+
+  '@babel/helper-validator-option@7.27.1': {}
+
+  '@babel/helpers@7.28.4':
+    dependencies:
+      '@babel/template': 7.27.2
+      '@babel/types': 7.28.5
+
+  '@babel/parser@7.28.5':
+    dependencies:
+      '@babel/types': 7.28.5
+
+  '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)':
+    dependencies:
+      '@babel/core': 7.28.5
+      '@babel/helper-plugin-utils': 7.27.1
+
+  '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)':
+    dependencies:
+      '@babel/core': 7.28.5
+      '@babel/helper-plugin-utils': 7.27.1
+
+  '@babel/runtime@7.28.4': {}
+
+  '@babel/template@7.27.2':
+    dependencies:
+      '@babel/code-frame': 7.27.1
+      '@babel/parser': 7.28.5
+      '@babel/types': 7.28.5
+
+  '@babel/traverse@7.28.5':
+    dependencies:
+      '@babel/code-frame': 7.27.1
+      '@babel/generator': 7.28.5
+      '@babel/helper-globals': 7.28.0
+      '@babel/parser': 7.28.5
+      '@babel/template': 7.27.2
+      '@babel/types': 7.28.5
+      debug: 4.4.3
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/types@7.28.5':
+    dependencies:
+      '@babel/helper-string-parser': 7.27.1
+      '@babel/helper-validator-identifier': 7.28.5
+
+  '@esbuild/aix-ppc64@0.25.12':
+    optional: true
+
+  '@esbuild/android-arm64@0.25.12':
+    optional: true
+
+  '@esbuild/android-arm@0.25.12':
+    optional: true
+
+  '@esbuild/android-x64@0.25.12':
+    optional: true
+
+  '@esbuild/darwin-arm64@0.25.12':
+    optional: true
+
+  '@esbuild/darwin-x64@0.25.12':
+    optional: true
+
+  '@esbuild/freebsd-arm64@0.25.12':
+    optional: true
+
+  '@esbuild/freebsd-x64@0.25.12':
+    optional: true
+
+  '@esbuild/linux-arm64@0.25.12':
+    optional: true
+
+  '@esbuild/linux-arm@0.25.12':
+    optional: true
+
+  '@esbuild/linux-ia32@0.25.12':
+    optional: true
+
+  '@esbuild/linux-loong64@0.25.12':
+    optional: true
+
+  '@esbuild/linux-mips64el@0.25.12':
+    optional: true
+
+  '@esbuild/linux-ppc64@0.25.12':
+    optional: true
+
+  '@esbuild/linux-riscv64@0.25.12':
+    optional: true
+
+  '@esbuild/linux-s390x@0.25.12':
+    optional: true
+
+  '@esbuild/linux-x64@0.25.12':
+    optional: true
+
+  '@esbuild/netbsd-arm64@0.25.12':
+    optional: true
+
+  '@esbuild/netbsd-x64@0.25.12':
+    optional: true
+
+  '@esbuild/openbsd-arm64@0.25.12':
+    optional: true
+
+  '@esbuild/openbsd-x64@0.25.12':
+    optional: true
+
+  '@esbuild/openharmony-arm64@0.25.12':
+    optional: true
+
+  '@esbuild/sunos-x64@0.25.12':
+    optional: true
+
+  '@esbuild/win32-arm64@0.25.12':
+    optional: true
+
+  '@esbuild/win32-ia32@0.25.12':
+    optional: true
+
+  '@esbuild/win32-x64@0.25.12':
+    optional: true
+
+  '@jridgewell/gen-mapping@0.3.13':
+    dependencies:
+      '@jridgewell/sourcemap-codec': 1.5.5
+      '@jridgewell/trace-mapping': 0.3.31
+
+  '@jridgewell/remapping@2.3.5':
+    dependencies:
+      '@jridgewell/gen-mapping': 0.3.13
+      '@jridgewell/trace-mapping': 0.3.31
+
+  '@jridgewell/resolve-uri@3.1.2': {}
+
+  '@jridgewell/sourcemap-codec@1.5.5': {}
+
+  '@jridgewell/trace-mapping@0.3.31':
+    dependencies:
+      '@jridgewell/resolve-uri': 3.1.2
+      '@jridgewell/sourcemap-codec': 1.5.5
+
+  '@remix-run/router@1.23.0': {}
+
+  '@rolldown/pluginutils@1.0.0-beta.43': {}
+
+  '@rollup/rollup-android-arm-eabi@4.52.5':
+    optional: true
+
+  '@rollup/rollup-android-arm64@4.52.5':
+    optional: true
+
+  '@rollup/rollup-darwin-arm64@4.52.5':
+    optional: true
+
+  '@rollup/rollup-darwin-x64@4.52.5':
+    optional: true
+
+  '@rollup/rollup-freebsd-arm64@4.52.5':
+    optional: true
+
+  '@rollup/rollup-freebsd-x64@4.52.5':
+    optional: true
+
+  '@rollup/rollup-linux-arm-gnueabihf@4.52.5':
+    optional: true
+
+  '@rollup/rollup-linux-arm-musleabihf@4.52.5':
+    optional: true
+
+  '@rollup/rollup-linux-arm64-gnu@4.52.5':
+    optional: true
+
+  '@rollup/rollup-linux-arm64-musl@4.52.5':
+    optional: true
+
+  '@rollup/rollup-linux-loong64-gnu@4.52.5':
+    optional: true
+
+  '@rollup/rollup-linux-ppc64-gnu@4.52.5':
+    optional: true
+
+  '@rollup/rollup-linux-riscv64-gnu@4.52.5':
+    optional: true
+
+  '@rollup/rollup-linux-riscv64-musl@4.52.5':
+    optional: true
+
+  '@rollup/rollup-linux-s390x-gnu@4.52.5':
+    optional: true
+
+  '@rollup/rollup-linux-x64-gnu@4.52.5':
+    optional: true
+
+  '@rollup/rollup-linux-x64-musl@4.52.5':
+    optional: true
+
+  '@rollup/rollup-openharmony-arm64@4.52.5':
+    optional: true
+
+  '@rollup/rollup-win32-arm64-msvc@4.52.5':
+    optional: true
+
+  '@rollup/rollup-win32-ia32-msvc@4.52.5':
+    optional: true
+
+  '@rollup/rollup-win32-x64-gnu@4.52.5':
+    optional: true
+
+  '@rollup/rollup-win32-x64-msvc@4.52.5':
+    optional: true
+
+  '@types/babel__core@7.20.5':
+    dependencies:
+      '@babel/parser': 7.28.5
+      '@babel/types': 7.28.5
+      '@types/babel__generator': 7.27.0
+      '@types/babel__template': 7.4.4
+      '@types/babel__traverse': 7.28.0
+
+  '@types/babel__generator@7.27.0':
+    dependencies:
+      '@babel/types': 7.28.5
+
+  '@types/babel__template@7.4.4':
+    dependencies:
+      '@babel/parser': 7.28.5
+      '@babel/types': 7.28.5
+
+  '@types/babel__traverse@7.28.0':
+    dependencies:
+      '@babel/types': 7.28.5
+
+  '@types/d3-array@3.2.2': {}
+
+  '@types/d3-color@3.1.3': {}
+
+  '@types/d3-ease@3.0.2': {}
+
+  '@types/d3-interpolate@3.0.4':
+    dependencies:
+      '@types/d3-color': 3.1.3
+
+  '@types/d3-path@3.1.1': {}
+
+  '@types/d3-scale@4.0.9':
+    dependencies:
+      '@types/d3-time': 3.0.4
+
+  '@types/d3-shape@3.1.7':
+    dependencies:
+      '@types/d3-path': 3.1.1
+
+  '@types/d3-time@3.0.4': {}
+
+  '@types/d3-timer@3.0.2': {}
+
+  '@types/estree@1.0.8': {}
+
+  '@types/node@22.19.0':
+    dependencies:
+      undici-types: 6.21.0
+
+  '@vitejs/plugin-react@5.1.0(vite@6.4.1(@types/node@22.19.0))':
+    dependencies:
+      '@babel/core': 7.28.5
+      '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5)
+      '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5)
+      '@rolldown/pluginutils': 1.0.0-beta.43
+      '@types/babel__core': 7.20.5
+      react-refresh: 0.18.0
+      vite: 6.4.1(@types/node@22.19.0)
+    transitivePeerDependencies:
+      - supports-color
+
+  baseline-browser-mapping@2.8.23: {}
+
+  browserslist@4.27.0:
+    dependencies:
+      baseline-browser-mapping: 2.8.23
+      caniuse-lite: 1.0.30001753
+      electron-to-chromium: 1.5.244
+      node-releases: 2.0.27
+      update-browserslist-db: 1.1.4(browserslist@4.27.0)
+
+  caniuse-lite@1.0.30001753: {}
+
+  clsx@2.1.1: {}
+
+  convert-source-map@2.0.0: {}
+
+  csstype@3.1.3: {}
+
+  d3-array@3.2.4:
+    dependencies:
+      internmap: 2.0.3
+
+  d3-color@3.1.0: {}
+
+  d3-ease@3.0.1: {}
+
+  d3-format@3.1.0: {}
+
+  d3-interpolate@3.0.1:
+    dependencies:
+      d3-color: 3.1.0
+
+  d3-path@3.1.0: {}
+
+  d3-scale@4.0.2:
+    dependencies:
+      d3-array: 3.2.4
+      d3-format: 3.1.0
+      d3-interpolate: 3.0.1
+      d3-time: 3.1.0
+      d3-time-format: 4.1.0
+
+  d3-shape@3.2.0:
+    dependencies:
+      d3-path: 3.1.0
+
+  d3-time-format@4.1.0:
+    dependencies:
+      d3-time: 3.1.0
+
+  d3-time@3.1.0:
+    dependencies:
+      d3-array: 3.2.4
+
+  d3-timer@3.0.1: {}
+
+  debug@4.4.3:
+    dependencies:
+      ms: 2.1.3
+
+  decimal.js-light@2.5.1: {}
+
+  dom-helpers@5.2.1:
+    dependencies:
+      '@babel/runtime': 7.28.4
+      csstype: 3.1.3
+
+  electron-to-chromium@1.5.244: {}
+
+  esbuild@0.25.12:
+    optionalDependencies:
+      '@esbuild/aix-ppc64': 0.25.12
+      '@esbuild/android-arm': 0.25.12
+      '@esbuild/android-arm64': 0.25.12
+      '@esbuild/android-x64': 0.25.12
+      '@esbuild/darwin-arm64': 0.25.12
+      '@esbuild/darwin-x64': 0.25.12
+      '@esbuild/freebsd-arm64': 0.25.12
+      '@esbuild/freebsd-x64': 0.25.12
+      '@esbuild/linux-arm': 0.25.12
+      '@esbuild/linux-arm64': 0.25.12
+      '@esbuild/linux-ia32': 0.25.12
+      '@esbuild/linux-loong64': 0.25.12
+      '@esbuild/linux-mips64el': 0.25.12
+      '@esbuild/linux-ppc64': 0.25.12
+      '@esbuild/linux-riscv64': 0.25.12
+      '@esbuild/linux-s390x': 0.25.12
+      '@esbuild/linux-x64': 0.25.12
+      '@esbuild/netbsd-arm64': 0.25.12
+      '@esbuild/netbsd-x64': 0.25.12
+      '@esbuild/openbsd-arm64': 0.25.12
+      '@esbuild/openbsd-x64': 0.25.12
+      '@esbuild/openharmony-arm64': 0.25.12
+      '@esbuild/sunos-x64': 0.25.12
+      '@esbuild/win32-arm64': 0.25.12
+      '@esbuild/win32-ia32': 0.25.12
+      '@esbuild/win32-x64': 0.25.12
+
+  escalade@3.2.0: {}
+
+  eventemitter3@4.0.7: {}
+
+  fast-equals@5.3.2: {}
+
+  fdir@6.5.0(picomatch@4.0.3):
+    optionalDependencies:
+      picomatch: 4.0.3
+
+  fsevents@2.3.3:
+    optional: true
+
+  gensync@1.0.0-beta.2: {}
+
+  internmap@2.0.3: {}
+
+  js-tokens@4.0.0: {}
+
+  jsesc@3.1.0: {}
+
+  json5@2.2.3: {}
+
+  lodash@4.17.21: {}
+
+  loose-envify@1.4.0:
+    dependencies:
+      js-tokens: 4.0.0
+
+  lru-cache@5.1.1:
+    dependencies:
+      yallist: 3.1.1
+
+  ms@2.1.3: {}
+
+  nanoid@3.3.11: {}
+
+  node-releases@2.0.27: {}
+
+  object-assign@4.1.1: {}
+
+  picocolors@1.1.1: {}
+
+  picomatch@4.0.3: {}
+
+  postcss@8.5.6:
+    dependencies:
+      nanoid: 3.3.11
+      picocolors: 1.1.1
+      source-map-js: 1.2.1
+
+  prop-types@15.8.1:
+    dependencies:
+      loose-envify: 1.4.0
+      object-assign: 4.1.1
+      react-is: 16.13.1
+
+  react-dom@19.2.0(react@19.2.0):
+    dependencies:
+      react: 19.2.0
+      scheduler: 0.27.0
+
+  react-is@16.13.1: {}
+
+  react-refresh@0.18.0: {}
+
+  react-router-dom@6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+    dependencies:
+      '@remix-run/router': 1.23.0
+      react: 19.2.0
+      react-dom: 19.2.0(react@19.2.0)
+      react-router: 6.30.1(react@19.2.0)
+
+  react-router@6.30.1(react@19.2.0):
+    dependencies:
+      '@remix-run/router': 1.23.0
+      react: 19.2.0
+
+  react-smooth@4.0.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+    dependencies:
+      fast-equals: 5.3.2
+      prop-types: 15.8.1
+      react: 19.2.0
+      react-dom: 19.2.0(react@19.2.0)
+      react-transition-group: 4.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+
+  react-transition-group@4.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+    dependencies:
+      '@babel/runtime': 7.28.4
+      dom-helpers: 5.2.1
+      loose-envify: 1.4.0
+      prop-types: 15.8.1
+      react: 19.2.0
+      react-dom: 19.2.0(react@19.2.0)
+
+  react@19.2.0: {}
+
+  recharts-scale@0.4.5:
+    dependencies:
+      decimal.js-light: 2.5.1
+
+  recharts@2.12.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+    dependencies:
+      clsx: 2.1.1
+      eventemitter3: 4.0.7
+      lodash: 4.17.21
+      react: 19.2.0
+      react-dom: 19.2.0(react@19.2.0)
+      react-is: 16.13.1
+      react-smooth: 4.0.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+      recharts-scale: 0.4.5
+      tiny-invariant: 1.3.3
+      victory-vendor: 36.9.2
+
+  rollup@4.52.5:
+    dependencies:
+      '@types/estree': 1.0.8
+    optionalDependencies:
+      '@rollup/rollup-android-arm-eabi': 4.52.5
+      '@rollup/rollup-android-arm64': 4.52.5
+      '@rollup/rollup-darwin-arm64': 4.52.5
+      '@rollup/rollup-darwin-x64': 4.52.5
+      '@rollup/rollup-freebsd-arm64': 4.52.5
+      '@rollup/rollup-freebsd-x64': 4.52.5
+      '@rollup/rollup-linux-arm-gnueabihf': 4.52.5
+      '@rollup/rollup-linux-arm-musleabihf': 4.52.5
+      '@rollup/rollup-linux-arm64-gnu': 4.52.5
+      '@rollup/rollup-linux-arm64-musl': 4.52.5
+      '@rollup/rollup-linux-loong64-gnu': 4.52.5
+      '@rollup/rollup-linux-ppc64-gnu': 4.52.5
+      '@rollup/rollup-linux-riscv64-gnu': 4.52.5
+      '@rollup/rollup-linux-riscv64-musl': 4.52.5
+      '@rollup/rollup-linux-s390x-gnu': 4.52.5
+      '@rollup/rollup-linux-x64-gnu': 4.52.5
+      '@rollup/rollup-linux-x64-musl': 4.52.5
+      '@rollup/rollup-openharmony-arm64': 4.52.5
+      '@rollup/rollup-win32-arm64-msvc': 4.52.5
+      '@rollup/rollup-win32-ia32-msvc': 4.52.5
+      '@rollup/rollup-win32-x64-gnu': 4.52.5
+      '@rollup/rollup-win32-x64-msvc': 4.52.5
+      fsevents: 2.3.3
+
+  scheduler@0.27.0: {}
+
+  semver@6.3.1: {}
+
+  source-map-js@1.2.1: {}
+
+  tiny-invariant@1.3.3: {}
+
+  tinyglobby@0.2.15:
+    dependencies:
+      fdir: 6.5.0(picomatch@4.0.3)
+      picomatch: 4.0.3
+
+  typescript@5.8.3: {}
+
+  undici-types@6.21.0: {}
+
+  update-browserslist-db@1.1.4(browserslist@4.27.0):
+    dependencies:
+      browserslist: 4.27.0
+      escalade: 3.2.0
+      picocolors: 1.1.1
+
+  victory-vendor@36.9.2:
+    dependencies:
+      '@types/d3-array': 3.2.2
+      '@types/d3-ease': 3.0.2
+      '@types/d3-interpolate': 3.0.4
+      '@types/d3-scale': 4.0.9
+      '@types/d3-shape': 3.1.7
+      '@types/d3-time': 3.0.4
+      '@types/d3-timer': 3.0.2
+      d3-array: 3.2.4
+      d3-ease: 3.0.1
+      d3-interpolate: 3.0.1
+      d3-scale: 4.0.2
+      d3-shape: 3.2.0
+      d3-time: 3.1.0
+      d3-timer: 3.0.1
+
+  vite@6.4.1(@types/node@22.19.0):
+    dependencies:
+      esbuild: 0.25.12
+      fdir: 6.5.0(picomatch@4.0.3)
+      picomatch: 4.0.3
+      postcss: 8.5.6
+      rollup: 4.52.5
+      tinyglobby: 0.2.15
+    optionalDependencies:
+      '@types/node': 22.19.0
+      fsevents: 2.3.3
+
+  yallist@3.1.1: {}

+ 25 - 0
routes.tsx

@@ -0,0 +1,25 @@
+import React from 'react';
+import { Routes, Route } from 'react-router-dom';
+import LandingPage from './LandingPage';
+import ContactPage from './ContactPage';
+import EditPage from './EditPage';
+import CloneChatPage from './CloneChatPage';
+import type { ContactData } from './types';
+
+interface AppRoutesProps {
+  contactData: ContactData;
+  onSave: (data: ContactData) => void;
+}
+
+const AppRoutes: React.FC<AppRoutesProps> = ({ contactData, onSave }) => {
+  return (
+    <Routes>
+      <Route path="/" element={<LandingPage />} />
+      <Route path="/contact" element={<ContactPage data={contactData} />} />
+      <Route path="/edit" element={<EditPage initialData={contactData} onSave={onSave} />} />
+      <Route path="/clone-chat" element={<CloneChatPage />} />
+    </Routes>
+  );
+};
+
+export default AppRoutes;

+ 44 - 0
src/App.tsx

@@ -0,0 +1,44 @@
+import React, { useState } from 'react';
+import AppRoutes from './routes';
+import type { ContactData } from './types';
+
+const App: React.FC = () => {
+  // Mock data for the contact, now managed by state
+  const [contactData, setContactData] = useState<ContactData>({
+    name: 'Alex Doe',
+    avatarUrl: 'https://picsum.photos/200',
+    virtualId: 'VID-2024-8A4B-C7D2-F1E0',
+    bio: 'Senior Frontend Engineer with a passion for creating beautiful and functional user interfaces. Expert in React, TypeScript, and modern web technologies. In my free time, I explore the intersection of AI and art.',
+    socials: {
+      linkedin: 'https://www.linkedin.com/in/alex-doe',
+      x: 'https://x.com/alex_doe',
+      github: 'https://github.com/alexdoe',
+      tiktok: 'https://www.tiktok.com/@alexdoe.dev',
+      instagram: 'https://www.instagram.com/alex.doe',
+      facebook: 'https://www.facebook.com/alex.doe',
+      discord: 'alex.doe#1234',
+    },
+    authenticityScore: {
+      videoVerification: 15, // max 15
+      socialBinding: 45, // max 45
+      cloneMaturity: 48, // max 60
+    },
+    videoIntroUrl: 'https://storage.googleapis.com/web-dev-assets/video-introduction-demo.mp4',
+    videoPosterUrl: 'https://picsum.photos/seed/video-poster/800/450',
+  });
+
+  const handleSave = (newData: ContactData) => {
+    setContactData(newData);
+  };
+
+  return (
+    <div className="min-h-screen bg-slate-50 font-sans text-slate-800">
+      <AppRoutes contactData={contactData} onSave={handleSave} />
+      <footer className="text-center p-4 text-slate-500 text-sm">
+        <p>&copy; 2024 Virtual Identity System. All rights reserved.</p>
+      </footer>
+    </div>
+  );
+};
+
+export default App;

+ 79 - 0
src/components/AuthenticityScore.tsx

@@ -0,0 +1,79 @@
+import React from 'react';
+import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts';
+
+interface AuthenticityScoreProps {
+  videoVerificationScore: number;
+  socialBindingScore: number;
+  cloneMaturityScore: number;
+}
+
+const ScoreBreakdownBar: React.FC<{ label: string; score: number; maxScore: number; color: string }> = ({ label, score, maxScore, color }) => {
+    const colorVariants: {[key: string]: string} = {
+        'text-blue-500': 'from-blue-400 to-blue-600',
+        'text-indigo-500': 'from-indigo-400 to-indigo-600',
+        'text-green-500': 'from-green-400 to-green-600',
+    }
+    return (
+        <div>
+            <div className="flex justify-between items-center mb-1">
+            <span className="text-sm font-medium text-slate-600">{label}</span>
+            <span className={`text-sm font-bold ${color}`}>{score} / {maxScore}</span>
+            </div>
+            <div className="w-full bg-slate-200 rounded-full h-2.5">
+            <div className={`bg-gradient-to-r ${colorVariants[color] || 'from-slate-400 to-slate-600'} h-2.5 rounded-full`} style={{ width: `${(score / maxScore) * 100}%` }}></div>
+            </div>
+        </div>
+    );
+};
+
+const AuthenticityScore: React.FC<AuthenticityScoreProps> = ({ videoVerificationScore, socialBindingScore, cloneMaturityScore }) => {
+  const totalScore = videoVerificationScore + socialBindingScore + cloneMaturityScore;
+  const maxTotalScore = 120; // 15 + 45 + 60
+  const scoreData = [
+    { name: 'Score', value: totalScore },
+    { name: 'Remaining', value: maxTotalScore - totalScore },
+  ];
+  const COLORS = ['#3b82f6', '#e5e7eb'];
+
+  return (
+    <div className="bg-white rounded-2xl border border-slate-200 p-6">
+      <h2 className="text-xl font-bold text-slate-800 mb-4 text-center">Authenticity Score</h2>
+      <div className="flex flex-col md:flex-row items-center gap-6 md:gap-8">
+        <div className="w-48 h-48 relative">
+          <ResponsiveContainer width="100%" height="100%">
+            <PieChart>
+              <Pie
+                data={scoreData}
+                cx="50%"
+                cy="50%"
+                innerRadius={60}
+                outerRadius={80}
+                startAngle={90}
+                endAngle={450}
+                paddingAngle={0}
+                dataKey="value"
+                cornerRadius={10}
+              >
+                {scoreData.map((entry, index) => (
+                  <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} stroke="none"/>
+                ))}
+              </Pie>
+              <Tooltip formatter={(value: number, name: string) => [value, '']} />
+            </PieChart>
+          </ResponsiveContainer>
+          <div className="absolute inset-0 flex flex-col items-center justify-center">
+            <span className="text-4xl font-extrabold text-blue-600">{totalScore}</span>
+            <span className="text-slate-500">/ {maxTotalScore}</span>
+          </div>
+        </div>
+        <div className="flex-1 w-full space-y-4">
+          <ScoreBreakdownBar label="Video Verification" score={videoVerificationScore} maxScore={15} color="text-green-500" />
+          <ScoreBreakdownBar label="Social Account Binding" score={socialBindingScore} maxScore={45} color="text-blue-500" />
+          <ScoreBreakdownBar label="Digital Clone Maturity" score={cloneMaturityScore} maxScore={60} color="text-indigo-500" />
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default AuthenticityScore;

+ 46 - 0
src/components/VirtualIdCard.tsx

@@ -0,0 +1,46 @@
+import React from 'react';
+import ShieldIcon from './icons/ShieldIcon';
+
+interface VirtualIdCardProps {
+  name: string;
+  avatarUrl: string;
+  virtualId: string;
+  totalScore: number;
+}
+
+const VirtualIdCard: React.FC<VirtualIdCardProps> = ({ name, avatarUrl, virtualId, totalScore }) => {
+  return (
+    <div className="bg-gradient-to-br from-slate-900 via-slate-800 to-sky-900 rounded-2xl shadow-2xl p-6 text-white overflow-hidden relative group">
+      {/* Decorative Circles */}
+      <div className="absolute -top-10 -right-10 w-40 h-40 bg-sky-500/10 rounded-full"></div>
+      <div className="absolute -bottom-16 -left-12 w-48 h-48 bg-slate-500/10 rounded-full"></div>
+
+      <div className="relative z-10 flex flex-col sm:flex-row items-center gap-6">
+        <img
+          src={avatarUrl}
+          alt={name}
+          className="w-24 h-24 rounded-full border-4 border-slate-600 shadow-lg object-cover"
+        />
+        <div className="text-center sm:text-left">
+          <h1 className="text-3xl font-bold tracking-wide">{name}</h1>
+          <p className="text-sm text-slate-400 font-mono tracking-widest mt-1">{virtualId}</p>
+        </div>
+      </div>
+      
+      {/* Holographic Security Tag */}
+      <div 
+        className="absolute bottom-4 right-6 w-14 h-14 flex items-center justify-center z-20" 
+        aria-label="Holographic Security Seal"
+      >
+        <div className="absolute inset-0 rounded-full bg-slate-700/30 backdrop-blur-sm border border-white/10"></div>
+        <div className="absolute inset-1 rounded-full border border-white/10 animate-[spin_8s_linear_infinite]"></div>
+        <div className={`relative w-12 h-12 rounded-full flex items-center justify-center overflow-hidden ${totalScore >= 30 ? 'holographic-gleam' : ''}`}>
+          <div className="absolute inset-0 bg-gradient-to-br from-sky-400/20 to-purple-500/20"></div>
+          <ShieldIcon />
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default VirtualIdCard;

+ 9 - 0
src/components/icons/ArrowRightIcon.tsx

@@ -0,0 +1,9 @@
+import React from 'react';
+
+const ArrowRightIcon: React.FC<{className?: string}> = ({ className }) => (
+  <svg xmlns="http://www.w3.org/2000/svg" className={className || "h-6 w-6"} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="3">
+    <path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
+  </svg>
+);
+
+export default ArrowRightIcon;

+ 27 - 0
src/components/icons/BrainCircuitIcon.tsx

@@ -0,0 +1,27 @@
+import React from 'react';
+
+const BrainCircuitIcon: React.FC = () => (
+  <svg 
+    xmlns="http://www.w3.org/2000/svg" 
+    width="28" 
+    height="28" 
+    viewBox="0 0 24 24" 
+    fill="none" 
+    stroke="currentColor" 
+    strokeWidth="2" 
+    strokeLinecap="round" 
+    strokeLinejoin="round"
+  >
+    <path d="M12 8V4m-2 2h4"/>
+    <path d="M12 20v-4m-2 2h4"/>
+    <path d="M8 12H4m2-2v4"/>
+    <path d="M20 12h-4m2-2v4"/>
+    <path d="M9.4 9.4l-2.8-2.8"/>
+    <path d="M17.4 17.4l-2.8-2.8"/>
+    <path d="M9.4 14.6l-2.8 2.8"/>
+    <path d="M17.4 6.6l-2.8 2.8"/>
+    <circle cx="12" cy="12" r="3"/>
+  </svg>
+);
+
+export default BrainCircuitIcon;

+ 14 - 0
src/components/icons/CertificateIcon.tsx

@@ -0,0 +1,14 @@
+import React from 'react';
+
+const CertificateIcon: React.FC = () => (
+  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
+    <path d="M12 21l-8-4.5v-9l8 4.5 8-4.5v9L12 21z"></path>
+    <path d="M12 12l8-4.5"></path>
+    <path d="M12 12v9"></path>
+    <path d="M12 12L4 7.5"></path>
+    <path d="M4 7.5l8-4.5 8 4.5"></path>
+    <path d="M4 7.5v9"></path>
+  </svg>
+);
+
+export default CertificateIcon;

+ 8 - 0
src/components/icons/DiscordIcon.tsx

@@ -0,0 +1,8 @@
+import React from 'react';
+
+const DiscordIcon: React.FC = () => (
+    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+      <path d="M20.317 4.36982C18.9185 3.59382 17.3595 3.03382 15.704 2.71382C15.5115 3.13382 15.2895 3.78382 15.0445 4.36982C13.2255 3.93382 11.3815 3.93382 9.5625 4.36982C9.3175 3.78382 9.0955 3.13382 8.903 2.71382C7.2475 3.03382 5.6885 3.59382 4.29 4.36982C1.693 8.16382 1.258 11.8218 2.011 15.3468C3.6555 16.7118 5.5475 17.5878 7.5755 18.1068C7.9045 17.5878 8.1945 17.0388 8.4555 16.4608C7.7375 16.2408 7.0485 15.9618 6.4055 15.6228C6.551 15.5428 6.6965 15.4578 6.8325 15.3728C10.4595 17.0138 14.1535 17.0138 17.771 15.3728C17.907 15.4578 18.0525 15.5428 18.198 15.6228C17.555 15.9618 16.866 16.2408 16.148 16.4608C16.409 17.0388 16.699 17.5878 17.028 18.1068C19.056 17.5878 20.948 16.7118 22.5925 15.3468C23.374 11.4328 22.883 7.82882 20.317 4.36982ZM14.1225 13.3308C13.1235 13.3308 12.2895 12.4818 12.2895 11.4708C12.2895 10.4598 13.114 9.61082 14.1225 9.61082C15.131 9.61082 15.965 10.4598 15.9555 11.4708C15.9555 12.4818 15.131 13.3308 14.1225 13.3308ZM10.4785 13.3308C9.4795 13.3308 8.6455 12.4818 8.6455 11.4708C8.6455 10.4598 9.47 9.61082 10.4785 9.61082C11.487 9.61082 12.321 10.4598 12.321 11.4708C12.321 12.4818 11.487 13.3308 10.4785 13.3308Z" />
+    </svg>
+);
+export default DiscordIcon;

+ 9 - 0
src/components/icons/FacebookIcon.tsx

@@ -0,0 +1,9 @@
+import React from 'react';
+
+const FacebookIcon: React.FC = () => (
+  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+    <path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
+  </svg>
+);
+
+export default FacebookIcon;

+ 9 - 0
src/components/icons/GithubIcon.tsx

@@ -0,0 +1,9 @@
+import React from 'react';
+
+const GithubIcon: React.FC = () => (
+  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+    <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
+  </svg>
+);
+
+export default GithubIcon;

+ 9 - 0
src/components/icons/InstagramIcon.tsx

@@ -0,0 +1,9 @@
+import React from 'react';
+
+const InstagramIcon: React.FC = () => (
+  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+    <path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.85s-.011 3.584-.069 4.85c-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07s-3.584-.012-4.85-.07c-3.252-.148-4.771-1.691-4.919-4.919-.058-1.265-.069-1.645-.069-4.85s.011-3.584.069-4.85c.149-3.225 1.664-4.771 4.919-4.919 1.266-.057 1.644-.069 4.85-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948s.014 3.667.072 4.947c.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072s3.667-.014 4.947-.072c4.358-.2 6.78-2.618 6.98-6.98.059-1.281.073-1.689.073-4.948s-.014-3.667-.072-4.947c-.2-4.358-2.618-6.78-6.98-6.98-1.281-.059-1.689-.073-4.948-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.162 6.162 6.162 6.162-2.759 6.162-6.162-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4s1.791-4 4-4 4 1.79 4 4-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.441 1.441 1.441 1.441-.645 1.441-1.441-.645-1.44-1.441-1.44z"/>
+  </svg>
+);
+
+export default InstagramIcon;

+ 9 - 0
src/components/icons/LinkedInIcon.tsx

@@ -0,0 +1,9 @@
+import React from 'react';
+
+const LinkedInIcon: React.FC = () => (
+  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+    <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667h-3.554v-12h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.281zM8.077 6.427h.022c1.225 0 2.002-.824 2.002-1.852C10.08 3.506 9.28 2.6 8.1 2.6S6.1 3.506 6.1 4.575c0 1.028.777 1.852 1.977 1.852zM6.35 8.452h3.554v12H6.35v-12z" />
+  </svg>
+);
+
+export default LinkedInIcon;

+ 21 - 0
src/components/icons/ShieldIcon.tsx

@@ -0,0 +1,21 @@
+import React from 'react';
+
+const ShieldIcon: React.FC = () => (
+    <svg 
+        xmlns="http://www.w3.org/2000/svg" 
+        width="32" 
+        height="32" 
+        viewBox="0 0 24 24" 
+        fill="none" 
+        stroke="currentColor" 
+        strokeWidth="1.5" 
+        strokeLinecap="round" 
+        strokeLinejoin="round"
+        className="opacity-80"
+        aria-hidden="true"
+    >
+        <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
+    </svg>
+);
+
+export default ShieldIcon;

+ 16 - 0
src/components/icons/TiktokIcon.tsx

@@ -0,0 +1,16 @@
+import React from 'react';
+
+const TiktokIcon: React.FC = () => (
+  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+    <defs>
+      <path id="tiktok-path-a" d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-2.43.05-4.86-.95-6.69-2.81-1.77-1.8-2.6-4.15-2.47-6.54.04-1.38.36-2.76.94-4.04 1.01-2.2 2.82-3.95 5.05-4.78.68-.26 1.38-.49 2.1-.7.02-3.46.02-6.92.01-10.38z" />
+    </defs>
+    <g>
+      <use href="#tiktok-path-a" fill="#FE2C55" transform="translate(-1.2, -1.2)" />
+      <use href="#tiktok-path-a" fill="#25F4EE" transform="translate(1.2, 1.2)" />
+      <use href="#tiktok-path-a" fill="white" />
+    </g>
+  </svg>
+);
+
+export default TiktokIcon;

+ 9 - 0
src/components/icons/UploadIcon.tsx

@@ -0,0 +1,9 @@
+import React from 'react';
+
+const UploadIcon: React.FC = () => (
+  <svg xmlns="http://www.w3.org/2000/svg" className="mx-auto h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
+    <path strokeLinecap="round" strokeLinejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
+  </svg>
+);
+
+export default UploadIcon;

+ 9 - 0
src/components/icons/VerifiedIcon.tsx

@@ -0,0 +1,9 @@
+import React from 'react';
+
+const VerifiedIcon: React.FC<{ className?: string }> = ({ className = 'h-5 w-5' }) => (
+    <svg xmlns="http://www.w3.org/2000/svg" className={className} viewBox="0 0 20 20" fill="currentColor">
+        <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
+    </svg>
+);
+
+export default VerifiedIcon;

+ 9 - 0
src/components/icons/XIcon.tsx

@@ -0,0 +1,9 @@
+import React from 'react';
+
+const XIcon: React.FC = () => (
+  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
+    <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
+  </svg>
+);
+
+export default XIcon;

+ 18 - 0
src/index.tsx

@@ -0,0 +1,18 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { BrowserRouter } from 'react-router-dom';
+import App from './App';
+
+const rootElement = document.getElementById('root');
+if (!rootElement) {
+  throw new Error("Could not find root element to mount to");
+}
+
+const root = ReactDOM.createRoot(rootElement);
+root.render(
+  <React.StrictMode>
+    <BrowserRouter>
+      <App />
+    </BrowserRouter>
+  </React.StrictMode>
+);

+ 118 - 0
src/pages/CloneChat/CloneChatPage.tsx

@@ -0,0 +1,118 @@
+import React, { useState, useRef, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+
+interface Message {
+  id: number;
+  text: string;
+  sender: 'user' | 'clone';
+}
+
+const CloneChatPage: React.FC = () => {
+  const navigate = useNavigate();
+  const [messages, setMessages] = useState<Message[]>([
+    { id: 1, text: "Hello! You can start training me by talking to me. What's on your mind?", sender: 'clone' }
+  ]);
+  const [inputValue, setInputValue] = useState('');
+  const [isTyping, setIsTyping] = useState(false);
+  const messagesEndRef = useRef<HTMLDivElement>(null);
+
+  const scrollToBottom = () => {
+    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+  };
+
+  useEffect(scrollToBottom, [messages, isTyping]);
+
+  const handleSendMessage = (e: React.FormEvent) => {
+    e.preventDefault();
+    if (inputValue.trim() === '') return;
+
+    const userMessage: Message = {
+      id: Date.now(),
+      text: inputValue,
+      sender: 'user',
+    };
+
+    setMessages(prev => [...prev, userMessage]);
+    setInputValue('');
+    setIsTyping(true);
+
+    // Simulate clone response
+    setTimeout(() => {
+      const cloneResponse: Message = {
+        id: Date.now() + 1,
+        text: `That's an interesting point. It reminds me of how you once said... (simulated response based on your persona).`,
+        sender: 'clone',
+      };
+      setIsTyping(false);
+      setMessages(prev => [...prev, cloneResponse]);
+    }, 1500 + Math.random() * 1000);
+  };
+
+  return (
+    <div className="flex flex-col h-[calc(100vh-60px)] max-w-3xl mx-auto bg-white rounded-2xl overflow-hidden border border-slate-200">
+        {/* Header */}
+        <header className="flex items-center justify-between p-4 border-b border-slate-200 bg-slate-50 flex-shrink-0">
+            <h1 className="text-xl font-bold text-slate-800">Train My Digital Clone</h1>
+            <button
+                onClick={() => navigate('/edit')}
+                className="px-4 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-300 rounded-md hover:bg-slate-100"
+            >
+                Back to Edit
+            </button>
+        </header>
+
+        {/* Chat Area */}
+        <main className="flex-1 overflow-y-auto p-6 space-y-4">
+            {messages.map(msg => (
+                <div key={msg.id} className={`flex items-end gap-2 ${msg.sender === 'user' ? 'justify-end' : 'justify-start'}`}>
+                    {msg.sender === 'clone' && (
+                        <div className="w-8 h-8 rounded-full bg-gradient-to-br from-indigo-500 to-purple-600 flex-shrink-0"></div>
+                    )}
+                    <div className={`max-w-xs md:max-w-md lg:max-w-lg px-4 py-2 rounded-2xl ${msg.sender === 'user' ? 'bg-blue-600 text-white rounded-br-none' : 'bg-slate-200 text-slate-800 rounded-bl-none'}`}>
+                       <p className="text-sm leading-relaxed">{msg.text}</p>
+                    </div>
+                </div>
+            ))}
+             {isTyping && (
+                <div className="flex items-end gap-2 justify-start">
+                    <div className="w-8 h-8 rounded-full bg-gradient-to-br from-indigo-500 to-purple-600 flex-shrink-0"></div>
+                    <div className="px-4 py-2 rounded-2xl bg-slate-200 rounded-bl-none">
+                        <div className="flex items-center justify-center space-x-1">
+                            <span className="w-1.5 h-1.5 bg-slate-500 rounded-full animate-bounce [animation-delay:-0.3s]"></span>
+                            <span className="w-1.5 h-1.5 bg-slate-500 rounded-full animate-bounce [animation-delay:-0.15s]"></span>
+                            <span className="w-1.5 h-1.5 bg-slate-500 rounded-full animate-bounce"></span>
+                        </div>
+                    </div>
+                </div>
+            )}
+            <div ref={messagesEndRef} />
+        </main>
+
+        {/* Input Form */}
+        <footer className="p-4 border-t border-slate-200 bg-slate-50 flex-shrink-0">
+            <form onSubmit={handleSendMessage} className="flex items-center gap-3">
+                <input
+                    type="text"
+                    value={inputValue}
+                    onChange={e => setInputValue(e.target.value)}
+                    placeholder="Talk to your clone..."
+                    className="flex-1 w-full px-4 py-2 bg-white border border-slate-300 rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500"
+                    autoComplete="off"
+                />
+                <button
+                    type="submit"
+                    aria-label="Send message"
+                    className="w-10 h-10 flex-shrink-0 bg-blue-600 text-white rounded-full flex items-center justify-center hover:bg-blue-700 disabled:bg-blue-300 transition-colors"
+                    disabled={inputValue.trim() === ''}
+                >
+                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="w-5 h-5">
+                      <path d="M3.105 2.289a.75.75 0 00-.826.95l1.414 4.925A1.5 1.5 0 005.135 9.25h6.115a.75.75 0 010 1.5H5.135a1.5 1.5 0 00-1.442 1.086L2.279 16.76a.75.75 0 00.826.95l14.433-6.414a.75.75 0 000-1.392L3.105 2.289z" />
+                    </svg>
+                </button>
+            </form>
+        </footer>
+    </div>
+  );
+};
+
+export default CloneChatPage;

+ 54 - 0
src/pages/Contact/ContactPage.tsx

@@ -0,0 +1,54 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import VirtualIdCard from '../../components/VirtualIdCard';
+import AuthenticityScore from '../../components/AuthenticityScore';
+import Bio from './components/Bio';
+import SocialLinks from './components/SocialLinks';
+import VideoIntroduction from './components/VideoIntroduction';
+import type { ContactData } from '../../types';
+
+interface ContactPageProps {
+  data: ContactData;
+}
+
+const ContactPage: React.FC<ContactPageProps> = ({ data }) => {
+  const totalScore = data.authenticityScore.videoVerification + data.authenticityScore.socialBinding + data.authenticityScore.cloneMaturity;
+
+  return (
+    <main className="max-w-3xl mx-auto p-4 sm:p-6 md:p-8">
+      <div className="space-y-8">
+        <div className="flex justify-between items-center mb-4">
+            <h1 className="text-2xl font-bold text-slate-800">Contact Profile</h1>
+            <div>
+                <Link to="/" className="text-sm text-slate-600 hover:text-blue-600 mr-4">Home</Link>
+                <Link to="/edit" className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700">Edit Page</Link>
+            </div>
+        </div>
+        <VirtualIdCard 
+          name={data.name}
+          avatarUrl={data.avatarUrl}
+          virtualId={data.virtualId}
+          totalScore={totalScore}
+        />
+        
+        <AuthenticityScore 
+          videoVerificationScore={data.authenticityScore.videoVerification}
+          socialBindingScore={data.authenticityScore.socialBinding}
+          cloneMaturityScore={data.authenticityScore.cloneMaturity}
+        />
+
+        <Bio text={data.bio} />
+
+        <VideoIntroduction
+          videoUrl={data.videoIntroUrl}
+          posterUrl={data.videoPosterUrl}
+        />
+
+        <SocialLinks links={data.socials} />
+
+      </div>
+    </main>
+  );
+};
+
+export default ContactPage;

+ 18 - 0
src/pages/Contact/components/Bio.tsx

@@ -0,0 +1,18 @@
+import React from 'react';
+
+interface BioProps {
+  text: string;
+}
+
+const Bio: React.FC<BioProps> = ({ text }) => {
+  return (
+    <div className="bg-white rounded-2xl border border-slate-200 p-6">
+      <h2 className="text-xl font-bold text-slate-800 mb-3">About Me</h2>
+      <p className="text-slate-600 leading-relaxed">
+        {text}
+      </p>
+    </div>
+  );
+};
+
+export default Bio;

+ 38 - 0
src/pages/Contact/components/Certifications.tsx

@@ -0,0 +1,38 @@
+import React from 'react';
+import type { Certification } from '../../../types';
+
+interface CertificationsProps {
+  certifications: Certification[];
+}
+
+const Certifications: React.FC<CertificationsProps> = ({ certifications }) => {
+  if (!certifications || certifications.length === 0) {
+    return null;
+  }
+
+  return (
+    <div className="bg-white rounded-2xl border border-slate-200 p-6">
+      <h2 className="text-xl font-bold text-slate-800 mb-4">Licenses & Certifications</h2>
+      <ul className="space-y-4">
+        {certifications.map((cert, index) => (
+          <li key={index} className="flex items-start gap-4 pt-4 first:pt-0 border-t border-slate-100 first:border-t-0">
+            <div className="flex-shrink-0 w-12 h-12 bg-slate-100 rounded-md flex items-center justify-center mt-1 p-1">
+              <img 
+                src={cert.imageUrl} 
+                alt={`${cert.issuer} logo`} 
+                className="w-full h-full object-contain rounded-sm"
+              />
+            </div>
+            <div>
+              <h3 className="font-semibold text-slate-800">{cert.name}</h3>
+              <p className="text-sm text-slate-600">{cert.issuer}</p>
+              <p className="text-xs text-slate-400 mt-1">{cert.date}</p>
+            </div>
+          </li>
+        ))}
+      </ul>
+    </div>
+  );
+};
+
+export default Certifications;

+ 71 - 0
src/pages/Contact/components/SocialLinks.tsx

@@ -0,0 +1,71 @@
+import React from 'react';
+import type { SocialLinksData } from '../../../types';
+import LinkedInIcon from '../../../components/icons/LinkedInIcon';
+import XIcon from '../../../components/icons/XIcon';
+import GithubIcon from '../../../components/icons/GithubIcon';
+import TiktokIcon from '../../../components/icons/TiktokIcon';
+import VerifiedIcon from '../../../components/icons/VerifiedIcon';
+import ArrowRightIcon from '../../../components/icons/ArrowRightIcon';
+import InstagramIcon from '../../../components/icons/InstagramIcon';
+import FacebookIcon from '../../../components/icons/FacebookIcon';
+import DiscordIcon from '../../../components/icons/DiscordIcon';
+
+interface SocialLinksProps {
+  links: SocialLinksData;
+}
+
+const socialPlatforms = [
+  { key: 'linkedin', name: 'LinkedIn', icon: <LinkedInIcon />, iconBg: 'bg-gradient-to-br from-blue-500 to-sky-400' },
+  { key: 'x', name: 'X (Twitter)', icon: <XIcon />, iconBg: 'bg-gradient-to-br from-slate-900 to-slate-700' },
+  { key: 'github', name: 'GitHub', icon: <GithubIcon />, iconBg: 'bg-gradient-to-br from-slate-900 to-slate-700' },
+  { key: 'tiktok', name: 'TikTok', icon: <TiktokIcon />, iconBg: 'bg-gradient-to-br from-black via-rose-500 to-cyan-400' },
+  { key: 'instagram', name: 'Instagram', icon: <InstagramIcon />, iconBg: 'bg-gradient-to-br from-purple-500 via-pink-500 to-yellow-500' },
+  { key: 'facebook', name: 'Facebook', icon: <FacebookIcon />, iconBg: 'bg-gradient-to-br from-blue-700 to-blue-500' },
+  { key: 'discord', name: 'Discord', icon: <DiscordIcon />, iconBg: 'bg-gradient-to-br from-indigo-600 to-purple-500' },
+] as const;
+
+
+const SocialLinks: React.FC<SocialLinksProps> = ({ links }) => {
+  return (
+    <div className="bg-white rounded-2xl border border-slate-200 p-6">
+      <h2 className="text-xl font-bold text-slate-800 mb-4">Verified Social Accounts</h2>
+      <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
+        {socialPlatforms.map((platform) => {
+          const link = links[platform.key];
+          if (!link) return null;
+          return (
+            <a
+              key={platform.key}
+              href={link}
+              target="_blank"
+              rel="noopener noreferrer"
+              className={`group flex items-center justify-between p-4 rounded-xl bg-slate-50 transition-colors duration-300 ease-in-out hover:bg-slate-100`}
+            >
+              <div className="flex items-center overflow-hidden">
+                <div className={`flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center text-white ${platform.iconBg}`}>
+                    {platform.icon}
+                </div>
+                <div className="ml-4">
+                    <div className="flex items-center">
+                        <h3 className="font-bold text-slate-800 text-sm">{platform.name}</h3>
+                        <div className="ml-2 flex items-center bg-green-100 text-green-800 text-xs font-medium px-2 py-0.5 rounded-full">
+                            <VerifiedIcon className="w-3 h-3 mr-1" />
+                            Verified
+                        </div>
+                    </div>
+                    <p className="text-xs text-slate-500 font-mono truncate max-w-[120px] sm:max-w-xs">{link.replace(/^https?:\/\/(www\.)?/, '')}</p>
+                </div>
+              </div>
+
+              <div className="text-slate-400 group-hover:text-slate-800 transition-transform duration-300 group-hover:translate-x-1">
+                <ArrowRightIcon className="w-4 h-4" />
+              </div>
+            </a>
+          );
+        })}
+      </div>
+    </div>
+  );
+};
+
+export default SocialLinks;

+ 28 - 0
src/pages/Contact/components/VideoIntroduction.tsx

@@ -0,0 +1,28 @@
+import React from 'react';
+
+interface VideoIntroductionProps {
+  videoUrl: string;
+  posterUrl: string;
+}
+
+const VideoIntroduction: React.FC<VideoIntroductionProps> = ({ videoUrl, posterUrl }) => {
+  return (
+    <div className="bg-white rounded-2xl border border-slate-200 p-6">
+      <h2 className="text-xl font-bold text-slate-800 mb-4">Video Introduction</h2>
+      <div className="aspect-video w-full">
+        <video
+          controls
+          poster={posterUrl}
+          className="w-full h-full rounded-lg object-cover shadow-md bg-slate-200"
+          preload="metadata"
+          aria-label="Video Introduction"
+        >
+          <source src={videoUrl} type="video/mp4" />
+          <p>Your browser does not support the video tag. You can <a href={videoUrl} download className="text-blue-600 hover:underline">download the video</a> instead.</p>
+        </video>
+      </div>
+    </div>
+  );
+};
+
+export default VideoIntroduction;

+ 368 - 0
src/pages/Edit/EditPage.tsx

@@ -0,0 +1,368 @@
+import React, { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import type { ContactData, SocialLinksData } from '../../types';
+import LinkedInIcon from '../../components/icons/LinkedInIcon';
+import XIcon from '../../components/icons/XIcon';
+import GithubIcon from '../../components/icons/GithubIcon';
+import TiktokIcon from '../../components/icons/TiktokIcon';
+import InstagramIcon from '../../components/icons/InstagramIcon';
+import FacebookIcon from '../../components/icons/FacebookIcon';
+import DiscordIcon from '../../components/icons/DiscordIcon';
+import VerifiedIcon from '../../components/icons/VerifiedIcon';
+import VirtualIdCard from '../../components/VirtualIdCard';
+import UploadIcon from '../../components/icons/UploadIcon';
+import AuthenticityScore from '../../components/AuthenticityScore';
+import BrainCircuitIcon from '../../components/icons/BrainCircuitIcon';
+import ArrowRightIcon from '../../components/icons/ArrowRightIcon';
+
+interface EditPageProps {
+  initialData: ContactData;
+  onSave: (data: ContactData) => void;
+}
+
+// Reusable Section Component for editable fields
+const EditSection: React.FC<{title: string, children: React.ReactNode}> = ({ title, children }) => (
+    <div className="bg-white rounded-2xl border border-slate-200 p-6 space-y-4">
+        <h2 className="text-xl font-bold text-slate-800 mb-4">{title}</h2>
+        {children}
+    </div>
+);
+
+const socialPlatforms = [
+    { 
+        key: 'linkedin', name: 'LinkedIn', icon: <LinkedInIcon />,
+        brandClasses: {
+            iconBg: 'bg-gradient-to-br from-blue-500 to-sky-400',
+            button: 'bg-blue-600 hover:bg-blue-700',
+        }
+    },
+    { 
+        key: 'x', name: 'X (Twitter)', icon: <XIcon />,
+        brandClasses: {
+            iconBg: 'bg-gradient-to-br from-slate-900 to-slate-700',
+            button: 'bg-slate-800 hover:bg-slate-900',
+        }
+    },
+    { 
+        key: 'github', name: 'GitHub', icon: <GithubIcon />,
+        brandClasses: {
+            iconBg: 'bg-gradient-to-br from-slate-900 to-slate-700',
+            button: 'bg-slate-800 hover:bg-slate-900',
+        }
+    },
+    { 
+        key: 'tiktok', name: 'TikTok', icon: <TiktokIcon />,
+        brandClasses: {
+            iconBg: 'bg-gradient-to-br from-black via-rose-500 to-cyan-400',
+            button: 'bg-black hover:bg-gray-800',
+        }
+    },
+    { 
+        key: 'instagram', name: 'Instagram', icon: <InstagramIcon />,
+        brandClasses: {
+            iconBg: 'bg-gradient-to-br from-purple-500 via-pink-500 to-yellow-500',
+            button: 'bg-pink-600 hover:bg-pink-700',
+        }
+    },
+    { 
+        key: 'facebook', name: 'Facebook', icon: <FacebookIcon />,
+        brandClasses: {
+            iconBg: 'bg-gradient-to-br from-blue-700 to-blue-500',
+            button: 'bg-blue-700 hover:bg-blue-800',
+        }
+    },
+    { 
+        key: 'discord', name: 'Discord', icon: <DiscordIcon />,
+        brandClasses: {
+            iconBg: 'bg-gradient-to-br from-indigo-600 to-purple-500',
+            button: 'bg-indigo-600 hover:bg-indigo-700',
+        }
+    },
+] as const;
+
+
+const EditPage: React.FC<EditPageProps> = ({ initialData, onSave }) => {
+    const navigate = useNavigate();
+    const [formData, setFormData] = useState<ContactData>(initialData);
+
+    useEffect(() => {
+        const handleOauthMessage = (event: MessageEvent) => {
+            if (event.origin !== window.location.origin) {
+                return;
+            }
+
+            const { type, platform, profileUrl, error } = event.data;
+
+            if (type === 'oauth-success' && platform && profileUrl) {
+                setFormData(prev => ({
+                    ...prev,
+                    socials: {
+                        ...prev.socials,
+                        [platform]: profileUrl
+                    }
+                }));
+            } else if (type === 'oauth-error') {
+                console.error(`OAuth Error for ${platform}:`, error);
+                alert(`Failed to connect ${platform}. Please try again.`);
+            }
+        };
+        
+        window.addEventListener('message', handleOauthMessage);
+
+        return () => {
+            window.removeEventListener('message', handleOauthMessage);
+        };
+    }, []);
+
+    useEffect(() => {
+        // Recalculate authenticity score when dependencies change
+        const connectedSocials = Object.values(formData.socials).filter(link => !!link).length;
+        
+        let socialScore = 0;
+        if (connectedSocials === 1) {
+            socialScore = 15;
+        } else if (connectedSocials === 2) {
+            socialScore = 30;
+        } else if (connectedSocials > 2) {
+            socialScore = 30 + (connectedSocials - 2) * 5;
+        }
+        
+        const finalSocialScore = Math.min(socialScore, 45);
+
+        const videoScore = formData.videoIntroUrl ? 15 : 0;
+        
+        // Only update if the scores have changed to prevent infinite loops
+        if (
+            finalSocialScore !== formData.authenticityScore.socialBinding ||
+            videoScore !== formData.authenticityScore.videoVerification
+        ) {
+            setFormData(prev => ({
+                ...prev,
+                authenticityScore: {
+                    ...prev.authenticityScore,
+                    socialBinding: finalSocialScore,
+                    videoVerification: videoScore,
+                }
+            }));
+        }
+
+    }, [formData.socials, formData.videoIntroUrl, formData.authenticityScore]);
+
+    const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
+        const { name, value } = e.target;
+        setFormData(prev => ({
+            ...prev,
+            [name]: value,
+        }));
+    };
+
+    const handleVideoChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        const file = e.target.files?.[0];
+        if (file) {
+            const videoUrl = URL.createObjectURL(file);
+            setFormData(prev => ({
+                ...prev,
+                videoIntroUrl: videoUrl,
+                videoPosterUrl: '' // Reset poster for new video
+            }));
+        }
+    };
+
+    const handleSocialConnect = (platform: keyof SocialLinksData) => {
+        const isConnected = !!formData.socials[platform];
+
+        if (isConnected) {
+            // Disconnect logic
+            const newSocials = { ...formData.socials, [platform]: '' };
+            setFormData(prev => ({ ...prev, socials: newSocials }));
+            return;
+        }
+
+        // --- Real OAuth URLs ---
+        const redirectUri = `${window.location.origin}/oauth-callback.html`;
+        let oauthUrl = '';
+
+        switch (platform) {
+            case 'github':
+                oauthUrl = `https://github.com/login/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&scope=read:user&state=github`;
+                break;
+            case 'linkedin':
+                oauthUrl = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&state=linkedin&scope=profile%20email%20openid`;
+                break;
+            case 'x':
+                 oauthUrl = `https://twitter.com/i/oauth2/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&scope=users.read%20tweet.read&state=x&code_challenge=challenge&code_challenge_method=plain`;
+                break;
+            case 'tiktok':
+                oauthUrl = `https://www.tiktok.com/v2/auth/authorize?client_key=YOUR_CLIENT_KEY&scope=user.info.basic&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&state=tiktok`;
+                break;
+            case 'instagram':
+                oauthUrl = `https://api.instagram.com/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&scope=user_profile,user_media&response_type=code&state=instagram`;
+                break;
+            case 'facebook':
+                oauthUrl = `https://www.facebook.com/v19.0/dialog/oauth?client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&state=facebook&scope=public_profile,email`;
+                break;
+            case 'discord':
+                oauthUrl = `https://discord.com/api/oauth2/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=identify%20email&state=discord`;
+                break;
+        }
+
+        // --- Popup Window Logic ---
+        const width = 600;
+        const height = 700;
+        const left = (window.innerWidth - width) / 2;
+        const top = (window.innerHeight - height) / 2;
+
+        window.open(
+            oauthUrl,
+            'socialLogin',
+            `width=${width},height=${height},left=${left},top=${top},toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes`
+        );
+    };
+
+    const handleSubmit = (e: React.FormEvent) => {
+        e.preventDefault();
+        onSave(formData);
+        navigate('/contact');
+    };
+
+    const totalScore = formData.authenticityScore.videoVerification + formData.authenticityScore.socialBinding + formData.authenticityScore.cloneMaturity;
+
+    return (
+        <main className="max-w-3xl mx-auto p-4 sm:p-6 md:p-8">
+            <form onSubmit={handleSubmit} className="space-y-8">
+                <div className="flex justify-between items-center mb-4">
+                    <h1 className="text-2xl font-bold text-slate-800">Certify My Homepage</h1>
+                    <div>
+                        <button type="button" onClick={() => navigate('/contact')} className="px-4 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-300 rounded-md hover:bg-slate-50 mr-2">Cancel</button>
+                        <button type="submit" className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700">Save Changes</button>
+                    </div>
+                </div>
+
+                <VirtualIdCard
+                    name={formData.name}
+                    avatarUrl={formData.avatarUrl}
+                    virtualId={formData.virtualId}
+                    totalScore={totalScore}
+                />
+
+                <AuthenticityScore 
+                  videoVerificationScore={formData.authenticityScore.videoVerification}
+                  socialBindingScore={formData.authenticityScore.socialBinding}
+                  cloneMaturityScore={formData.authenticityScore.cloneMaturity}
+                />
+
+                <div className="bg-sky-50 border border-sky-200 text-sky-800 rounded-xl p-3 text-center text-sm -mt-4">
+                    <p>At your current score, you'll earn <span className="font-bold">100 tokens/minute</span> from calls.</p>
+                </div>
+
+                <div 
+                  onClick={() => navigate('/clone-chat')}
+                  className="bg-white rounded-2xl border border-slate-200 p-6 group cursor-pointer transition-colors duration-300 ease-in-out hover:bg-slate-50"
+                  role="button"
+                  aria-label="Train My Digital Clone"
+                >
+                  <div className="flex items-center gap-4">
+                      <div className="flex-shrink-0 w-12 h-12 rounded-2xl flex items-center justify-center bg-gradient-to-br from-indigo-500 to-purple-600 text-white group-hover:scale-110 transition-transform duration-300">
+                          <BrainCircuitIcon />
+                      </div>
+                      <div className="flex-grow">
+                          <h2 className="text-xl font-bold text-slate-800">Train My Digital Clone</h2>
+                          <p className="text-sm text-slate-600 mt-1">
+                              Improve your clone's quality score by continuously chatting with it.
+                          </p>
+                          <p className="text-sm text-indigo-600 font-medium mt-1">
+                              A higher score leads to greater call earnings.
+                          </p>
+                      </div>
+                      <div className="text-slate-400 group-hover:text-slate-800 transition-transform duration-300 group-hover:translate-x-1">
+                        <ArrowRightIcon className="w-5 h-5" />
+                      </div>
+                  </div>
+                </div>
+
+                <EditSection title="About Me">
+                    <label htmlFor="bio" className="sr-only">About Me</label>
+                    <textarea
+                        id="bio"
+                        name="bio"
+                        rows={5}
+                        className="w-full px-3 py-2 text-slate-700 bg-white border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition"
+                        placeholder="Tell us about yourself..."
+                        value={formData.bio}
+                        onChange={handleInputChange}
+                    />
+                </EditSection>
+
+                <EditSection title="Video Introduction">
+                    <label htmlFor="video-upload" className="block aspect-video w-full rounded-lg bg-slate-200 relative overflow-hidden group cursor-pointer">
+                        <video key={formData.videoIntroUrl} poster={formData.videoPosterUrl} src={formData.videoIntroUrl} className="w-full h-full object-cover" />
+                        
+                        <div className="absolute inset-0 bg-black/60 flex flex-col items-center justify-center text-white opacity-0 group-hover:opacity-100 transition-opacity duration-300">
+                            <UploadIcon />
+                            <span className="mt-2 block font-semibold">Upload New Video</span>
+                            <span className="text-xs text-slate-300">Click or drag & drop</span>
+                        </div>
+                         <input id="video-upload" type="file" onChange={handleVideoChange} accept="video/*" className="sr-only"/>
+                    </label>
+                </EditSection>
+                
+                <EditSection title="Verified Social Accounts">
+                    <p className="text-sm text-slate-600">Connect your social accounts to verify your identity. This enhances your Authenticity Score.</p>
+                    <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
+                        {socialPlatforms.map((platform) => {
+                            const isConnected = !!formData.socials[platform.key];
+                            
+                            if (!isConnected) {
+                                return (
+                                    <div key={platform.key} className="flex items-center p-4 rounded-xl border-2 border-dashed border-slate-200 bg-slate-50 transition-colors hover:border-slate-300 hover:bg-slate-100">
+                                        <div className="flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center text-slate-400 bg-slate-200">
+                                            {platform.icon}
+                                        </div>
+                                        <div className="ml-4 flex-grow">
+                                            <h3 className="font-bold text-slate-700 text-sm">{platform.name}</h3>
+                                            <p className="text-xs text-slate-500">Not Connected</p>
+                                        </div>
+                                        <button 
+                                            type="button" 
+                                            onClick={() => handleSocialConnect(platform.key)} 
+                                            className={`px-3 py-1.5 text-xs font-semibold text-white ${platform.brandClasses.button} rounded-md transition-transform transform hover:scale-105`}
+                                        >
+                                            Connect
+                                        </button>
+                                    </div>
+                                );
+                            }
+
+                            return (
+                                <div key={platform.key} className="flex items-center p-4 rounded-xl border border-slate-200 bg-white">
+                                  <div className={`flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center text-white ${platform.brandClasses.iconBg}`}>
+                                      {platform.icon}
+                                  </div>
+                                  <div className="ml-4 flex-grow overflow-hidden">
+                                      <div className="flex items-center">
+                                          <h3 className="font-bold text-slate-800 text-sm">{platform.name}</h3>
+                                          <div className="ml-2 flex items-center bg-green-100 text-green-800 text-xs font-medium px-2 py-0.5 rounded-full">
+                                            <VerifiedIcon className="w-3 h-3 mr-1" />
+                                            Verified
+                                          </div>
+                                      </div>
+                                      <p className="text-xs text-slate-500 font-mono truncate max-w-[120px] sm:max-w-xs">{formData.socials[platform.key].replace(/^https?:\/\/(www\.)?/, '')}</p>
+                                  </div>
+                                  <button 
+                                    type="button" 
+                                    onClick={() => handleSocialConnect(platform.key)} 
+                                    className="text-xs font-semibold text-red-600 hover:text-red-800 hover:bg-red-100 px-2 py-1 rounded-md transition-colors"
+                                  >
+                                    Disconnect
+                                  </button>
+                                </div>
+                            );
+                        })}
+                    </div>
+                </EditSection>
+            </form>
+        </main>
+    );
+};
+
+export default EditPage;

+ 35 - 0
src/pages/Landing/LandingPage.tsx

@@ -0,0 +1,35 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+
+const LandingPage: React.FC = () => {
+  const navigate = useNavigate();
+
+  return (
+    <div className="flex flex-col items-center justify-center min-h-[calc(100vh-60px)]">
+      <div className="text-center p-8">
+        <h1 className="text-4xl sm:text-5xl font-extrabold text-slate-900 tracking-tight">
+          Virtual Identity System
+        </h1>
+        <p className="mt-4 max-w-xl mx-auto text-lg text-slate-600">
+          Your unified, verifiable online presence.
+        </p>
+        <div className="mt-8 flex flex-col sm:flex-row justify-center gap-4">
+          <button
+            onClick={() => navigate('/contact')}
+            className="w-full sm:w-auto inline-flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-transform transform hover:scale-105"
+          >
+            Contact Homepage
+          </button>
+          <button
+            onClick={() => navigate('/edit')}
+            className="w-full sm:w-auto inline-flex items-center justify-center px-8 py-3 border border-slate-300 text-base font-medium rounded-md text-slate-700 bg-white hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-transform transform hover:scale-105"
+          >
+            Certify My Page
+          </button>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default LandingPage;

+ 25 - 0
src/routes.tsx

@@ -0,0 +1,25 @@
+import React from 'react';
+import { Routes, Route } from 'react-router-dom';
+import LandingPage from './pages/Landing/LandingPage';
+import ContactPage from './pages/Contact/ContactPage';
+import EditPage from './pages/Edit/EditPage';
+import CloneChatPage from './pages/CloneChat/CloneChatPage';
+import type { ContactData } from './types';
+
+interface AppRoutesProps {
+  contactData: ContactData;
+  onSave: (data: ContactData) => void;
+}
+
+const AppRoutes: React.FC<AppRoutesProps> = ({ contactData, onSave }) => {
+  return (
+    <Routes>
+      <Route path="/" element={<LandingPage />} />
+      <Route path="/contact" element={<ContactPage data={contactData} />} />
+      <Route path="/edit" element={<EditPage initialData={contactData} onSave={onSave} />} />
+      <Route path="/clone-chat" element={<CloneChatPage />} />
+    </Routes>
+  );
+};
+
+export default AppRoutes;

+ 34 - 0
src/types.ts

@@ -0,0 +1,34 @@
+export interface SocialLinksData {
+  linkedin: string;
+  x: string;
+  github: string;
+  tiktok: string;
+  instagram: string;
+  facebook: string;
+  discord: string;
+}
+
+export interface AuthenticityScoreData {
+  videoVerification: number;
+  socialBinding: number;
+  cloneMaturity: number;
+}
+
+// FIX: Add missing Certification interface definition.
+export interface Certification {
+  name: string;
+  issuer: string;
+  date: string;
+  imageUrl: string;
+}
+
+export interface ContactData {
+  name: string;
+  avatarUrl: string;
+  virtualId: string;
+  bio: string;
+  socials: SocialLinksData;
+  authenticityScore: AuthenticityScoreData;
+  videoIntroUrl: string;
+  videoPosterUrl: string;
+}

+ 29 - 0
tsconfig.json

@@ -0,0 +1,29 @@
+{
+  "compilerOptions": {
+    "target": "ES2022",
+    "experimentalDecorators": true,
+    "useDefineForClassFields": false,
+    "module": "ESNext",
+    "lib": [
+      "ES2022",
+      "DOM",
+      "DOM.Iterable"
+    ],
+    "skipLibCheck": true,
+    "types": [
+      "node"
+    ],
+    "moduleResolution": "bundler",
+    "isolatedModules": true,
+    "moduleDetection": "force",
+    "allowJs": true,
+    "jsx": "react-jsx",
+    "paths": {
+      "@/*": [
+        "./*"
+      ]
+    },
+    "allowImportingTsExtensions": true,
+    "noEmit": true
+  }
+}

+ 34 - 0
types.ts

@@ -0,0 +1,34 @@
+export interface SocialLinksData {
+  linkedin: string;
+  x: string;
+  github: string;
+  tiktok: string;
+  instagram: string;
+  facebook: string;
+  discord: string;
+}
+
+export interface AuthenticityScoreData {
+  videoVerification: number;
+  socialBinding: number;
+  cloneMaturity: number;
+}
+
+// FIX: Add missing Certification interface definition.
+export interface Certification {
+  name: string;
+  issuer: string;
+  date: string;
+  imageUrl: string;
+}
+
+export interface ContactData {
+  name: string;
+  avatarUrl: string;
+  virtualId: string;
+  bio: string;
+  socials: SocialLinksData;
+  authenticityScore: AuthenticityScoreData;
+  videoIntroUrl: string;
+  videoPosterUrl: string;
+}

+ 23 - 0
vite.config.ts

@@ -0,0 +1,23 @@
+import path from 'path';
+import { defineConfig, loadEnv } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig(({ mode }) => {
+    const env = loadEnv(mode, '.', '');
+    return {
+      server: {
+        port: 3000,
+        host: '0.0.0.0',
+      },
+      plugins: [react()],
+      define: {
+        'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
+        'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
+      },
+      resolve: {
+        alias: {
+          '@': path.resolve(__dirname, '.'),
+        }
+      }
+    };
+});